Σ(゚Д゚;≡;゚д゚)Σ(゚Д゚;≡;゚д゚)Σ(゚Д゚;≡;゚д゚)始,故人唐宰相鲁公,🆚开府南服,余以布衣从戎。明年,别公漳水湄。后明年,公以事过张睢阳庙及颜杲卿所尝往来处,悲歌慷慨,卒不负其言而从之游。今其诗具在,可考也。😭 HEX
HEX
Server: Apache
System: Linux webm006.cluster120.gra.hosting.ovh.net 6.18.39-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Tue Jul 21 12:03:15 CEST 2026 x86_64
User: studionolh (122383)
PHP: 7.3.33
Disabled: _dyuweyrj4,_dyuweyrj4r,dl
Upload Files
File: /home/s/t/u/studionolh/www/wp-includes/Requests/Cookie/reusable-blocks/config.tar
core.json000064400000001613152334410420006366 0ustar00{
	"features": {
		"activity-panels": true,
		"analytics": true,
		"coupons": true,
		"customer-effort-score-tracks": true,
		"experimental-products-task": true,
		"experimental-import-products-task": true,
		"experimental-fashion-sample-products": true,
		"shipping-smart-defaults": true,
		"shipping-setting-tour": true,
		"homescreen": true,
		"marketing": true,
		"multichannel-marketing": true,
		"minified-js": false,
		"mobile-app-banner": true,
		"navigation": true,
		"new-product-management-experience": false,
		"onboarding": true,
		"onboarding-tasks": true,
		"remote-inbox-notifications": true,
		"remote-free-extensions": true,
		"payment-gateway-suggestions": true,
		"settings": false,
		"shipping-label-banner": true,
		"subscriptions": true,
		"store-alerts": true,
		"transient-notices": true,
		"woo-mobile-welcome": true,
		"wc-pay-promotion": true,
		"wc-pay-welcome-page": true
	}
}
development.json000064400000001612152334410420007757 0ustar00{
	"features": {
		"activity-panels": true,
		"analytics": true,
		"coupons": true,
		"customer-effort-score-tracks": true,
		"experimental-products-task": true,
		"experimental-import-products-task": true,
		"experimental-fashion-sample-products": true,
		"shipping-smart-defaults": true,
		"shipping-setting-tour": true,
		"homescreen": true,
		"marketing": true,
		"multichannel-marketing": true,
		"minified-js": true,
		"mobile-app-banner": true,
		"navigation": true,
		"new-product-management-experience": false,
		"onboarding": true,
		"onboarding-tasks": true,
		"payment-gateway-suggestions": true,
		"remote-inbox-notifications": true,
		"remote-free-extensions": true,
		"settings": false,
		"shipping-label-banner": true,
		"subscriptions": true,
		"store-alerts": true,
		"transient-notices": true,
		"woo-mobile-welcome": true,
		"wc-pay-promotion": true,
		"wc-pay-welcome-page": true
	}
}
indexing-reasons.php000064400000001473152335317770010554 0ustar00<?php

namespace Yoast\WP\SEO\Config;

/**
 * Class Indexing_Reasons. Contains constants that aren't context specific.
 */
class Indexing_Reasons {

	/**
	 * Represents the reason that the indexing process failed and should be tried again.
	 */
	const REASON_INDEXING_FAILED = 'indexing_failed';

	/**
	 * Represents the reason that the permalink settings are changed.
	 */
	const REASON_PERMALINK_SETTINGS = 'permalink_settings_changed';

	/**
	 * Represents the reason that the category base is changed.
	 */
	const REASON_CATEGORY_BASE_PREFIX = 'category_base_changed';

	/**
	 * Represents the reason that the tag base is changed.
	 */
	const REASON_TAG_BASE_PREFIX = 'tag_base_changed';

	/**
	 * Represents the reason that the home url option is changed.
	 */
	const REASON_HOME_URL_OPTION = 'home_url_option_changed';
}
conflicting-plugins.php000064400000012407152335317770011254 0ustar00<?php

namespace Yoast\WP\SEO\Config;

/**
 * Conflicting_Plugins class that holds all known conflicting plugins.
 */
class Conflicting_Plugins {

	const OPEN_GRAPH_PLUGINS = [
		'2-click-socialmedia-buttons/2-click-socialmedia-buttons.php',
		// 2 Click Social Media Buttons.
		'add-link-to-facebook/add-link-to-facebook.php',         // Add Link to Facebook.
		'add-meta-tags/add-meta-tags.php',                       // Add Meta Tags.
		'easy-facebook-share-thumbnails/esft.php',               // Easy Facebook Share Thumbnail.
		'facebook/facebook.php',                                 // Facebook (official plugin).
		'facebook-awd/AWD_facebook.php',                         // Facebook AWD All in one.
		'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php',
		// Facebook Featured Image & OG Meta Tags.
		'facebook-meta-tags/facebook-metatags.php',              // Facebook Meta Tags.
		'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php',
		// Facebook Open Graph Meta Tags for WordPress.
		'facebook-revised-open-graph-meta-tag/index.php',        // Facebook Revised Open Graph Meta Tag.
		'facebook-thumb-fixer/_facebook-thumb-fixer.php',        // Facebook Thumb Fixer.
		'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php',
		// Fedmich's Facebook Open Graph Meta.
		'network-publisher/networkpub.php',                      // Network Publisher.
		'nextgen-facebook/nextgen-facebook.php',                 // NextGEN Facebook OG.
		'opengraph/opengraph.php',                               // Open Graph.
		'open-graph-protocol-framework/open-graph-protocol-framework.php',
		// Open Graph Protocol Framework.
		'seo-facebook-comments/seofacebook.php',                 // SEO Facebook Comments.
		'sexybookmarks/sexy-bookmarks.php',                      // Shareaholic.
		'shareaholic/sexy-bookmarks.php',                        // Shareaholic.
		'sharepress/sharepress.php',                             // SharePress.
		'simple-facebook-connect/sfc.php',                       // Simple Facebook Connect.
		'social-discussions/social-discussions.php',             // Social Discussions.
		'social-sharing-toolkit/social_sharing_toolkit.php',     // Social Sharing Toolkit.
		'socialize/socialize.php',                               // Socialize.
		'only-tweet-like-share-and-google-1/tweet-like-plusone.php',
		// Tweet, Like, Google +1 and Share.
		'wordbooker/wordbooker.php',                             // Wordbooker.
		'wpsso/wpsso.php',                                       // WordPress Social Sharing Optimization.
		'wp-caregiver/wp-caregiver.php',                         // WP Caregiver.
		'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php',
		// WP Facebook Like Send & Open Graph Meta.
		'wp-facebook-open-graph-protocol/wp-facebook-ogp.php',   // WP Facebook Open Graph protocol.
		'wp-ogp/wp-ogp.php',                                     // WP-OGP.
		'zoltonorg-social-plugin/zosp.php',                      // Zolton.org Social Plugin.
	];

	const XML_SITEMAPS_PLUGINS = [
		'google-sitemap-plugin/google-sitemap-plugin.php',
		// Google Sitemap (BestWebSoft).
		'xml-sitemaps/xml-sitemaps.php',
		// XML Sitemaps (Denis de Bernardy and Mike Koepke).
		'bwp-google-xml-sitemaps/bwp-simple-gxs.php',
		// Better WordPress Google XML Sitemaps (Khang Minh).
		'google-sitemap-generator/sitemap.php',
		// Google XML Sitemaps (Arne Brachhold).
		'xml-sitemap-feed/xml-sitemap.php',
		// XML Sitemap & Google News feeds (RavanH).
		'google-monthly-xml-sitemap/monthly-xml-sitemap.php',
		// Google Monthly XML Sitemap (Andrea Pernici).
		'simple-google-sitemap-xml/simple-google-sitemap-xml.php',
		// Simple Google Sitemap XML (iTx Technologies).
		'another-simple-xml-sitemap/another-simple-xml-sitemap.php',
		// Another Simple XML Sitemap.
		'xml-maps/google-sitemap.php',
		// Xml Sitemap (Jason Martens).
		'google-xml-sitemap-generator-by-anton-dachauer/adachauer-google-xml-sitemap.php',
		// Google XML Sitemap Generator by Anton Dachauer (Anton Dachauer).
		'wp-xml-sitemap/wp-xml-sitemap.php',
		// WP XML Sitemap (Team Vivacity).
		'sitemap-generator-for-webmasters/sitemap.php',
		// Sitemap Generator for Webmasters (iwebslogtech).
		'xml-sitemap-xml-sitemapcouk/xmls.php',
		// XML Sitemap - XML-Sitemap.co.uk (Simon Hancox).
		'sewn-in-xml-sitemap/sewn-xml-sitemap.php',
		// Sewn In XML Sitemap (jcow).
		'rps-sitemap-generator/rps-sitemap-generator.php',
		// RPS Sitemap Generator (redpixelstudios).
	];

	const CLOAKING_PLUGINS = [
		'rs-head-cleaner/rs-head-cleaner.php',
		// RS Head Cleaner Plus https://wordpress.org/plugins/rs-head-cleaner/.
		'rs-head-cleaner-lite/rs-head-cleaner-lite.php',
		// RS Head Cleaner Lite https://wordpress.org/plugins/rs-head-cleaner-lite/.
	];

	const SEO_PLUGINS = [
		'all-in-one-seo-pack/all_in_one_seo_pack.php',           // All in One SEO Pack.
		'seo-ultimate/seo-ultimate.php',                         // SEO Ultimate.
		'seo-by-rank-math/rank-math.php',                        // Rank Math.
	];

	/**
	 * Returns the list of all conflicting plugins.
	 *
	 * @return array The list of all conflicting plugins.
	 */
	public static function all_plugins() {
		return \array_merge(
			self::OPEN_GRAPH_PLUGINS,
			self::XML_SITEMAPS_PLUGINS,
			self::CLOAKING_PLUGINS,
			self::SEO_PLUGINS
		);
	}
}
migration-status.php000064400000012220152335317770010601 0ustar00<?php

namespace Yoast\WP\SEO\Config;

/**
 * Migration_Status class.
 *
 * Used to validate whether or not migrations have been run and whether or not they should be run again.
 */
class Migration_Status {

	/**
	 * The migration option key.
	 *
	 * @var string
	 */
	const MIGRATION_OPTION_KEY = 'yoast_migrations_';

	/**
	 * The migration options.
	 *
	 * @var array
	 */
	protected $migration_options = [];

	/**
	 * Checks if a given migration should be run.
	 *
	 * @param string $name    The name of the migration.
	 * @param string $version The current version.
	 *
	 * @return bool Whether or not the migration should be run.
	 */
	public function should_run_migration( $name, $version = \WPSEO_VERSION ) {
		$migration_status = $this->get_migration_status( $name );

		// Check if we've attempted to run this migration in the past 10 minutes. If so, it may still be running.
		if ( \array_key_exists( 'lock', $migration_status ) ) {
			$timestamp = \strtotime( '-10 minutes' );

			return $timestamp > $migration_status['lock'];
		}

		// Is the migration version less than the current version.
		return \version_compare( $migration_status['version'], $version, '<' );
	}

	/**
	 * Checks whether or not the given migration is at least the given version, defaults to checking for the latest version.
	 *
	 * @param string $name    The name of the migration.
	 * @param string $version The version to check, defaults to the latest version.
	 *
	 * @return bool Whether or not the requested migration is at least the requested version.
	 */
	public function is_version( $name, $version = \WPSEO_VERSION ) {
		$migration_status = $this->get_migration_status( $name );

		return \version_compare( $version, $migration_status['version'], '<=' );
	}

	/**
	 * Gets the error of a given migration if it exists.
	 *
	 * @param string $name The name of the migration.
	 *
	 * @return bool|array False if there is no error, otherwise the error.
	 */
	public function get_error( $name ) {
		$migration_status = $this->get_migration_status( $name );

		if ( ! isset( $migration_status['error'] ) ) {
			return false;
		}

		return $migration_status['error'];
	}

	/**
	 * Sets an error for the migration.
	 *
	 * @param string $name    The name of the migration.
	 * @param string $message Message explaining the reason for the error.
	 * @param string $version The current version.
	 *
	 * @return void
	 */
	public function set_error( $name, $message, $version = \WPSEO_VERSION ) {
		$migration_status = $this->get_migration_status( $name );

		$migration_status['error'] = [
			'time'    => \strtotime( 'now' ),
			'version' => $version,
			'message' => $message,
		];

		$this->set_migration_status( $name, $migration_status );
	}

	/**
	 * Updates the migration version to the latest version.
	 *
	 * @param string $name    The name of the migration.
	 * @param string $version The current version.
	 *
	 * @return void
	 */
	public function set_success( $name, $version = \WPSEO_VERSION ) {
		$migration_status = $this->get_migration_status( $name );
		unset( $migration_status['lock'] );
		unset( $migration_status['error'] );
		$migration_status['version'] = $version;
		$this->set_migration_status( $name, $migration_status );
	}

	/**
	 * Locks the migration status.
	 *
	 * @param string $name The name of the migration.
	 *
	 * @return bool Whether or not the migration was succesfully locked.
	 */
	public function lock_migration( $name ) {
		$migration_status         = $this->get_migration_status( $name );
		$migration_status['lock'] = \strtotime( 'now' );

		return $this->set_migration_status( $name, $migration_status );
	}

	/**
	 * Retrieves the migration option.
	 *
	 * @param string $name The name of the migration.
	 *
	 * @return bool|array The status of the migration, false if no status exists.
	 */
	protected function get_migration_status( $name ) {
		$current_blog_id = \get_current_blog_id();
		if ( ! isset( $this->migration_options[ $current_blog_id ][ $name ] ) ) {
			$migration_status = \get_option( self::MIGRATION_OPTION_KEY . $name );

			if ( ! \is_array( $migration_status ) || ! isset( $migration_status['version'] ) ) {
				$migration_status = [ 'version' => '0.0' ];
			}

			if ( ! isset( $this->migration_options[ $current_blog_id ] ) ) {
				$this->migration_options[ $current_blog_id ] = [];
			}
			$this->migration_options[ $current_blog_id ][ $name ] = $migration_status;
		}

		return $this->migration_options[ $current_blog_id ][ $name ];
	}

	/**
	 * Retrieves the migration option.
	 *
	 * @param string $name             The name of the migration.
	 * @param array  $migration_status The migration status.
	 *
	 * @return bool True if the status was succesfully updated, false otherwise.
	 */
	protected function set_migration_status( $name, $migration_status ) {
		if ( ! \is_array( $migration_status ) || ! isset( $migration_status['version'] ) ) {
			return false;
		}
		$current_blog_id = \get_current_blog_id();

		if ( ! isset( $this->migration_options[ $current_blog_id ] ) ) {
			$this->migration_options[ $current_blog_id ] = [];
		}
		$this->migration_options[ $current_blog_id ][ $name ] = $migration_status;

		return \update_option( self::MIGRATION_OPTION_KEY . $name, $migration_status );
	}
}
researcher-languages.php000064400000000514152335317770011361 0ustar00<?php

namespace Yoast\WP\SEO\Config;

/**
 * Holds all languages supported with specific researches for our readability analysis.
 */
class Researcher_Languages {

	const SUPPORTED_LANGUAGES = [ 'ar', 'ca', 'de', 'en', 'es', 'fa', 'fr', 'he', 'hu', 'id', 'it', 'nb', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'cs', 'sk', 'el', 'ja' ];
}
schema-types.php000064400000010577152335317770007706 0ustar00<?php

namespace Yoast\WP\SEO\Config;

/**
 * Class Schema_Types.
 */
class Schema_Types {

	/**
	 * Holds the possible schema page types.
	 *
	 * Capitalized in this way so the value can be directly used in the schema output.
	 *
	 * @var string[]
	 */
	const PAGE_TYPES = [
		'WebPage'           => '',
		'ItemPage'          => '',
		'AboutPage'         => '',
		'FAQPage'           => '',
		'QAPage'            => '',
		'ProfilePage'       => '',
		'ContactPage'       => '',
		'MedicalWebPage'    => '',
		'CollectionPage'    => '',
		'CheckoutPage'      => '',
		'RealEstateListing' => '',
		'SearchResultsPage' => '',
	];

	/**
	 * Holds the possible schema article types.
	 *
	 * Capitalized in this way so the value can be directly used in the schema output.
	 *
	 * @var string[]
	 */
	const ARTICLE_TYPES = [
		'Article'                  => '',
		'BlogPosting'              => '',
		'SocialMediaPosting'       => '',
		'NewsArticle'              => '',
		'AdvertiserContentArticle' => '',
		'SatiricalArticle'         => '',
		'ScholarlyArticle'         => '',
		'TechArticle'              => '',
		'Report'                   => '',
		'None'                     => '',
	];

	/**
	 * Gets the page type options.
	 *
	 * @return array[] The schema page type options.
	 */
	public function get_page_type_options() {
		return [
			[
				'name'  => \__( 'Web Page', 'wordpress-seo' ),
				'value' => 'WebPage',
			],
			[
				'name'  => \__( 'Item Page', 'wordpress-seo' ),
				'value' => 'ItemPage',
			],
			[
				'name'  => \__( 'About Page', 'wordpress-seo' ),
				'value' => 'AboutPage',
			],
			[
				'name'  => \__( 'FAQ Page', 'wordpress-seo' ),
				'value' => 'FAQPage',
			],
			[
				'name'  => \__( 'QA Page', 'wordpress-seo' ),
				'value' => 'QAPage',
			],
			[
				'name'  => \__( 'Profile Page', 'wordpress-seo' ),
				'value' => 'ProfilePage',
			],
			[
				'name'  => \__( 'Contact Page', 'wordpress-seo' ),
				'value' => 'ContactPage',
			],
			[
				'name'  => \__( 'Medical Web Page', 'wordpress-seo' ),
				'value' => 'MedicalWebPage',
			],
			[
				'name'  => \__( 'Collection Page', 'wordpress-seo' ),
				'value' => 'CollectionPage',
			],
			[
				'name'  => \__( 'Checkout Page', 'wordpress-seo' ),
				'value' => 'CheckoutPage',
			],
			[
				'name'  => \__( 'Real Estate Listing', 'wordpress-seo' ),
				'value' => 'RealEstateListing',
			],
			[
				'name'  => \__( 'Search Results Page', 'wordpress-seo' ),
				'value' => 'SearchResultsPage',
			],
		];
	}

	/**
	 * Gets the article type options.
	 *
	 * @return array[] The schema article type options.
	 */
	public function get_article_type_options() {
		/**
		 * Filter: 'wpseo_schema_article_types_labels' - Allow developers to filter the available article types and their labels.
		 *
		 * Make sure when you filter this to also filter `wpseo_schema_article_types`.
		 *
		 * @api array $schema_article_types_labels The available schema article types and their labels.
		 */
		return \apply_filters(
			'wpseo_schema_article_types_labels',
			[
				[
					'name'  => \__( 'Article', 'wordpress-seo' ),
					'value' => 'Article',
				],
				[
					'name'  => \__( 'Blog Post', 'wordpress-seo' ),
					'value' => 'BlogPosting',
				],
				[
					'name'  => \__( 'Social Media Posting', 'wordpress-seo' ),
					'value' => 'SocialMediaPosting',
				],
				[
					'name'  => \__( 'News Article', 'wordpress-seo' ),
					'value' => 'NewsArticle',
				],
				[
					'name'  => \__( 'Advertiser Content Article', 'wordpress-seo' ),
					'value' => 'AdvertiserContentArticle',
				],
				[
					'name'  => \__( 'Satirical Article', 'wordpress-seo' ),
					'value' => 'SatiricalArticle',
				],
				[
					'name'  => \__( 'Scholarly Article', 'wordpress-seo' ),
					'value' => 'ScholarlyArticle',
				],
				[
					'name'  => \__( 'Tech Article', 'wordpress-seo' ),
					'value' => 'TechArticle',
				],
				[
					'name'  => \__( 'Report', 'wordpress-seo' ),
					'value' => 'Report',
				],
				[
					'name'  => \__( 'None', 'wordpress-seo' ),
					'value' => 'None',
				],
			]
		);
	}

	/**
	 * Gets the values of the article type options.
	 *
	 * @return array[] The values of the Schema article type options.
	 */
	public function get_article_type_options_values() {
		$article_types       = $this->get_article_type_options();
		$article_type_values = [];

		foreach ( $article_types as $type ) {
			$article_type_values[] = $type['value'];
		}

		return $article_type_values;
	}
}
wincher-pkce-provider.php000064400000016002152335317770011500 0ustar00<?php

namespace Yoast\WP\SEO\Config;

use UnexpectedValueException;
use YoastSEO_Vendor\GuzzleHttp\Exception\BadResponseException;
use YoastSEO_Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use YoastSEO_Vendor\League\OAuth2\Client\Provider\GenericProvider;
use YoastSEO_Vendor\League\OAuth2\Client\Token\AccessToken;
use YoastSEO_Vendor\League\OAuth2\Client\Token\AccessTokenInterface;
use YoastSEO_Vendor\League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use YoastSEO_Vendor\Psr\Http\Message\RequestInterface;
use YoastSEO_Vendor\Psr\Log\InvalidArgumentException;

/**
 * Class Wincher_PKCE_Provider
 *
 * @codeCoverageIgnore Ignoring as this class is purely a temporary wrapper until https://github.com/thephpleague/oauth2-client/pull/901 is merged.
 *
 * @phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase -- This class extends an external class.
 * @phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- This class extends an external class.
 */
class Wincher_PKCE_Provider extends GenericProvider {

	use BearerAuthorizationTrait;

	/**
	 * The method to use.
	 *
	 * @var string
	 */
	protected $pkceMethod = null;

	/**
	 * The PKCE code.
	 *
	 * @var string
	 */
	protected $pkceCode;

	/**
	 * Set the value of the pkceCode parameter.
	 *
	 * When using PKCE this should be set before requesting an access token.
	 *
	 * @param string $pkce_code The value for the pkceCode.
	 * @return self
	 */
	public function setPkceCode( $pkce_code ) {
		$this->pkceCode = $pkce_code;
		return $this;
	}

	/**
	 * Returns the current value of the pkceCode parameter.
	 *
	 * This can be accessed by the redirect handler during authorization.
	 *
	 * @return string
	 */
	public function getPkceCode() {
		return $this->pkceCode;
	}

	/**
	 * Returns a new random string to use as PKCE code_verifier and
	 * hashed as code_challenge parameters in an authorization flow.
	 * Must be between 43 and 128 characters long.
	 *
	 * @param int $length Length of the random string to be generated.
	 *
	 * @return string
	 *
	 * @throws \Exception Throws exception if an invalid value is passed to random_bytes.
	 */
	protected function getRandomPkceCode( $length = 64 ) {
		return \substr(
			\strtr(
				// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
				\base64_encode( \random_bytes( $length ) ),
				'+/',
				'-_'
			),
			0,
			$length
		);
	}

	/**
	 * Returns the current value of the pkceMethod parameter.
	 *
	 * @return string|null
	 */
	protected function getPkceMethod() {
		return $this->pkceMethod;
	}

	/**
	 * Returns authorization parameters based on provided options.
	 *
	 * @param array $options The options to use in the authorization parameters.
	 *
	 * @return array The authorization parameters
	 *
	 * @throws InvalidArgumentException Throws exception if an invalid PCKE method is passed in the options.
	 * @throws \Exception               When something goes wrong with generating the PKCE code.
	 */
	protected function getAuthorizationParameters( array $options ) {
		if ( empty( $options['state'] ) ) {
			$options['state'] = $this->getRandomState();
		}

		if ( empty( $options['scope'] ) ) {
			$options['scope'] = $this->getDefaultScopes();
		}

		$options += [
			'response_type'   => 'code',
		];

		if ( \is_array( $options['scope'] ) ) {
			$separator        = $this->getScopeSeparator();
			$options['scope'] = \implode( $separator, $options['scope'] );
		}

		// Store the state as it may need to be accessed later on.
		$this->state = $options['state'];

		$pkce_method = $this->getPkceMethod();
		if ( ! empty( $pkce_method ) ) {
			$this->pkceCode = $this->getRandomPkceCode();
			if ( $pkce_method === 'S256' ) {
				$options['code_challenge'] = \trim(
					\strtr(
						// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
						\base64_encode( \hash( 'sha256', $this->pkceCode, true ) ),
						'+/',
						'-_'
					),
					'='
				);
			}
			elseif ( $pkce_method === 'plain' ) {
				$options['code_challenge'] = $this->pkceCode;
			}
			else {
				throw new InvalidArgumentException( 'Unknown PKCE method "' . $pkce_method . '".' );
			}
			$options['code_challenge_method'] = $pkce_method;
		}

		// Business code layer might set a different redirect_uri parameter.
		// Depending on the context, leave it as-is.
		if ( ! isset( $options['redirect_uri'] ) ) {
			$options['redirect_uri'] = $this->redirectUri;
		}

		$options['client_id'] = $this->clientId;

		return $options;
	}

	/**
	 * Requests an access token using a specified grant and option set.
	 *
	 * @param mixed $grant   The grant to request access for.
	 * @param array $options The options to use with the current request.
	 *
	 * @return AccessToken|AccessTokenInterface The access token.
	 *
	 * @throws UnexpectedValueException Exception thrown if the provider response contains errors.
	 */
	public function getAccessToken( $grant, array $options = [] ) {
		$grant = $this->verifyGrant( $grant );

		$params = [
			'client_id'     => $this->clientId,
			'client_secret' => $this->clientSecret,
			'redirect_uri'  => $this->redirectUri,
		];

		if ( ! empty( $this->pkceCode ) ) {
			$params['code_verifier'] = $this->pkceCode;
		}

		$params   = $grant->prepareRequestParameters( $params, $options );
		$request  = $this->getAccessTokenRequest( $params );
		$response = $this->getParsedResponse( $request );

		if ( \is_array( $response ) === false ) {
			throw new UnexpectedValueException(
				'Invalid response received from Authorization Server. Expected JSON.'
			);
		}

		$prepared = $this->prepareAccessTokenResponse( $response );
		$token    = $this->createAccessToken( $prepared, $grant );

		return $token;
	}

	/**
	 * Returns all options that can be configured.
	 *
	 * @return array The configurable options.
	 */
	protected function getConfigurableOptions() {
		return \array_merge(
			$this->getRequiredOptions(),
			[
				'accessTokenMethod',
				'accessTokenResourceOwnerId',
				'scopeSeparator',
				'responseError',
				'responseCode',
				'responseResourceOwnerId',
				'scopes',
				'pkceMethod',
			]
		);
	}

	/**
	 * Parses the request response.
	 *
	 * @param RequestInterface $request The request interface.
	 *
	 * @return array The parsed response.
	 *
	 * @throws IdentityProviderException Exception thrown if there is no proper identity provider.
	 */
	public function getParsedResponse( RequestInterface $request ) {
		try {
			$response = $this->getResponse( $request );
		} catch ( BadResponseException $e ) {
			$response = $e->getResponse();
		}

		$parsed = $this->parseResponse( $response );

		$this->checkResponse( $response, $parsed );

		// We always expect an array from the API except for on DELETE requests.
		// We convert to an array here to prevent problems with array_key_exists on PHP8.
		if ( ! \is_array( $parsed ) ) {
			$parsed = [ 'data' => [] ];
		}

		// Add the response code as this is omitted from Winchers API.
		if ( ! \array_key_exists( 'status', $parsed ) ) {
			$parsed['status'] = $response->getStatusCode();
		}

		return $parsed;
	}
}
semrush-client.php000064400000005163152335317770010241 0ustar00<?php

namespace Yoast\WP\SEO\Config;

use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Token_Exception;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Wrappers\WP_Remote_Handler;
use YoastSEO_Vendor\GuzzleHttp\Client;
use YoastSEO_Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use YoastSEO_Vendor\League\OAuth2\Client\Provider\GenericProvider;

/**
 * Class SEMrush_Client
 */
class SEMrush_Client extends OAuth_Client {

	/**
	 * The option's key.
	 */
	const TOKEN_OPTION = 'semrush_tokens';

	/**
	 * SEMrush_Client constructor.
	 *
	 * @param Options_Helper    $options_helper    The Options_Helper instance.
	 * @param WP_Remote_Handler $wp_remote_handler The request handler.
	 *
	 * @throws Empty_Property_Exception Throws when one of the required properties is empty.
	 */
	public function __construct(
		Options_Helper $options_helper,
		WP_Remote_Handler $wp_remote_handler
	) {
		$provider = new GenericProvider(
			[
				'clientId'                => 'yoast',
				'clientSecret'            => 'YdqNsWwnP4vE54WO1ugThKEjGMxMAHJt',
				'redirectUri'             => 'https://oauth.semrush.com/oauth2/yoast/success',
				'urlAuthorize'            => 'https://oauth.semrush.com/oauth2/authorize',
				'urlAccessToken'          => 'https://oauth.semrush.com/oauth2/access_token',
				'urlResourceOwnerDetails' => 'https://oauth.semrush.com/oauth2/resource',
			],
			[
				'httpClient' => new Client( [ 'handler' => $wp_remote_handler ] ),
			]
		);

		parent::__construct(
			self::TOKEN_OPTION,
			$provider,
			$options_helper
		);
	}

	/**
	 * Performs the specified request.
	 *
	 * @codeCoverageIgnore
	 *
	 * @param string $method  The HTTP method to use.
	 * @param string $url     The URL to send the request to.
	 * @param array  $options The options to pass along to the request.
	 *
	 * @return mixed The parsed API response.
	 *
	 * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data.
	 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
	 * @throws Empty_Token_Exception Exception thrown if the token is empty.
	 */
	public function do_request( $method, $url, array $options ) {
		// Add the access token to the GET parameters as well since this is what
		// the SEMRush API expects.
		$options = \array_merge_recursive(
			$options,
			[
				'params' => [
					'access_token' => $this->get_tokens()->access_token,
				],
			]
		);

		return parent::do_request( $method, $url, $options );
	}
}
migrations/20211020091404_AddObjectTimestamps.php000064400000003007152335317770014765 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * AddObjectTimestamps class.
 */
class AddObjectTimestamps extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		$this->add_column(
			$this->get_table_name(),
			'object_last_modified',
			'datetime',
			[
				'null'    => true,
				'default' => null,
			]
		);
		$this->add_column(
			$this->get_table_name(),
			'object_published_at',
			'datetime',
			[
				'null'    => true,
				'default' => null,
			]
		);
		$this->add_index(
			$this->get_table_name(),
			[
				'object_published_at',
				'is_robots_noindex',
				'object_type',
				'object_sub_type',
			],
			[
				'name' => 'published_sitemap_index',
			]
		);
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
		$this->remove_column( $this->get_table_name(), 'object_last_modified' );
		$this->remove_column( $this->get_table_name(), 'object_published_at' );
		$this->remove_index(
			$this->get_table_name(),
			[
				'object_published_at',
				'is_robots_noindex',
				'object_type',
				'object_sub_type',
			],
			[
				'name' => 'published_sitemap_index',
			]
		);
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20200428194858_ExpandIndexableColumnLengths.php000064400000003341152335317770016671 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class ExpandIndexableColumnLengths.
 */
class ExpandIndexableColumnLengths extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$this->change_column( $this->get_table_name(), 'title', 'text', [ 'null' => true ] );
		$this->change_column( $this->get_table_name(), 'open_graph_title', 'text', [ 'null' => true ] );
		$this->change_column( $this->get_table_name(), 'twitter_title', 'text', [ 'null' => true ] );
		$this->change_column( $this->get_table_name(), 'open_graph_image_source', 'text', [ 'null' => true ] );
		$this->change_column( $this->get_table_name(), 'twitter_image_source', 'text', [ 'null' => true ] );
	}

	/**
	 * Migration down.
	 */
	public function down() {
		$attr_limit_191 = [
			'null'  => true,
			'limit' => 191,
		];

		$this->change_column(
			$this->get_table_name(),
			'title',
			'string',
			$attr_limit_191
		);
		$this->change_column(
			$this->get_table_name(),
			'opengraph_title',
			'string',
			$attr_limit_191
		);
		$this->change_column(
			$this->get_table_name(),
			'twitter_title',
			'string',
			$attr_limit_191
		);
		$this->change_column(
			$this->get_table_name(),
			'open_graph_image_source',
			'string',
			$attr_limit_191
		);
		$this->change_column(
			$this->get_table_name(),
			'twitter_image_source',
			'string',
			$attr_limit_191
		);
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20200420073606_AddColumnsToIndexables.php000064400000004052152335317770015441 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class AddColumnsToIndexables.
 */
class AddColumnsToIndexables extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$tables  = $this->get_tables();
		$blog_id = \get_current_blog_id();
		foreach ( $tables as $table ) {
			$this->add_column(
				$table,
				'blog_id',
				'biginteger',
				[
					'null'    => false,
					'limit'   => 20,
					'default' => $blog_id,
				]
			);
		}

		$attr_limit_32 = [
			'null'  => true,
			'limit' => 32,
		];
		$attr_limit_64 = [
			'null'  => true,
			'limit' => 64,
		];

		$indexable_table = $this->get_indexable_table();
		$this->add_column( $indexable_table, 'language', 'string', $attr_limit_32 );
		$this->add_column( $indexable_table, 'region', 'string', $attr_limit_32 );
		$this->add_column( $indexable_table, 'schema_page_type', 'string', $attr_limit_64 );
		$this->add_column( $indexable_table, 'schema_article_type', 'string', $attr_limit_64 );
	}

	/**
	 * Migration down.
	 */
	public function down() {
		$tables = $this->get_tables();
		foreach ( $tables as $table ) {
			$this->remove_column( $table, 'blog_id' );
		}

		$indexable_table = $this->get_indexable_table();
		$this->remove_column( $indexable_table, 'language' );
		$this->remove_column( $indexable_table, 'region' );
		$this->remove_column( $indexable_table, 'schema_page_type' );
		$this->remove_column( $indexable_table, 'schema_article_type' );
	}

	/**
	 * Retrieves the Indexable table.
	 *
	 * @return string The Indexable table name.
	 */
	protected function get_indexable_table() {
		return Model::get_table_name( 'Indexable' );
	}

	/**
	 * Retrieves the table names to use.
	 *
	 * @return string[] The table names to use.
	 */
	protected function get_tables() {
		return [
			$this->get_indexable_table(),
			Model::get_table_name( 'Indexable_Hierarchy' ),
			Model::get_table_name( 'Primary_Term' ),
		];
	}
}
migrations/20201202144329_AddEstimatedReadingTime.php000064400000001703152335317770015547 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * AddEstimatedReadingTime class.
 */
class AddEstimatedReadingTime extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		$table_name = $this->get_table_name();

		$this->add_column(
			$table_name,
			'estimated_reading_time_minutes',
			'integer',
			[
				'null'     => true,
				'default'  => null,
			]
		);
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
		$table_name = $this->get_table_name();

		$this->remove_column( $table_name, 'estimated_reading_time_minutes' );
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20200702141921_CreateIndexableSubpagesIndex.php000064400000002354152335317770016611 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * CreateIndexableSubpagesIndex class.
 */
class CreateIndexableSubpagesIndex extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		$this->change_column(
			$this->get_table_name(),
			'post_status',
			'string',
			[
				'null'  => true,
				'limit' => 20,
			]
		);
		$this->add_index(
			$this->get_table_name(),
			[ 'post_parent', 'object_type', 'post_status', 'object_id' ],
			[ 'name' => 'subpages' ]
		);
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
		$this->change_column(
			$this->get_table_name(),
			'post_status',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
		$this->remove_index(
			$this->get_table_name(),
			[ 'post_parent', 'object_type', 'post_status', 'object_id' ],
			[ 'name' => 'subpages' ]
		);
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20200609154515_AddHasAncestorsColumn.php000064400000001607152335317770015305 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;
use Yoast\WP\SEO\WordPress\Wrapper;

/**
 * Class AddHasAncestorsColumn.
 */
class AddHasAncestorsColumn extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$this->add_column(
			Model::get_table_name( 'Indexable' ),
			'has_ancestors',
			'boolean',
			[
				'default' => false,
			]
		);

		Wrapper::get_wpdb()->query(
			'
			UPDATE ' . Model::get_table_name( 'Indexable' ) . '
			SET has_ancestors = 1
			WHERE id IN ( SELECT indexable_id FROM ' . Model::get_table_name( 'Indexable_Hierarchy' ) . ' )
			'
		);
	}

	/**
	 * Migration down.
	 */
	public function down() {
		$this->remove_column( Model::get_table_name( 'Indexable' ), 'has_ancestors' );
	}
}
migrations/20171228151840_WpYoastIndexable.php000064400000014204152335317770014342 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Indexable migration.
 */
class WpYoastIndexable extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		$this->add_table();
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
		$this->drop_table( $this->get_table_name() );
	}

	/**
	 * Creates the indexable table.
	 */
	private function add_table() {
		$table_name = $this->get_table_name();

		$indexable_table = $this->create_table( $table_name );

		// Permalink.
		$indexable_table->column( 'permalink', 'mediumtext', [ 'null' => true ] );
		$indexable_table->column(
			'permalink_hash',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);

		// Object information.
		$indexable_table->column(
			'object_id',
			'integer',
			[
				'unsigned' => true,
				'null'     => true,
				'limit'    => 11,
			]
		);
		$indexable_table->column(
			'object_type',
			'string',
			[
				'null'  => false,
				'limit' => 32,
			]
		);
		$indexable_table->column(
			'object_sub_type',
			'string',
			[
				'null'  => true,
				'limit' => 32,
			]
		);

		// Ownership.
		$indexable_table->column(
			'author_id',
			'integer',
			[
				'unsigned' => true,
				'null'     => true,
				'limit'    => 11,
			]
		);
		$indexable_table->column(
			'post_parent',
			'integer',
			[
				'unsigned' => true,
				'null'     => true,
				'limit'    => 11,
			]
		);

		// Title and description.
		$indexable_table->column(
			'title',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
		$indexable_table->column( 'description', 'text', [ 'null' => true ] );
		$indexable_table->column(
			'breadcrumb_title',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);

		// Post metadata: status, public, protected.
		$indexable_table->column(
			'post_status',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
		$indexable_table->column(
			'is_public',
			'boolean',
			[
				'null'    => true,
				'default' => null,
			]
		);
		$indexable_table->column( 'is_protected', 'boolean', [ 'default' => false ] );
		$indexable_table->column(
			'has_public_posts',
			'boolean',
			[
				'null'    => true,
				'default' => null,
			]
		);

		$indexable_table->column(
			'number_of_pages',
			'integer',
			[
				'unsigned' => true,
				'null'     => true,
				'default'  => null,
				'limit'    => 11,
			]
		);

		$indexable_table->column( 'canonical', 'mediumtext', [ 'null' => true ] );

		// SEO and readability analysis.
		$indexable_table->column(
			'primary_focus_keyword',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
		$indexable_table->column(
			'primary_focus_keyword_score',
			'integer',
			[
				'null'  => true,
				'limit' => 3,
			]
		);
		$indexable_table->column(
			'readability_score',
			'integer',
			[
				'null'  => true,
				'limit' => 3,
			]
		);
		$indexable_table->column( 'is_cornerstone', 'boolean', [ 'default' => false ] );

		// Robots.
		$indexable_table->column(
			'is_robots_noindex',
			'boolean',
			[
				'null'    => true,
				'default' => false,
			]
		);
		$indexable_table->column(
			'is_robots_nofollow',
			'boolean',
			[
				'null'    => true,
				'default' => false,
			]
		);
		$indexable_table->column(
			'is_robots_noarchive',
			'boolean',
			[
				'null'    => true,
				'default' => false,
			]
		);
		$indexable_table->column(
			'is_robots_noimageindex',
			'boolean',
			[
				'null'    => true,
				'default' => false,
			]
		);
		$indexable_table->column(
			'is_robots_nosnippet',
			'boolean',
			[
				'null'    => true,
				'default' => false,
			]
		);

		// Twitter.
		$indexable_table->column(
			'twitter_title',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
		$indexable_table->column( 'twitter_image', 'mediumtext', [ 'null' => true ] );
		$indexable_table->column( 'twitter_description', 'mediumtext', [ 'null' => true ] );
		$indexable_table->column(
			'twitter_image_id',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
		$indexable_table->column(
			'twitter_image_source',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);

		// Open-Graph.
		$indexable_table->column(
			'open_graph_title',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
		$indexable_table->column( 'open_graph_description', 'mediumtext', [ 'null' => true ] );
		$indexable_table->column( 'open_graph_image', 'mediumtext', [ 'null' => true ] );
		$indexable_table->column(
			'open_graph_image_id',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
		$indexable_table->column(
			'open_graph_image_source',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
		$indexable_table->column( 'open_graph_image_meta', 'text', [ 'null' => true ] );

		// Link count.
		$indexable_table->column(
			'link_count',
			'integer',
			[
				'null'  => true,
				'limit' => 11,
			]
		);
		$indexable_table->column(
			'incoming_link_count',
			'integer',
			[
				'null'  => true,
				'limit' => 11,
			]
		);

		// Prominent words.
		$indexable_table->column(
			'prominent_words_version',
			'integer',
			[
				'null'     => true,
				'limit'    => 11,
				'unsigned' => true,
				'default'  => null,
			]
		);

		$indexable_table->finish();

		$this->add_indexes( $table_name );

		$this->add_timestamps( $table_name );
	}

	/**
	 * Adds indexes to the indexable table.
	 *
	 * @param string $indexable_table_name The name of the indexable table.
	 */
	private function add_indexes( $indexable_table_name ) {
		$this->add_index(
			$indexable_table_name,
			[
				'object_type',
				'object_sub_type',
			],
			[
				'name' => 'object_type_and_sub_type',
			]
		);

		$this->add_index(
			$indexable_table_name,
			'permalink_hash',
			[
				'name' => 'permalink_hash',
			]
		);
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20200430150130_ClearIndexableTables.php000064400000002005152335317770015064 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class ClearIndexableTables.
 */
class ClearIndexableTables extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$this->query( 'TRUNCATE TABLE ' . $this->get_indexable_table_name() );
		$this->query( 'TRUNCATE TABLE ' . $this->get_indexable_hierarchy_table_name() );
	}

	/**
	 * Migration down.
	 */
	public function down() {
		// Nothing to do.
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_indexable_table_name() {
		return Model::get_table_name( 'Indexable' );
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string The table name to use.
	 */
	protected function get_indexable_hierarchy_table_name() {
		return Model::get_table_name( 'Indexable_Hierarchy' );
	}
}
migrations/20200408101900_AddCollationToTables.php000064400000001624152335317770015076 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class AddCollationToTables.
 */
class AddCollationToTables extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		global $wpdb;

		$charset_collate = $wpdb->get_charset_collate();
		if ( empty( $charset_collate ) ) {
			return;
		}

		$tables = [
			Model::get_table_name( 'migrations' ),
			Model::get_table_name( 'Indexable' ),
			Model::get_table_name( 'Indexable_Hierarchy' ),
			Model::get_table_name( 'Primary_Term' ),
		];

		foreach ( $tables as $table ) {
			$this->query( 'ALTER TABLE ' . $table . ' CONVERT TO ' . \str_replace( 'DEFAULT ', '', $charset_collate ) );
		}
	}

	/**
	 * Migration down.
	 */
	public function down() {
		// No down required.
	}
}
migrations/20190529075038_WpYoastDropIndexableMetaTableIfExists.php000064400000001521152335317770020434 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class WpYoastDropIndexableMetaTableIfExists.
 */
class WpYoastDropIndexableMetaTableIfExists extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$table_name = $this->get_table_name();

		// This can be done safely as it executes a DROP IF EXISTS.
		$this->drop_table( $table_name );
	}

	/**
	 * Migration down.
	 */
	public function down() {
		// No down required. This specific table should never exist.
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable_Meta' );
	}
}
migrations/20200617122511_CreateSEOLinksTable.php000064400000004735152335317770014637 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * CreateSEOLinksTable class.
 */
class CreateSEOLinksTable extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		$table_name = $this->get_table_name();
		$adapter    = $this->get_adapter();

		// The table may already have been created by legacy code.
		// If not, create it exactly as it was.
		if ( ! $adapter->table_exists( $table_name ) ) {
			$table = $this->create_table( $table_name, [ 'id' => false ] );
			$table->column(
				'id',
				'biginteger',
				[
					'primary_key'    => true,
					'limit'          => 20,
					'unsigned'       => true,
					'auto_increment' => true,
				]
			);
			$table->column( 'url', 'string', [ 'limit' => 255 ] );
			$table->column(
				'post_id',
				'biginteger',
				[
					'limit'    => 20,
					'unsigned' => true,
				]
			);
			$table->column(
				'target_post_id',
				'biginteger',
				[
					'limit'    => 20,
					'unsigned' => true,
				]
			);
			$table->column( 'type', 'string', [ 'limit' => 8 ] );
			$table->finish();
		}
		if ( ! $adapter->has_index( $table_name, [ 'post_id', 'type' ], [ 'name' => 'link_direction' ] ) ) {
			$this->add_index( $table_name, [ 'post_id', 'type' ], [ 'name' => 'link_direction' ] );
		}

		// Add these columns outside of the initial table creation as these did not exist on the legacy table.
		$this->add_column( $table_name, 'indexable_id', 'integer', [ 'unsigned' => true ] );
		$this->add_column( $table_name, 'target_indexable_id', 'integer', [ 'unsigned' => true ] );
		$this->add_column( $table_name, 'height', 'integer', [ 'unsigned' => true ] );
		$this->add_column( $table_name, 'width', 'integer', [ 'unsigned' => true ] );
		$this->add_column( $table_name, 'size', 'integer', [ 'unsigned' => true ] );
		$this->add_column( $table_name, 'language', 'string', [ 'limit' => 32 ] );
		$this->add_column( $table_name, 'region', 'string', [ 'limit' => 32 ] );

		$this->add_index( $table_name, [ 'indexable_id', 'type' ], [ 'name' => 'indexable_link_direction' ] );
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
		$this->drop_table( $this->get_table_name() );
	}

	/**
	 * Returns the SEO Links table name.
	 *
	 * @return string
	 */
	private function get_table_name() {
		return Model::get_table_name( 'SEO_Links' );
	}
}
migrations/20171228151841_WpYoastPrimaryTerm.php000064400000002775152335317770014735 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Migration for the Primary Term.
 */
class WpYoastPrimaryTerm extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		$table_name = $this->get_table_name();

		$indexable_table = $this->create_table( $table_name );

		$indexable_table->column(
			'post_id',
			'integer',
			[
				'unsigned' => true,
				'null'     => false,
				'limit'    => 11,
			]
		);
		$indexable_table->column(
			'term_id',
			'integer',
			[
				'unsigned' => true,
				'null'     => false,
				'limit'    => 11,
			]
		);
		$indexable_table->column(
			'taxonomy',
			'string',
			[
				'null'  => false,
				'limit' => 32,
			]
		);

		// Executes the SQL to create the table.
		$indexable_table->finish();

		$this->add_index(
			$table_name,
			[
				'post_id',
				'taxonomy',
			],
			[
				'name' => 'post_taxonomy',
			]
		);

		$this->add_index(
			$table_name,
			[
				'post_id',
				'term_id',
			],
			[
				'name' => 'post_term',
			]
		);

		$this->add_timestamps( $table_name );
	}

	/**
	 * Migration down.
	 */
	public function down() {
		$this->drop_table( $this->get_table_name() );
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string Table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Primary_Term' );
	}
}
migrations/20200430075614_AddIndexableObjectIdAndTypeIndex.php000064400000001665152335317770017304 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class AddIndexableObjectIdAndTypeIndex.
 */
class AddIndexableObjectIdAndTypeIndex extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$this->add_index(
			$this->get_table_name(),
			[
				'object_id',
				'object_type',
			],
			[
				'name' => 'object_id_and_type',
			]
		);
	}

	/**
	 * Migration down.
	 */
	public function down() {
		$this->remove_index(
			$this->get_table_name(),
			[
				'object_id',
				'object_type',
			],
			[
				'name' => 'object_id_and_type',
			]
		);
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20201216141134_ExpandPrimaryTermIDColumnLengths.php000064400000002005152335317770017431 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * ExpandPrimaryTermIDColumnLengths class.
 */
class ExpandPrimaryTermIDColumnLengths extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * The columns to change the column type and length of.
	 *
	 * @var string[]
	 */
	protected static $columns_to_change = [
		'post_id',
		'term_id',
	];

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		foreach ( self::$columns_to_change as $column ) {
			$this->change_column(
				$this->get_table_name(),
				$column,
				'biginteger',
				[ 'limit' => 20 ]
			);
		}
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Primary_Term' );
	}
}
migrations/20210817092415_AddVersionColumnToIndexables.php000064400000001547152335317770016644 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * AddVersionColumnToIndexables class.
 */
class AddVersionColumnToIndexables extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		$this->add_column(
			$this->get_table_name(),
			'version',
			'integer',
			[
				'default'  => 1,
			]
		);
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
		$this->remove_column(
			$this->get_table_name(),
			'version'
		);
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20200428123747_BreadcrumbTitleAndHierarchyReset.php000064400000002304152335317770017453 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class BreadcrumbTitleAndHierarchyReset.
 */
class BreadcrumbTitleAndHierarchyReset extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$this->change_column( $this->get_indexable_table_name(), 'breadcrumb_title', 'text', [ 'null' => true ] );
		$this->query( 'DELETE FROM ' . $this->get_indexable_hierarchy_table_name() );
	}

	/**
	 * Migration down.
	 */
	public function down() {
		$this->change_column(
			$this->get_indexable_table_name(),
			'breadcrumb_title',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_indexable_table_name() {
		return Model::get_table_name( 'Indexable' );
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string The table name to use.
	 */
	protected function get_indexable_hierarchy_table_name() {
		return Model::get_table_name( 'Indexable_Hierarchy' );
	}
}
migrations/20200513133401_ResetIndexableHierarchyTable.php000064400000001314152335317770016602 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class ResetIndexableHierarchyTable.
 */
class ResetIndexableHierarchyTable extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$this->query( 'TRUNCATE TABLE ' . $this->get_table_name() );
	}

	/**
	 * Migration down.
	 */
	public function down() {
		// Nothing to do.
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable_Hierarchy' );
	}
}
migrations/20200429105310_TruncateIndexableTables.php000064400000002013152335317770015632 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class TruncateIndexableTables.
 */
class TruncateIndexableTables extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$this->query( 'TRUNCATE TABLE ' . $this->get_indexable_table_name() );
		$this->query( 'TRUNCATE TABLE ' . $this->get_indexable_hierarchy_table_name() );
	}

	/**
	 * Migration down.
	 */
	public function down() {
		// Nothing to do.
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_indexable_table_name() {
		return Model::get_table_name( 'Indexable' );
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string The table name to use.
	 */
	protected function get_indexable_hierarchy_table_name() {
		return Model::get_table_name( 'Indexable_Hierarchy' );
	}
}
migrations/20191011111109_WpYoastIndexableHierarchy.php000064400000003020152335317770016155 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class WpYoastIndexableHierarchy.
 */
class WpYoastIndexableHierarchy extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$table_name = $this->get_table_name();

		$indexable_table = $this->create_table( $table_name, [ 'id' => false ] );

		$indexable_table->column(
			'indexable_id',
			'integer',
			[
				'primary_key' => true,
				'unsigned'    => true,
				'null'        => true,
				'limit'       => 11,
			]
		);
		$indexable_table->column(
			'ancestor_id',
			'integer',
			[
				'primary_key' => true,
				'unsigned'    => true,
				'null'        => true,
				'limit'       => 11,
			]
		);
		$indexable_table->column(
			'depth',
			'integer',
			[
				'unsigned' => true,
				'null'     => true,
				'limit'    => 11,
			]
		);
		$indexable_table->finish();

		$this->add_index( $table_name, 'indexable_id', [ 'name' => 'indexable_id' ] );
		$this->add_index( $table_name, 'ancestor_id', [ 'name' => 'ancestor_id' ] );
		$this->add_index( $table_name, 'depth', [ 'name' => 'depth' ] );
	}

	/**
	 * Migration up.
	 */
	public function down() {
		$this->drop_table( $this->get_table_name() );
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable_Hierarchy' );
	}
}
migrations/20201216124002_ExpandIndexableIDColumnLengths.php000064400000002023152335317770017044 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * ExpandIndexableIDColumnLengths class.
 */
class ExpandIndexableIDColumnLengths extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * The columns to change the column type and length of.
	 *
	 * @var string[]
	 */
	protected static $columns_to_change = [
		'object_id',
		'author_id',
		'post_parent',
	];

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		foreach ( self::$columns_to_change as $column ) {
			$this->change_column(
				$this->get_table_name(),
				$column,
				'biginteger',
				[ 'limit' => 20 ]
			);
		}
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20200507054848_DeleteDuplicateIndexables.php000064400000002041152335317770016153 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * Class DeleteDuplicateIndexables.
 */
class DeleteDuplicateIndexables extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 */
	public function up() {
		$table_name = $this->get_table_name();

		/*
		 * Deletes duplicate indexables that have the same object_id and object_type.
		 * The rows with a higher ID are deleted as those should be unused and could be outdated.
		 */
		$this->query( 'DELETE wyi FROM ' . $table_name . ' wyi INNER JOIN ' . $table_name . ' wyi2 WHERE wyi2.object_id = wyi.object_id AND wyi2.object_type = wyi.object_type AND wyi2.id < wyi.id;' );
	}

	/**
	 * Migration down.
	 */
	public function down() {
		// Nothing to do.
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20200728095334_AddIndexesForProminentWordsOnIndexables.php000064400000002265152335317770021015 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * AddIndexesForProminentWordsOnIndexables class.
 */
class AddIndexesForProminentWordsOnIndexables extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * The columns on which an index should be added.
	 *
	 * @var string[]
	 */
	private $columns_with_index = [
		'prominent_words_version',
		'object_type',
		'object_sub_type',
		'post_status',
	];

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		$table_name = $this->get_table_name();
		$adapter    = $this->get_adapter();

		if ( ! $adapter->has_index( $table_name, $this->columns_with_index, [ 'name' => 'prominent_words' ] ) ) {
			$this->add_index(
				$table_name,
				$this->columns_with_index,
				[
					'name' => 'prominent_words',
				]
			);
		}
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
	}

	/**
	 * Retrieves the table name to use.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
migrations/20200616130143_ReplacePermalinkHashIndex.php000064400000004365152335317770016124 0ustar00<?php

namespace Yoast\WP\SEO\Config\Migrations;

use Yoast\WP\Lib\Migrations\Migration;
use Yoast\WP\Lib\Model;

/**
 * ReplacePermalinkHashIndex class.
 */
class ReplacePermalinkHashIndex extends Migration {

	/**
	 * The plugin this migration belongs to.
	 *
	 * @var string
	 */
	public static $plugin = 'free';

	/**
	 * Migration up.
	 *
	 * @return void
	 */
	public function up() {
		$table_name = $this->get_table_name();
		$adapter    = $this->get_adapter();

		if ( ! $adapter->has_table( $table_name ) ) {
			return;
		}

		$this->change_column(
			$table_name,
			'permalink_hash',
			'string',
			[
				'null'  => true,
				'limit' => 40,
			]
		);

		if ( $adapter->has_index( $table_name, [ 'permalink_hash' ], [ 'name' => 'permalink_hash' ] ) ) {
			$this->remove_index(
				$table_name,
				[
					'permalink_hash',
				],
				[
					'name' => 'permalink_hash',
				]
			);
		}

		if ( ! $adapter->has_index( $table_name, [ 'permalink_hash', 'object_type' ], [ 'name' => 'permalink_hash_and_object_type' ] ) ) {
			$this->add_index(
				$table_name,
				[
					'permalink_hash',
					'object_type',
				],
				[
					'name' => 'permalink_hash_and_object_type',
				]
			);
		}
	}

	/**
	 * Migration down.
	 *
	 * @return void
	 */
	public function down() {
		$table_name = $this->get_table_name();
		$adapter    = $this->get_adapter();

		if ( ! $adapter->has_table( $table_name ) ) {
			return;
		}

		if ( $adapter->has_index( $table_name, [ 'permalink_hash', 'object_type' ], [ 'name' => 'permalink_hash_and_object_type' ] ) ) {
			$this->remove_index(
				$table_name,
				[
					'permalink_hash',
					'object_type',
				],
				[
					'name' => 'permalink_hash_and_object_type',
				]
			);
		}

		$this->change_column(
			$table_name,
			'permalink_hash',
			'string',
			[
				'null'  => true,
				'limit' => 191,
			]
		);

		if ( ! $adapter->has_index( $table_name, [ 'permalink_hash' ], [ 'name' => 'permalink_hash' ] ) ) {
			$this->add_index(
				$table_name,
				[
					'permalink_hash',
				],
				[
					'name' => 'permalink_hash',
				]
			);
		}
	}

	/**
	 * Retrieves the table name to use for storing indexables.
	 *
	 * @return string The table name to use.
	 */
	protected function get_table_name() {
		return Model::get_table_name( 'Indexable' );
	}
}
schema-ids.php000064400000002142152335317770007306 0ustar00<?php

namespace Yoast\WP\SEO\Config;

/**
 * Class Schema_IDs.
 */
class Schema_IDs {

	/**
	 * Hash used for the Author `@id`.
	 */
	const AUTHOR_HASH = '#author';

	/**
	 * Hash used for the Author Logo's `@id`.
	 */
	const AUTHOR_LOGO_HASH = '#authorlogo';

	/**
	 * Hash used for the Breadcrumb's `@id`.
	 */
	const BREADCRUMB_HASH = '#breadcrumb';

	/**
	 * Hash used for the Person `@id`.
	 */
	const PERSON_HASH = '#/schema/person/';

	/**
	 * Hash used for the Article `@id`.
	 */
	const ARTICLE_HASH = '#article';

	/**
	 * Hash used for the Organization `@id`.
	 */
	const ORGANIZATION_HASH = '#organization';

	/**
	 * Hash used for the Organization `@id`.
	 */
	const ORGANIZATION_LOGO_HASH = '#/schema/logo/image/';

	/**
	 * Hash used for the logo `@id`.
	 */
	const PERSON_LOGO_HASH = '#/schema/person/image/';

	/**
	 * Hash used for an Article's primary image `@id`.
	 */
	const PRIMARY_IMAGE_HASH = '#primaryimage';

	/**
	 * Hash used for the WebPage's `@id`.
	 *
	 * @deprecated 19.3
	 */
	const WEBPAGE_HASH = '';

	/**
	 * Hash used for the Website's `@id`.
	 */
	const WEBSITE_HASH = '#website';
}
oauth-client.php000064400000021656152335317770007700 0ustar00<?php

namespace Yoast\WP\SEO\Config;

use Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Token_Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Failed_Storage_Exception;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Values\OAuth\OAuth_Token;
use YoastSEO_Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use YoastSEO_Vendor\League\OAuth2\Client\Provider\GenericProvider;

/**
 * Class OAuth_Client
 */
abstract class OAuth_Client {

	/**
	 * The option's key.
	 *
	 * @var string
	 */
	protected $token_option = null;

	/**
	 * The provider.
	 *
	 * @var Wincher_PKCE_Provider|GenericProvider
	 */
	protected $provider;

	/**
	 * The options helper.
	 *
	 * @var Options_Helper
	 */
	protected $options_helper;

	/**
	 * The token.
	 *
	 * @var OAuth_Token|null
	 */
	protected $token = null;

	/**
	 * OAuth_Client constructor.
	 *
	 * @param string                                $token_option   The option's name to save the token as.
	 * @param Wincher_PKCE_Provider|GenericProvider $provider       The provider.
	 * @param Options_Helper                        $options_helper The Options_Helper instance.
	 *
	 * @throws Empty_Property_Exception Exception thrown if a token property is empty.
	 */
	public function __construct(
		$token_option,
		$provider,
		Options_Helper $options_helper
	) {
		$this->provider       = $provider;
		$this->token_option   = $token_option;
		$this->options_helper = $options_helper;

		$tokens = $this->options_helper->get( $this->token_option );

		if ( ! empty( $tokens ) ) {
			$this->token = new OAuth_Token(
				$tokens['access_token'],
				$tokens['refresh_token'],
				$tokens['expires'],
				$tokens['has_expired'],
				$tokens['created_at'],
				isset( $tokens['error_count'] ) ? $tokens['error_count'] : 0
			);
		}
	}

	/**
	 * Requests the access token and refresh token based on the passed code.
	 *
	 * @param string $code The code to send.
	 *
	 * @return OAuth_Token The requested tokens.
	 *
	 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
	 */
	public function request_tokens( $code ) {
		try {
			$response = $this->provider
				->getAccessToken(
					'authorization_code',
					[
						'code' => $code,
					]
				);

			$token = OAuth_Token::from_response( $response );

			return $this->store_token( $token );
		} catch ( Exception $exception ) {
			throw new Authentication_Failed_Exception( $exception );
		}
	}

	/**
	 * Performs an authenticated GET request to the desired URL.
	 *
	 * @param string $url     The URL to send the request to.
	 * @param array  $options The options to pass along to the request.
	 *
	 * @return mixed The parsed API response.
	 *
	 * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data.
	 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
	 * @throws Empty_Token_Exception Exception thrown if the token is empty.
	 */
	public function get( $url, $options = [] ) {
		return $this->do_request( 'GET', $url, $options );
	}

	/**
	 * Performs an authenticated POST request to the desired URL.
	 *
	 * @param string $url     The URL to send the request to.
	 * @param mixed  $body    The data to send along in the request's body.
	 * @param array  $options The options to pass along to the request.
	 *
	 * @return mixed The parsed API response.
	 *
	 * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data.
	 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
	 * @throws Empty_Token_Exception Exception thrown if the token is empty.
	 */
	public function post( $url, $body, $options = [] ) {
		$options['body'] = $body;

		return $this->do_request( 'POST', $url, $options );
	}

	/**
	 * Performs an authenticated DELETE request to the desired URL.
	 *
	 * @param string $url     The URL to send the request to.
	 * @param array  $options The options to pass along to the request.
	 *
	 * @return mixed The parsed API response.
	 *
	 * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data.
	 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
	 * @throws Empty_Token_Exception Exception thrown if the token is empty.
	 */
	public function delete( $url, $options = [] ) {
		return $this->do_request( 'DELETE', $url, $options );
	}

	/**
	 * Determines whether there are valid tokens available.
	 *
	 * @return bool Whether there are valid tokens.
	 */
	public function has_valid_tokens() {
		return ! empty( $this->token ) && $this->token->has_expired() === false;
	}

	/**
	 * Gets the stored tokens and refreshes them if they've expired.
	 *
	 * @return OAuth_Token The stored tokens.
	 *
	 * @throws Empty_Token_Exception Exception thrown if the token is empty.
	 */
	public function get_tokens() {
		if ( empty( $this->token ) ) {
			throw new Empty_Token_Exception();
		}

		if ( $this->token->has_expired() ) {
			$this->token = $this->refresh_tokens( $this->token );
		}

		return $this->token;
	}

	/**
	 * Stores the passed token.
	 *
	 * @param OAuth_Token $token The token to store.
	 *
	 * @return OAuth_Token The stored token.
	 *
	 * @throws Failed_Storage_Exception Exception thrown if storing of the token fails.
	 */
	public function store_token( OAuth_Token $token ) {
		$saved = $this->options_helper->set( $this->token_option, $token->to_array() );

		if ( $saved === false ) {
			throw new Failed_Storage_Exception();
		}

		return $token;
	}

	/**
	 * Clears the stored token from storage.
	 *
	 * @return boolean The stored token.
	 *
	 * @throws Failed_Storage_Exception Exception thrown if clearing of the token fails.
	 */
	public function clear_token() {
		$saved = $this->options_helper->set( $this->token_option, [] );

		if ( $saved === false ) {
			throw new Failed_Storage_Exception();
		}

		return true;
	}

	/**
	 * Performs the specified request.
	 *
	 * @param string $method  The HTTP method to use.
	 * @param string $url     The URL to send the request to.
	 * @param array  $options The options to pass along to the request.
	 *
	 * @return mixed The parsed API response.
	 *
	 * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data.
	 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
	 * @throws Empty_Token_Exception Exception thrown if the token is empty.
	 */
	protected function do_request( $method, $url, array $options ) {
		$defaults = [
			'headers' => $this->provider->getHeaders( $this->get_tokens()->access_token ),
		];

		$options = \array_merge_recursive( $defaults, $options );

		if ( \array_key_exists( 'params', $options ) ) {
			$url .= '?' . \http_build_query( $options['params'] );
			unset( $options['params'] );
		}

		$request = $this->provider
			->getAuthenticatedRequest( $method, $url, null, $options );

		return $this->provider->getParsedResponse( $request );
	}

	/**
	 * Refreshes the outdated tokens.
	 *
	 * @param OAuth_Token $tokens The outdated tokens.
	 *
	 * @return OAuth_Token The refreshed tokens.
	 *
	 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
	 */
	protected function refresh_tokens( OAuth_Token $tokens ) {
		// We do this dance with transients since we need to make sure we don't
		// delete valid tokens because of a race condition when two calls are
		// made simultaneously to this function and refresh token rotation is
		// turned on in the OAuth server. This is not 100% safe, but should at
		// least be much better than not having any lock at all.
		$lock_name = \sprintf( 'lock:%s', $this->token_option );
		$can_lock  = \get_transient( $lock_name ) === false;
		$has_lock  = $can_lock && \set_transient( $lock_name, true, 30 );

		try {
			$new_tokens = $this->provider->getAccessToken(
				'refresh_token',
				[
					'refresh_token' => $tokens->refresh_token,
				]
			);

			$token_obj = OAuth_Token::from_response( $new_tokens );

			return $this->store_token( $token_obj );
		} catch ( Exception $exception ) {
			// If we tried to refresh but the refresh token is invalid, delete
			// the tokens so that we don't try again. Only do this if we got the
			// lock at the beginning of the call.
			if ( $has_lock && $exception->getMessage() === 'invalid_grant' ) {
				try {
					// To protect from race conditions, only do this if we've
					// seen an error before with the same token.
					if ( $tokens->error_count >= 1 ) {
						$this->clear_token();
					}
					else {
						$tokens->error_count += 1;
						$this->store_token( $tokens );
					}
				} catch ( Exception $e ) {  // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
					// Pass through.
				}
			}

			throw new Authentication_Failed_Exception( $exception );
		} finally {
			delete_transient( $lock_name );
		}
	}
}
wincher-client.php000064400000007513152335317770010213 0ustar00<?php

namespace Yoast\WP\SEO\Config;

use WPSEO_Utils;
use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception;
use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Token_Exception;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Values\OAuth\OAuth_Token;
use Yoast\WP\SEO\Wrappers\WP_Remote_Handler;
use YoastSEO_Vendor\GuzzleHttp\Client;
use YoastSEO_Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException;

/**
 * Class Wincher_Client
 */
class Wincher_Client extends OAuth_Client {

	/**
	 * The option's key.
	 */
	const TOKEN_OPTION = 'wincher_tokens';

	/**
	 * Name of the temporary PKCE cookie.
	 */
	const PKCE_TRANSIENT_NAME = 'yoast_wincher_pkce';

	/**
	 * The WP_Remote_Handler instance.
	 *
	 * @var WP_Remote_Handler
	 */
	protected $wp_remote_handler;

	/**
	 * Wincher_Client constructor.
	 *
	 * @param Options_Helper    $options_helper    The Options_Helper instance.
	 * @param WP_Remote_Handler $wp_remote_handler The request handler.
	 *
	 * @throws Empty_Property_Exception Exception thrown if a token property is empty.
	 */
	public function __construct(
		Options_Helper $options_helper,
		WP_Remote_Handler $wp_remote_handler
	) {

		$provider = new Wincher_PKCE_Provider(
			[
				'clientId'                => 'yoast',
				'redirectUri'             => 'https://auth.wincher.com/yoast/setup',
				'urlAuthorize'            => 'https://auth.wincher.com/connect/authorize',
				'urlAccessToken'          => 'https://auth.wincher.com/connect/token',
				'urlResourceOwnerDetails' => 'https://api.wincher.com/beta/user',
				'scopes'                  => [ 'profile', 'account', 'websites:read', 'websites:write', 'offline_access' ],
				'scopeSeparator'          => ' ',
				'pkceMethod'              => 'S256',
			],
			[
				'httpClient' => new Client( [ 'handler' => $wp_remote_handler ] ),
			]
		);

		parent::__construct(
			self::TOKEN_OPTION,
			$provider,
			$options_helper
		);
	}

	/**
	 * Return the authorization URL.
	 *
	 * @return string The authentication URL.
	 */
	public function get_authorization_url() {
		$parsed_site_url = \wp_parse_url( \get_site_url() );

		$url = $this->provider->getAuthorizationUrl(
			[
				'state' => WPSEO_Utils::format_json_encode( [ 'domain' => $parsed_site_url['host'] ] ),
			]
		);

		$pkce_code = $this->provider->getPkceCode();

		// Store a transient value with the PKCE code that we need in order to
		// exchange the returned code for a token after authorization.
		\set_transient( self::PKCE_TRANSIENT_NAME, $pkce_code, \DAY_IN_SECONDS );

		return $url;
	}

	/**
	 * Requests the access token and refresh token based on the passed code.
	 *
	 * @param string $code The code to send.
	 *
	 * @return OAuth_Token The requested tokens.
	 *
	 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
	 */
	public function request_tokens( $code ) {
		$pkce_code = \get_transient( self::PKCE_TRANSIENT_NAME );
		if ( $pkce_code ) {
			$this->provider->setPkceCode( $pkce_code );
		}
		return parent::request_tokens( $code );
	}

	/**
	 * Performs the specified request.
	 *
	 * @codeCoverageIgnore
	 *
	 * @param string $method  The HTTP method to use.
	 * @param string $url     The URL to send the request to.
	 * @param array  $options The options to pass along to the request.
	 *
	 * @return mixed The parsed API response.
	 *
	 * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data.
	 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
	 * @throws Empty_Token_Exception Exception thrown if the token is empty.
	 */
	protected function do_request( $method, $url, array $options ) {
		$options['headers'] = [ 'Content-Type' => 'application/json' ];
		return parent::do_request( $method, $url, $options );
	}
}
badge-group-names.php000064400000002753152335317770010576 0ustar00<?php

namespace Yoast\WP\SEO\Config;

/**
 * Class Badge_Group_Names.
 *
 * This class defines groups for "new" badges, with the version in which those groups are no longer considered
 * to be "new".
 */
class Badge_Group_Names {

	const GROUP_GLOBAL_TEMPLATES = 'global-templates';

	/**
	 * Constant describing when certain groups of new badges will no longer be shown.
	 */
	const GROUP_NAMES = [
		self::GROUP_GLOBAL_TEMPLATES => '16.7-beta0',
	];

	/**
	 * The current plugin version.
	 *
	 * @var string
	 */
	protected $version;

	/**
	 * Badge_Group_Names constructor.
	 *
	 * @param string|null $version Optional: the current plugin version.
	 */
	public function __construct( $version = null ) {
		if ( ! $version ) {
			$version = \WPSEO_VERSION;
		}
		$this->version = $version;
	}

	/**
	 * Check whether a group of badges is still eligible for a "new" badge.
	 *
	 * @param string      $group           One of the GROUP_* constants.
	 * @param string|null $current_version The current version of the plugin that's being checked.
	 *
	 * @return bool Whether a group of badges is still eligible for a "new" badge.
	 */
	public function is_still_eligible_for_new_badge( $group, $current_version = null ) {
		if ( ! \array_key_exists( $group, $this::GROUP_NAMES ) ) {
			return false;
		}

		$group_version = $this::GROUP_NAMES[ $group ];

		if ( \is_null( $current_version ) ) {
			$current_version = $this->version;
		}

		return (bool) \version_compare( $group_version, $current_version, '>' );
	}
}
wordproof-translations.php000064400000005003152335317770012030 0ustar00<?php

namespace Yoast\WP\SEO\Config;

use YoastSEO_Vendor\WordProof\SDK\Translations\TranslationsInterface;

/**
 * Class WordProof_Translations
 *
 * @package Yoast\WP\SEO\Config
 */
class Wordproof_Translations implements TranslationsInterface {

	/**
	 * Returns no balance notice translation.
	 *
	 * @return string The translation.
	 */
	public function getNoBalanceNotice() {
		/* translators: %s expands to WordProof. */
		return \sprintf( \__( 'You are out of timestamps. Please upgrade your account by opening the %s settings.', 'wordpress-seo' ), 'WordProof' );
	}

	/**
	 * Returns no balance notice translation.
	 *
	 * @return string The translation.
	 */
	public function getTimestampSuccessNotice() {
		/* translators: %s expands to WordProof. */
		return \sprintf( \__( '%s has successfully timestamped this page.', 'wordpress-seo' ), 'WordProof' );
	}

	/**
	 * Returns timestamp failed notice translation.
	 *
	 * @return string The translation.
	 */
	public function getTimestampFailedNotice() {
		/* translators: %s expands to WordProof. */
		return \sprintf( \__( '%1$s failed to timestamp this page. Please check if you\'re correctly authenticated with %1$s and try to save this page again.', 'wordpress-seo' ), 'WordProof' );
	}

	/**
	 * Returns webhook failed notice translation.
	 *
	 * @return string The translation.
	 */
	public function getWebhookFailedNotice() {
		/* translators: %s expands to WordProof. */
		return \sprintf( \__( 'The timestamp is not retrieved by your site. Please try again or contact %1$s support.', 'wordpress-seo' ), 'WordProof' );
	}

	/**
	 * Returns no authentication notice translation.
	 *
	 * @return string The translation.
	 */
	public function getNotAuthenticatedNotice() {
		/* translators: %s expands to WordProof. */
		return \sprintf( \__( 'The timestamp is not created because you need to authenticate with %s first.', 'wordpress-seo' ), 'WordProof' );
	}

	/**
	 * Returns authenticate button text.
	 *
	 * @return string The translation.
	 */
	public function getOpenAuthenticationButtonText() {
		return \__( 'Open authentication', 'wordpress-seo' );
	}

	/**
	 * Returns open settings button translation.
	 *
	 * @return string The translation.
	 */
	public function getOpenSettingsButtonText() {
		return \__( 'Open settings', 'wordpress-seo' );
	}

	/**
	 * Returns get contact WordProof Support button translation.
	 *
	 * @return string The translation.
	 */
	public function getContactWordProofSupportButtonText() {
		return \__( 'Contact WordProof support', 'wordpress-seo' );
	}
}
wordproof-app-config.php000064400000001020152335317770011325 0ustar00<?php

namespace Yoast\WP\SEO\Config;

use YoastSEO_Vendor\WordProof\SDK\Config\DefaultAppConfig;

/**
 * Class WordProof_App_Config.
 *
 * @package Yoast\WP\SEO\Config
 */
class Wordproof_App_Config extends DefaultAppConfig {

	/**
	 * Returns the partner.
	 *
	 * @return string The partner.
	 */
	public function getPartner() {
		return 'yoast';
	}

	/**
	 * Returns if the WordProof Uikit should be loaded from a cdn.
	 *
	 * @return boolean True or false.
	 */
	public function getLoadUikitFromCdn() {
		return false;
	}
}
BaseController.php000064400000002547152336322210010202 0ustar00<?php
/**
 * Base controller for global plugin configuration screens 
 */
abstract class Loco_admin_config_BaseController extends Loco_mvc_AdminController {


    /**
     * {@inheritdoc}
     */
    public function init(){
        parent::init();
        // navigate between config view siblings, but only if privileged user
        if( current_user_can('manage_options') ){
            $tabs = new Loco_admin_Navigation;
            $this->set( 'tabs', $tabs );
            $actions =  [
                ''  => __('Site options','loco-translate'),
                'user' => __('User options','loco-translate'),
                'apis' => __('API keys','loco-translate'),
                'version' => __('Version','loco-translate'),
                'debug' => __('System','loco-translate'),
            ];
            $suffix = (string) $this->get('action');
            foreach( $actions as $action => $name ){
                $href = Loco_mvc_AdminRouter::generate( 'config-'.$action, $_GET );
                $tabs->add( $name, $href, $action === $suffix );
            }
        }
    }
    


    /**
     * {@inheritdoc}
     */
    public function getHelpTabs(){
        return  [
            __('Overview','loco-translate') => $this->viewSnippet('tab-config'),
            __('API keys','loco-translate') => $this->viewSnippet('tab-config-apis'),
        ];
    }
    
    
}VersionController.php000064400000004712152336322210010751 0ustar00<?php
/**
 *  Plugin version / upgrade screen
 */
class Loco_admin_config_VersionController extends Loco_admin_config_BaseController {


    /**
     * {@inheritdoc}
     */
    public function init(){
        parent::init();
        $this->set( 'title', __('Version','loco-translate') );
    }


    /**
     * {@inheritdoc}
     */
    public function render(){
        
        $title = __('Plugin settings','loco-translate');
        $breadcrumb = new Loco_admin_Navigation;
        $breadcrumb->add( $title );
        
        // current plugin version
        $version = loco_plugin_version();
        if( $updates = get_site_transient('update_plugins') ){
            $key = loco_plugin_self();
            if( isset($updates->response[$key]) ){
                $latest = $updates->response[$key]->new_version;
                // if current version is lower than latest, prompt update
                if( version_compare($version,$latest,'<') ){
                    $this->setLocoUpdate($latest);
                }
            }
        }
        // notify if running a development snapshot, but only if ahead of latest stable
        if( '-dev' === substr($version,-4) ){
            $this->set( 'devel', true );
        }
        
        
        // check PHP version, noting that we want to move to minimum version 5.6 as per latest WordPress
        $phpversion = PHP_VERSION;
        if( version_compare($phpversion,'7.4.0','<') ){
            $this->set('phpupdate','7.4');
        }
        
        
        // check WordPress version, No plans to increase this until WP bumps their min PHP requirement.
        $wpversion = $GLOBALS['wp_version'];
        /*if( version_compare($wpversion,'5.2','<') ){
            $this->setWpUpdate('5.2');
        }*/
        
        return $this->view('admin/config/version', compact('breadcrumb','version','phpversion','wpversion') ); 
    }


    /**
     * @param string version
     */
    private function setLocoUpdate( $version ){
        $action = 'upgrade-plugin_'.loco_plugin_self();
        $link = admin_url( 'update.php?action=upgrade-plugin&plugin='.rawurlencode(loco_plugin_self()) );
        $this->set('update', $version );
        $this->set('update_href', wp_nonce_url( $link, $action ) );
    }


    /**
     * @param string minimum recommended version
     *
    private function setWpUpdate( $version ){
        $this->set('wpupdate',$version);
        $this->set('wpupdate_href', admin_url('update-core.php') );
    }*/

    
}DebugController.php000064400000017430152336322210010353 0ustar00<?php
/**
 *  Plugin config check (system diagnostics)
 */
class Loco_admin_config_DebugController extends Loco_admin_config_BaseController {

    /**
     * {@inheritdoc}
     */
    public function init(){
        parent::init();
        $this->set( 'title', __('Debug','loco-translate') );
    }


    /**
     * @param string $raw
     * @return int 
     */
    private function memory_size( $raw ){
        $bytes = wp_convert_hr_to_bytes($raw);
        return Loco_mvc_FileParams::renderBytes($bytes);
    }


    /**
     * Get path relative to WordPress ABSPATH
     * @param string $path
     * @return string
     */
    private function rel_path( $path ){
        if( is_string($path) && $path && '/' === $path[0] ){
            $file = new Loco_fs_File( $path );
            $path = $file->getRelativePath(ABSPATH);
        }
        else if( ! $path ){
            $path = '(none)';
        }
        return $path;
    }
    
    
    private function file_params( Loco_fs_File $file ){
        $ctx = new Loco_fs_FileWriter($file);
        return new Loco_mvc_ViewParams(['path'=>$this->rel_path($file->getPath()), 'writable'=>$ctx->writable()]);
    }
    


    /**
     * {@inheritdoc}
     */
    public function render(){
        
        $title = __('System diagnostics','loco-translate');
        $breadcrumb = new Loco_admin_Navigation;
        $breadcrumb->add( $title );

        // extensions that are normally enabled in PHP by default
        loco_check_extension('json');
        loco_check_extension('ctype');

        // product versions:
        $versions = new Loco_mvc_ViewParams(  [
            'Loco Translate' => loco_plugin_version(),
            'WordPress' => $GLOBALS['wp_version'],
            'PHP' => phpversion().' ('.PHP_SAPI.')',
            'Server' => isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : ( function_exists('apache_get_version') ? apache_get_version() : '' ),
            'jQuery' => '...',
        ] );
        // we want to know about modules in case there are security mods installed known to break functionality
        if( function_exists('apache_get_modules') && ( $mods = preg_grep('/^mod_/',apache_get_modules() ) ) ){
            $versions['Server'] .= ' + '.implode(', ',$mods);
        }
        // Add Xdebug version if installed
        if( extension_loaded('xdebug') ){
            $versions['PHP'] .= ' + Xdebug '. phpversion('xdebug');
        }

        // byte code cache (currently only checking for Zend OPcache)
        if( function_exists('opcache_get_configuration') && ini_get('opcache.enable') ){
            $info = opcache_get_configuration();
            $vers = $info['version'];
            $versions[ $vers['opcache_product_name'] ] = ' '.$vers['version'];
        }
        
        // utf8 / encoding:
        $cs = get_option('blog_charset');
        $encoding = new Loco_mvc_ViewParams(  [
            'OK' => "\xCE\x9F\xCE\x9A",
            'tick' => "\xE2\x9C\x93",
            'json' => json_decode('"\\u039f\\u039a \\u2713"'),
            'charset' => $cs.' '.( preg_match('/^utf-?8$/i',$cs) ? "\xE2\x9C\x93" : '(not recommended)' ),
            'mbstring' => loco_check_extension('mbstring') ? "\xCE\x9F\xCE\x9A \xE2\x9C\x93" : 'No',
        ] );
        // Sanity check mbstring.func_overload
        if( 2 !== strlen("\xC2\xA3") ){
            $encoding->mbstring = 'Error, disable mbstring.func_overload';
        }

        // PHP / env memory settings:
        $memory = new Loco_mvc_PostParams( [
            'WP_MEMORY_LIMIT' => $this->memory_size( loco_constant('WP_MEMORY_LIMIT') ),
            'WP_MAX_MEMORY_LIMIT' => $this->memory_size( loco_constant('WP_MAX_MEMORY_LIMIT') ),
            'PHP memory_limit' => $this->memory_size( ini_get('memory_limit') ),
            'PHP post_max_size' => $this->memory_size( ini_get('post_max_size') ),
            //'PHP upload_max_filesize' => $this->memory_size( ini_get('upload_max_filesize') ),
            'PHP max_execution_time' => (string) ini_get('max_execution_time'),
        ] );

        // Check if raising memory limit works (wp>=4.6)
        if( function_exists('wp_is_ini_value_changeable') && wp_is_ini_value_changeable('memory_limit') ){
            $memory['PHP memory_limit'] .= ' (changeable)';
        }
        
        // Ajaxing:
        $this->enqueueScript('system');
        $this->set( 'js', new Loco_mvc_ViewParams(  [
            'nonces' => [ 'ping' => wp_create_nonce('ping'), 'apis' => wp_create_nonce('apis') ],
        ] ) );
        
        // Third party API integrations:
        $apis = [];
        $jsapis = [];
        foreach( Loco_api_Providers::sort( Loco_api_Providers::export() ) as $api ){
            $apis[] = new Loco_mvc_ViewParams($api);
            $jsapis[] = $api;
        }
        if( $apis ){
            $this->set('apis',$apis);
            $jsconf = $this->get('js');
            $jsconf['apis'] = $jsapis;
        }
        
        // File system access
        $ctx = new Loco_fs_FileWriter( new Loco_fs_Directory(WP_LANG_DIR) );
        $fsp = Loco_data_Settings::get()->fs_protect;
        $fs = new Loco_mvc_PostParams( [
            'disabled' => $ctx->disabled(),
            'fs_protect' => 1 === $fsp ? 'Warn' : ( $fsp ? 'Block' : 'Off' ),
        ] );
        // important locations, starting with LOCO_LANG_DIR
        $locations = [
            'WP_LANG_DIR' => $this->file_params( new Loco_fs_Directory( loco_constant('WP_LANG_DIR') ) ),
            'LOCO_LANG_DIR' => $this->file_params( new Loco_fs_Directory( loco_constant('LOCO_LANG_DIR') ) ),
        ];
        // WP_TEMP_DIR takes precedence over sys_get_temp_dir in WordPress get_temp_dir();
        if( defined('WP_TEMP_DIR') ){
            $locations['WP_TEMP_DIR'] = $this->file_params( new Loco_fs_Directory(WP_TEMP_DIR) );
        }
        $locations['PHP sys_temp_dir'] = $this->file_params( new Loco_fs_Directory( sys_get_temp_dir() ) );
        $locations['PHP upload_tmp_dir'] = $this->file_params( new Loco_fs_Directory( ini_get('upload_tmp_dir') ) );
        $locations['PHP error_log'] = $this->file_params( new Loco_fs_Directory( ini_get('error_log') ) );

        // Debug and error log settings
        $debug = new Loco_mvc_ViewParams( [
            'WP_DEBUG' => loco_constant('WP_DEBUG') ? 'On' : 'Off',
            'WP_DEBUG_LOG' => loco_constant('WP_DEBUG_LOG') ? 'On' : 'Off',
            'WP_DEBUG_DISPLAY' => loco_constant('WP_DEBUG_DISPLAY') ? 'On' : 'Off',
            'PHP display_errors' => ini_get('display_errors')  ? 'On' : 'Off',
            'PHP log_errors' => ini_get('log_errors')  ? 'On' : 'Off',
        ] );

        /* Output buffering settings
	    $this->set('ob', new Loco_mvc_ViewParams( array(
		    'output_handler' => ini_get('output_handler'),
		    'zlib.output_compression' => ini_get('zlib.output_compression'),
			'zlib.output_compression_level' => ini_get('zlib.output_compression_level'),
			'zlib.output_handler' => ini_get('zlib.output_handler'),
	    ) ) );*/
        
        // alert to known system setting problems:
        if( version_compare(PHP_VERSION,'7.4','<') ){
            // phpcs:disable -- PHP version is checked prior to deprecated function call.
            if( get_magic_quotes_gpc() ){
                Loco_error_AdminNotices::info('You have "magic_quotes_gpc" enabled. We recommend you disable this in PHP');
            }
            if( get_magic_quotes_runtime() ){
                Loco_error_AdminNotices::info('You have "magic_quotes_runtime" enabled. We recommend you disable this in PHP');
            }
            if( version_compare(PHP_VERSION,'5.6.20','<') ){
                Loco_error_AdminNotices::info('Your PHP version is very old. We recommend you upgrade');
            }
            // phpcs:enable
        }
        
        return $this->view('admin/config/debug', compact('breadcrumb','versions','encoding','memory','fs','locations','debug') );
    }

}
PrefsController.php000064400000002404152336322210010377 0ustar00<?php
/**
 *  User-level plugin preferences
 */
class Loco_admin_config_PrefsController extends Loco_admin_config_BaseController {


    /**
     * {@inheritdoc}
     */
    public function init(){
        parent::init();
        $this->set( 'title', __('User options','loco-translate') );
        
        // user preference options
        $opts = Loco_data_Preferences::get();
        $this->set( 'opts', $opts );
        
        // handle save action 
        $nonce = $this->setNonce('save-prefs');
        try {
            if( $this->checkNonce($nonce->action) ){
                $post = Loco_mvc_PostParams::get();
                if( $post->has('opts') ){
                    $opts->populate( $post->opts )->persist();
                    Loco_error_AdminNotices::success( __('Settings saved','loco-translate') );
                }
            }
        }
        catch( Loco_error_Exception $e ){
            Loco_error_AdminNotices::add($e);
        }
    }



    /**
     * {@inheritdoc}
     */
    public function render(){
        
        $title = __('Plugin settings','loco-translate');
        $breadcrumb = new Loco_admin_Navigation;
        $breadcrumb->add( $title );
        
        return $this->view('admin/config/prefs', compact('breadcrumb') ); 
    }

}
ApisController.php000064400000004544152336322210010223 0ustar00<?php
/**
 *  API keys/settings screen
 */
class Loco_admin_config_ApisController extends Loco_admin_config_BaseController {

    /**
     * {@inheritdoc}
     */
    public function init(){
        parent::init();
        $this->set( 'title', __('API keys','loco-translate') );

        // collect support API keys
        $apis = [];
        foreach( Loco_api_Providers::builtin() as $api ){
            $apis[ $api['id'] ] = new Loco_mvc_ViewParams($api);
        }
        $this->set('apis',$apis);

        // handle save action
        $nonce = $this->setNonce('save-apis');
        try {
            if( $this->checkNonce($nonce->action) ){
                $post = Loco_mvc_PostParams::get();
                if( $post->has('api') ){
                    // Save only options in post. Avoids overwrite of missing site options
                    $data = [];
                    $filter = [];
                    foreach( $apis as $id => $api ){
                        $fields = $post->api[$id];
                        if( is_array($fields) ){
                            foreach( $fields as $prop => $value ){
                                $apis[$id][$prop] = $value;
                                $prop = $id.'_api_'.$prop;
                                $data[$prop] = $value;
                                $filter[] = $prop;
                            }
                        }
                    }
                    if( $filter ){
                        Loco_data_Settings::get()->populate($data,$filter)->persistIfDirty();
                        Loco_error_AdminNotices::success( __('Settings saved','loco-translate') );
                    }
                }
            }
        }
        catch( Loco_error_Exception $e ){
            Loco_error_AdminNotices::add($e);
        }
    }


    /**
     * {@inheritdoc}
     */
    public function render(){

        $title = __('Plugin settings','loco-translate');
        $breadcrumb = new Loco_admin_Navigation;
        $breadcrumb->add( $title );

        // common ui elements / labels
        $this->set( 'ui', new Loco_mvc_ViewParams( [
            'api_key' => __('API key','loco-translate'),
            'api_url' => __('API URL','loco-translate'),
            'api_region' => __('API region','loco-translate'),
        ] ) );
        
        return $this->view('admin/config/apis', compact('breadcrumb') );
    }

}SettingsController.php000064400000005701152336322210011123 0ustar00<?php
/**
 *  Site-wide Loco options (plugin settings)
 */
class Loco_admin_config_SettingsController extends Loco_admin_config_BaseController {


    /**
     * {@inheritdoc}
     */
    public function init(){
        parent::init();
        
        // set current plugin options and defaults for placeholders
        $opts = Loco_data_Settings::get();
        $this->set( 'opts', $opts );
        $this->set( 'dflt', Loco_data_Settings::create() );
        
        // roles and capabilities
        $perms = new Loco_data_Permissions;

        // handle save action 
        $nonce = $this->setNonce('save-config');
        try {
            if( $this->checkNonce($nonce->action) ){
                $post = Loco_mvc_PostParams::get();
                if( $post->has('opts') ){
                    $opts->populate( $post->opts )->persist();
                    $perms->populate( $post->has('caps') ? $post->caps : [] );
                    // done update
                    Loco_error_AdminNotices::success( __('Settings saved','loco-translate') );
                    // remove saved params from session if persistent options unset
                    if( ! $opts['fs_persist'] ){
                        $session = Loco_data_Session::get();
                        if( isset($session['loco-fs']) ){
                            unset( $session['loco-fs'] );
                            $session->persist();
                        }
                    }
                }
            }
        }
        catch( Loco_error_Exception $e ){
            Loco_error_AdminNotices::add($e);
        }

        $this->set('caps', $caps = new Loco_mvc_ViewParams );
        // there is no distinct role for network admin, so we'll fake it for UI
        if( is_multisite() ){
            $caps[''] = new Loco_mvc_ViewParams( [
                'label' => __('Super Admin','loco-translate'),
                'name' => 'dummy-admin-cap',
                'attrs' => 'checked disabled'
            ] );
        }
        foreach( $perms->getRoles() as $id => $role ){
            $caps[$id] = new Loco_mvc_ViewParams( [
                'value' => '1',
                'label' => $perms->getRoleName($id),
                'name' => 'caps['.$id.'][loco_admin]',
                'attrs' => $perms->isProtectedRole($role) ? 'checked disabled ' : ( $role->has_cap('loco_admin') ? 'checked ' : '' ),
            ] );
        }
        // allow/deny warning levels
        $this->set('verbose', new Loco_mvc_ViewParams( [
            0 => __('Allow','loco-translate'),
            1 => __('Allow (with warning)','loco-translate'),
            2 => __('Disallow','loco-translate'),
        ] ) );
    }



    /**
     * {@inheritdoc}
     */
    public function render(){
        
        $title = __('Plugin settings','loco-translate');
        $breadcrumb = new Loco_admin_Navigation;
        $breadcrumb->add( $title );
        
        return $this->view('admin/config/settings', compact('breadcrumb') ); 
    }
    
}