Σ(゚Д゚;≡;゚д゚)Σ(゚Д゚;≡;゚д゚)Σ(゚Д゚;≡;゚д゚)始,故人唐宰相鲁公,🆚开府南服,余以布衣从戎。明年,别公漳水湄。后明年,公以事过张睢阳庙及颜杲卿所尝往来处,悲歌慷慨,卒不负其言而从之游。今其诗具在,可考也。😭
core.json 0000644 00000001613 15233441042 0006366 0 ustar 00 {
"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.json 0000644 00000001612 15233441042 0007757 0 ustar 00 {
"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.php 0000644 00000001473 15233531777 0010554 0 ustar 00 <?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.php 0000644 00000012407 15233531777 0011254 0 ustar 00 <?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.php 0000644 00000012220 15233531777 0010601 0 ustar 00 <?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.php 0000644 00000000514 15233531777 0011361 0 ustar 00 <?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.php 0000644 00000010577 15233531777 0007706 0 ustar 00 <?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.php 0000644 00000016002 15233531777 0011500 0 ustar 00 <?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.php 0000644 00000005163 15233531777 0010241 0 ustar 00 <?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.php 0000644 00000003007 15233531777 0014765 0 ustar 00 <?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.php 0000644 00000003341 15233531777 0016671 0 ustar 00 <?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.php 0000644 00000004052 15233531777 0015441 0 ustar 00 <?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.php 0000644 00000001703 15233531777 0015547 0 ustar 00 <?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.php 0000644 00000002354 15233531777 0016611 0 ustar 00 <?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.php 0000644 00000001607 15233531777 0015305 0 ustar 00 <?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.php 0000644 00000014204 15233531777 0014342 0 ustar 00 <?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.php 0000644 00000002005 15233531777 0015064 0 ustar 00 <?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.php 0000644 00000001624 15233531777 0015076 0 ustar 00 <?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.php 0000644 00000001521 15233531777 0020434 0 ustar 00 <?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.php 0000644 00000004735 15233531777 0014637 0 ustar 00 <?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.php 0000644 00000002775 15233531777 0014735 0 ustar 00 <?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.php 0000644 00000001665 15233531777 0017304 0 ustar 00 <?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.php 0000644 00000002005 15233531777 0017431 0 ustar 00 <?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.php 0000644 00000001547 15233531777 0016644 0 ustar 00 <?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.php 0000644 00000002304 15233531777 0017453 0 ustar 00 <?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.php 0000644 00000001314 15233531777 0016602 0 ustar 00 <?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.php 0000644 00000002013 15233531777 0015632 0 ustar 00 <?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.php 0000644 00000003020 15233531777 0016155 0 ustar 00 <?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.php 0000644 00000002023 15233531777 0017044 0 ustar 00 <?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.php 0000644 00000002041 15233531777 0016153 0 ustar 00 <?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.php 0000644 00000002265 15233531777 0021015 0 ustar 00 <?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.php 0000644 00000004365 15233531777 0016124 0 ustar 00 <?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.php 0000644 00000002142 15233531777 0007306 0 ustar 00 <?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.php 0000644 00000021656 15233531777 0007700 0 ustar 00 <?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.php 0000644 00000007513 15233531777 0010213 0 ustar 00 <?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.php 0000644 00000002753 15233531777 0010576 0 ustar 00 <?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.php 0000644 00000005003 15233531777 0012030 0 ustar 00 <?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.php 0000644 00000001020 15233531777 0011325 0 ustar 00 <?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.php 0000644 00000002547 15233632221 0010202 0 ustar 00 <?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.php 0000644 00000004712 15233632221 0010751 0 ustar 00 <?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.php 0000644 00000017430 15233632221 0010353 0 ustar 00 <?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.php 0000644 00000002404 15233632221 0010377 0 ustar 00 <?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.php 0000644 00000004544 15233632221 0010223 0 ustar 00 <?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.php 0000644 00000005701 15233632221 0011123 0 ustar 00 <?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') );
}
}