Your IP : 216.73.216.162


Current Path : /home/x/b/o/xbodynamge/namtation/wp-content/
Upload File :
Current File : /home/x/b/o/xbodynamge/namtation/wp-content/Helpers.tar

Templates.php000066600000007222151120043500007211 0ustar00<?php

namespace WPForms\Helpers;

/**
 * Template related helper methods.
 *
 * @package    WPForms\Helpers
 * @author     WPForms
 * @since      1.5.4
 * @license    GPL-2.0+
 * @copyright  Copyright (c) 2019, WPForms LLC
 */
class Templates {

	/**
	 * Returns a list of paths to check for template locations
	 *
	 * @since 1.5.4
	 *
	 * @return array
	 */
	public static function get_theme_template_paths() {

		$template_dir = 'wpforms';

		$file_paths = array(
			1   => \trailingslashit( \get_stylesheet_directory() ) . $template_dir,
			10  => \trailingslashit( \get_template_directory() ) . $template_dir,
			100 => \trailingslashit( \WPFORMS_PLUGIN_DIR ) . 'templates',
		);

		$file_paths = \apply_filters( 'wpforms_helpers_templates_get_theme_template_paths', $file_paths );

		// Sort the file paths based on priority.
		\ksort( $file_paths, SORT_NUMERIC );

		return \array_map( 'trailingslashit', $file_paths );
	}

	/**
	 * Locate a template and return the path for inclusion.
	 *
	 * @since 1.5.4
	 *
	 * @param string $template_name Template name.
	 *
	 * @return string
	 */
	public static function locate( $template_name ) {

		// Trim off any slashes from the template name.
		$template_name = \ltrim( $template_name, '/' );

		if ( empty( $template_name ) ) {
			return \apply_filters( 'wpforms_helpers_templates_locate', '', $template_name );
		}

		$located = '';

		// Try locating this template file by looping through the template paths.
		foreach ( self::get_theme_template_paths() as $template_path ) {
			if ( \file_exists( $template_path . $template_name ) ) {
				$located = $template_path . $template_name;
				break;
			}
		}

		return \apply_filters( 'wpforms_helpers_templates_locate', $located, $template_name );
	}

	/**
	 * Include a template.
	 * Uses 'require' if $args are passed or 'load_template' if not.
	 *
	 * @since 1.5.4
	 *
	 * @param string $template_name Template name.
	 * @param array  $args          Arguments.
	 * @param bool   $extract       Extract arguments.
	 *
	 * @throws \RuntimeException If extract() tries to modify the scope.
	 */
	public static function include_html( $template_name, $args = array(), $extract = false ) {

		$template_name .= '.php';

		// Allow 3rd party plugins to filter template file from their plugin.
		$located = \apply_filters( 'wpforms_helpers_templates_include_html_located', self::locate( $template_name ), $template_name, $args, $extract );
		$args    = \apply_filters( 'wpforms_helpers_templates_include_html_args', $args, $template_name, $extract );

		if ( empty( $located ) || ! \is_readable( $located ) ) {
			return;
		}

		// Load template WP way if no arguments were passed.
		if ( empty( $args ) ) {
			\load_template( $located, false );
			return;
		}

		$extract = \apply_filters( 'wpforms_helpers_templates_include_html_extract_args', $extract, $template_name, $args );

		if ( $extract && \is_array( $args ) ) {

			$created_vars_count = extract( $args, EXTR_SKIP ); // phpcs:ignore WordPress.PHP.DontExtract

			// Protecting existing scope from modification.
			if ( count( $args ) !== $created_vars_count ) {
				throw new \RuntimeException( 'Extraction failed: variable names are clashing with the existing ones.' );
			}
		}

		require $located;
	}

	/**
	 * Like self::include_html, but returns the HTML instead of including.
	 *
	 * @since 1.5.4
	 *
	 * @param string $template_name Template name.
	 * @param array  $args          Arguments.
	 * @param bool   $extract       Extract arguments.
	 *
	 * @return string
	 */
	public static function get_html( $template_name, $args = array(), $extract = false ) {

		\ob_start();
		self::include_html( $template_name, $args, $extract );
		return \ob_get_clean();
	}
}
ThirdParty.php000066600000055042151135523120007357 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains all third-party related helper methods.
 *
 * @since 4.1.4
 */
trait ThirdParty {
	/**
	 * Checks whether WooCommerce is active.
	 *
	 * @since 4.0.0
	 *
	 * @return bool Whether WooCommerce is active.
	 */
	public function isWooCommerceActive() {
		return class_exists( 'WooCommerce' );
	}

	/**
	 * Checks if the current page is a special WooCommerce page (Cart, Checkout, ...).
	 *
	 * @since 4.0.0
	 *
	 * @param  int    $postId The post ID.
	 * @return string         The type of page or an empty string if it isn't a WooCommerce page.
	 */
	public function isWooCommercePage( $postId = 0 ) {
		$postId                  = $postId ? (int) $postId : get_the_ID();
		$specialWooCommercePages = $this->getWooCommercePages();

		if ( in_array( $postId, $specialWooCommercePages, true ) ) {
			return array_search( $postId, $specialWooCommercePages, true );
		}

		return '';
	}

	/**
	 * Returns the WooCommerce pages.
	 *
	 * @since 4.7.3
	 *
	 * @return array An associative list of special WooCommerce pages.
	 */
	public function getWooCommercePages() {
		if ( ! $this->isWooCommerceActive() ) {
			$wooCommercePages = [];

			return $wooCommercePages;
		}

		$wooCommercePages = [
			'cart'      => (int) get_option( 'woocommerce_cart_page_id' ),
			'checkout'  => (int) get_option( 'woocommerce_checkout_page_id' ),
			'myAccount' => (int) get_option( 'woocommerce_myaccount_page_id' ),
			'terms'     => (int) get_option( 'woocommerce_terms_page_id' ),
		];

		return $wooCommercePages;
	}

	/**
	 * Checks whether the current page is a special WooCommerce page we shouldn't show our schema settings for.
	 *
	 * @since 4.1.6
	 *
	 * @param  int  $postId The post ID.
	 * @return bool         Whether the current page is a disallowed WooCommerce page.
	 */
	public function isWooCommercePageWithoutSchema( $postId = 0 ) {
		$page = $this->isWooCommercePage( $postId );
		if ( ! $page ) {
			return false;
		}

		$disallowedPages = [ 'cart', 'checkout', 'myAccount' ];

		return in_array( $page, $disallowedPages, true );
	}

	/**
	 * Checks whether the queried object is the WooCommerce shop page.
	 *
	 * @since 4.0.0
	 *
	 * @param  int  $id The post ID to check against (optional).
	 * @return bool     Whether the current page is the WooCommerce shop page.
	 */
	public function isWooCommerceShopPage( $id = 0 ) {
		if ( ! $this->isWooCommerceActive() ) {
			return false;
		}

		if ( ! is_admin() && ! aioseo()->helpers->isAjaxCronRestRequest() && function_exists( 'is_shop' ) ) {
			return is_shop();
		}

		// Prevent non-numeric id.
		$id = is_numeric( $id ) ? (int) $id : 0;

		// phpcs:disable HM.Security.ValidatedSanitizedInput, HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
		$id = ! $id && ! empty( $_GET['post'] )
			? (int) sanitize_text_field( wp_unslash( $_GET['post'] ) )
			: $id;
		// phpcs:enable

		return $id && wc_get_page_id( 'shop' ) === $id;
	}

	/**
	 * Checks whether the queried object is the WooCommerce cart page.
	 *
	 * @since 4.1.3
	 *
	 * @param  int  $id The post ID to check against (optional).
	 * @return bool     Whether the current page is the WooCommerce cart page.
	 */
	public function isWooCommerceCartPage( $id = 0 ) {
		if ( ! $this->isWooCommerceActive() ) {
			return false;
		}

		if ( ! is_admin() && ! aioseo()->helpers->isAjaxCronRestRequest() && function_exists( 'is_cart' ) ) {
			return is_cart();
		}

		// phpcs:disable HM.Security.ValidatedSanitizedInput, HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
		$id = ! $id && ! empty( $_GET['post'] )
			? (int) sanitize_text_field( wp_unslash( $_GET['post'] ) )
			: (int) $id;
		// phpcs:enable

		return $id && wc_get_page_id( 'cart' ) === $id;
	}

	/**
	 * Checks whether the queried object is the WooCommerce checkout page.
	 *
	 * @since 4.1.3
	 *
	 * @param  int  $id The post ID to check against (optional).
	 * @return bool     Whether the current page is the WooCommerce checkout page.
	 */
	public function isWooCommerceCheckoutPage( $id = 0 ) {
		if ( ! $this->isWooCommerceActive() ) {
			return false;
		}

		if ( ! is_admin() && ! aioseo()->helpers->isAjaxCronRestRequest() && function_exists( 'is_checkout' ) ) {
			return is_checkout();
		}

		// phpcs:disable HM.Security.ValidatedSanitizedInput, HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
		$id = ! $id && ! empty( $_GET['post'] )
			? (int) sanitize_text_field( wp_unslash( $_GET['post'] ) )
			: (int) $id;
		// phpcs:enable

		return $id && wc_get_page_id( 'checkout' ) === $id;
	}

	/**
	 * Checks whether the queried object is the WooCommerce account page.
	 *
	 * @since 4.1.3
	 *
	 * @param  int  $id The post ID to check against (optional).
	 * @return bool     Whether the current page is the WooCommerce account page.
	 */
	public function isWooCommerceAccountPage( $id = 0 ) {
		if ( ! $this->isWooCommerceActive() ) {
			return false;
		}

		if ( ! is_admin() && ! aioseo()->helpers->isAjaxCronRestRequest() && function_exists( 'is_account_page' ) ) {
			return is_account_page();
		}

		// phpcs:disable HM.Security.ValidatedSanitizedInput, HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
		$id = ! $id && ! empty( $_GET['post'] )
			? (int) sanitize_text_field( wp_unslash( $_GET['post'] ) )
			: (int) $id;
		// phpcs:enable

		return $id && wc_get_page_id( 'myaccount' ) === $id;
	}

	/**
	 * Checks whether the queried object is a WooCommerce product page.
	 *
	 * @since 4.5.5
	 *
	 * @return bool Whether the current page is a WooCommerce product page.
	 */
	public function isWooCommerceProductPage() {
		if (
			! $this->isWooCommerceActive() ||
			! function_exists( 'is_product' )
		) {
			return false;
		}

		return is_product();
	}

	/**
	 * Checks whether the queried object is a WooCommerce taxonomy page.
	 *
	 * @since 4.5.5
	 *
	 * @return bool Whether the current page is a WooCommerce taxonomy page.
	 */
	public function isWooCommerceTaxonomyPage() {
		if (
			! $this->isWooCommerceActive() ||
			! function_exists( 'is_product_taxonomy' )
		) {
			return false;
		}

		return is_product_taxonomy();
	}

	/**
	 * Internationalize.
	 *
	 * @since 4.0.0
	 *
	 * @param $in
	 * @return mixed|void
	 */
	public function internationalize( $in ) {
		if ( function_exists( 'langswitch_filter_langs_with_message' ) ) {
			$in = langswitch_filter_langs_with_message( $in );
		}

		if ( function_exists( 'polyglot_filter' ) ) {
			$in = polyglot_filter( $in );
		}

		if ( function_exists( 'qtrans_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) ) {
			$in = qtrans_useCurrentLanguageIfNotFoundUseDefaultLanguage( $in );
		} elseif ( function_exists( 'ppqtrans_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) ) {
			$in = ppqtrans_useCurrentLanguageIfNotFoundUseDefaultLanguage( $in );
		} elseif ( function_exists( 'qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) ) {
			$in = qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage( $in );
		}

		return apply_filters( 'localization', $in );
	}

	/**
	 * Checks if WPML is active.
	 *
	 * @since 4.0.0
	 *
	 * @return bool True if it is, false if not.
	 */
	public function isWpmlActive() {
		return class_exists( 'SitePress' );
	}

	/**
	 * Checks if TranslatePress is active.
	 *
	 * @since 4.7.3
	 *
	 * @return bool True if it is, false if not.
	 */
	public function isTranslatePressActive() {
		return class_exists( 'TRP_Translate_Press' );
	}

	/**
	 * Localizes a given URL.
	 *
	 * This is required for compatibility with WPML.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $path The relative path of the URL.
	 * @return string $url  The filtered URL.
	 */
	public function localizedUrl( $path ) {
		$url = apply_filters( 'wpml_home_url', home_url( '/' ) );

		// Remove URL parameters.
		preg_match_all( '/\?[\s\S]+/', (string) $url, $matches );

		// Get the base URL.
		$url  = preg_replace( '/\?[\s\S]+/', '', (string) $url );
		$url  = trailingslashit( $url );
		$url .= preg_replace( '/\//', '', (string) $path, 1 );

		// Readd URL parameters.
		if ( $matches && $matches[0] ) {
			$url .= $matches[0][0];
		}

		return $url;
	}

	/**
	 * Checks whether BuddyPress is active.
	 *
	 * @since 4.0.0
	 *
	 * @return boolean
	 */
	public function isBuddyPressActive() {
		return class_exists( 'BuddyPress' );
	}

	/**
	 * Checks whether the queried object is a buddy press user page.
	 *
	 * @since 4.0.0
	 *
	 * @return boolean
	 */
	public function isBuddyPressUser() {
		return $this->isBuddyPressActive() && function_exists( 'bp_is_user' ) && bp_is_user();
	}

	/**
	 * Returns if the page is a BuddyPress page (Activity, Members, Groups).
	 *
	 * @since 4.0.0
	 *
	 * @param  int  $postId The post ID.
	 * @return bool         If the page is a BuddyPress page or not.
	 */
	public function isBuddyPressPage( $postId = 0 ) {
		$bpPageIds = $this->getBuddyPressPageIds();

		return in_array( $postId, $bpPageIds, true );
	}

	/**
	 * Returns the BuddyPress pages.
	 *
	 * @since 4.7.3
	 *
	 * @return array A list of BuddyPress page IDs.
	 */
	public function getBuddyPressPageIds() {
		if ( ! $this->isBuddyPressActive() ) {
			return [];
		}

		static $bpPageIds = null;
		if ( null === $bpPageIds ) {
			$bpPageIds = (array) get_option( 'bp-pages' );
			$bpPageIds = array_map( 'intval', $bpPageIds );
		}

		return $bpPageIds;
	}

	/**
	 * Returns ACF fields as an array of meta keys and values.
	 *
	 * @since 4.0.6
	 *
	 * @param  \WP_Post|int $post  The post.
	 * @param  array        $types A whitelist of ACF field types.
	 * @return array               An array of meta keys and values.
	 */
	public function getAcfContent( $post = null, $types = [] ) {
		$post = ( $post && is_object( $post ) ) ? $post : $this->getPost( $post );

		if ( ! class_exists( 'ACF' ) || ! function_exists( 'get_field_objects' ) ) {
			return [];
		}

		if ( defined( 'ACF_VERSION' ) && version_compare( ACF_VERSION, '5.7.0', '<' ) ) {
			return [];
		}

		// Set defaults.
		$allowedTypes = [
			'text',
			'textarea',
			'email',
			'url',
			'wysiwyg',
			'image',
			'gallery',
			'link',
		];

		$types        = wp_parse_args( $types, $allowedTypes );
		$fieldObjects = get_field_objects( $post->ID );

		if ( empty( $fieldObjects ) ) {
			return [];
		}

		// Filter out any fields that are not in our allowed types.
		$fields = array_filter( $fieldObjects, function( $object ) use ( $types ) {
			return ! empty( $object['value'] ) && in_array( $object['type'], $types, true );
		});

		// Create an array with the field names and values with added HTML markup.
		$acfFields = [];
		foreach ( $fields as $field ) {
			switch ( $field['type'] ) {
				case 'url':
					$value = make_clickable( $field['value'] ?? '' );
					break;
				case 'image':
					// Image format options are array, URL (string), id (int).
					$imageUrl = is_array( $field['value'] ) ? $field['value']['url'] : $field['value'];
					$imageUrl = is_numeric( $imageUrl ) ? wp_get_attachment_image_url( $imageUrl ) : $imageUrl;

					$value = "<img src='$imageUrl' />"; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
					break;
				case 'gallery':
					$imageUrl = $field['value'];
					// The value of a gallery field should always be an array.
					if ( is_array( $imageUrl ) ) {
						$imageUrl = current( $imageUrl );
					}

					// Image array format.
					if ( is_array( $imageUrl ) && ! empty( $imageUrl['url'] ) ) {
						$imageUrl = $imageUrl['url'];
					}

					// Image ID format.
					$imageUrl = is_numeric( $imageUrl ) ? wp_get_attachment_image_url( $imageUrl ) : $imageUrl;

					$value = ! empty( $imageUrl ) ? "<img src='{$imageUrl}' />" : ''; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
					break;
				case 'link':
					$value = make_clickable( $field['value']['url'] ?? $field['value'] ?? '' );
					break;
				default:
					$value = $field['value'];
					break;
			}

			if ( $value ) {
				$acfFields[ $field['name'] ] = $value;
			}
		}

		return $acfFields;
	}

	/**
	 * Retrieves the ACF Flexible Content field value for a given post.
	 *
	 * @since 4.7.9
	 *
	 * @param  string     $name The name of the field.
	 * @param  int|object $post The post ID or object.
	 * @return string           The field value.
	 */
	public function getAcfFlexibleContentField( $name, $post ) {
		$output = '';
		if ( ! function_exists( 'acf_get_raw_field' ) || ! function_exists( 'acf_get_field' ) ) {
			return $output;
		}

		$parentTrace = [];
		$field       = acf_get_raw_field( $name ) ?? [];
		while ( ! empty( $field['parent'] ) && ! empty( $field['parent_layout'] ) ) {
			$parentField   = acf_get_field( $field['parent'] );
			$parentTrace[] = $parentField['name'] ?? '';
			$field         = $parentField;
		}

		$parentTrace = array_filter( $parentTrace );
		if ( empty( $parentTrace ) ) {
			return $output;
		}

		$parentTrace        = array_reverse( $parentTrace );
		$parentName         = array_shift( $parentTrace );
		$highestParentField = get_field( $parentName, $post );

		for ( $i = 0; $i <= count( $parentTrace ); $i++ ) {
			$values = array_filter( array_column( $highestParentField, $name ), 'is_scalar' );
			if ( $values ) {
				return implode( ' ', $values );
			}

			$highestParentField = $highestParentField[0] ?? '';
			if (
				! is_array( $highestParentField ) ||
				! isset( $parentTrace[ $i ] )
			) {
				break;
			}

			$highestParentField = $highestParentField[ $parentTrace[ $i ] ];
		}

		return $output;
	}

	/**
	 * Checks whether the Smash Balloon Custom Facebook Feed plugin is active.
	 *
	 * @since 4.2.0
	 *
	 * @return bool Whether the SB CFF plugin is active.
	 */
	public function isSbCustomFacebookFeedActive() {
		static $isActive = null;
		if ( null !== $isActive ) {
			return $isActive;
		}

		$isActive = defined( 'CFFVER' ) || is_plugin_active( 'custom-facebook-feed/custom-facebook-feed.php' );

		return $isActive;
	}

	/**
	 * Returns the access token for Facebook from Smash Balloon if there is one.
	 *
	 * @since 4.2.0
	 *
	 * @return string|false The access token or false if there is none.
	 */
	public function getSbAccessToken() {
		static $accessToken = null;
		if ( null !== $accessToken ) {
			return $accessToken;
		}

		if ( ! $this->isSbCustomFacebookFeedActive() ) {
			$accessToken = false;

			return $accessToken;
		}

		$oembedTokenData = get_option( 'cff_oembed_token', [] );
		if ( ! $oembedTokenData || empty( $oembedTokenData['access_token'] ) ) {
			$accessToken = false;

			return $accessToken;
		}

		$sbFacebookDataEncryptionInstance = new \CustomFacebookFeed\SB_Facebook_Data_Encryption();
		$accessToken                      = $sbFacebookDataEncryptionInstance->maybe_decrypt( $oembedTokenData['access_token'] );

		return $accessToken;
	}

	/**
	* Returns the homepage URL for a language code.
	*
	* @since 4.2.1
	*
	* @param  string|int $identifier The language code or the post id to return the url.
	* @return string                 The home URL.
	*/
	public function wpmlHomeUrl( $identifier ) {
		foreach ( $this->wpmlHomePages() as $langCode => $wpmlHomePage ) {
			if (
				( is_string( $identifier ) && $langCode === $identifier ) ||
				( is_numeric( $identifier ) && $wpmlHomePage['id'] === $identifier )
			) {
				return $wpmlHomePage['url'];
			}
		}

		return '';
	}

	/**
	 * Returns the homepage IDs.
	 *
	 * @since 4.2.1
	 *
	 * @return array An array of home page ids.
	 */
	public function wpmlHomePages() {
		global $sitepress;
		static $homePages = [];

		if ( ! $this->isWpmlActive() || empty( $sitepress ) || ! method_exists( $sitepress, 'language_url' ) ) {
			return $homePages;
		}

		if ( empty( $homePages ) ) {
			$languages  = apply_filters( 'wpml_active_languages', [] );
			$homePageId = (int) get_option( 'page_on_front' );
			foreach ( $languages as $language ) {
				$homePages[ $language['code'] ] = [
					'id'  => apply_filters( 'wpml_object_id', $homePageId, 'page', false, $language['code'] ),
					'url' => $sitepress->language_url( $language['code'] )
				];
			}
		}

		return $homePages;
	}

	/**
	 * Returns if the post id os a WPML home page.
	 *
	 * @since 4.2.1
	 *
	 * @param  int  $postId The post ID.
	 * @return bool         Is the post id a home page.
	 */
	public function wpmlIsHomePage( $postId ) {
		foreach ( $this->wpmlHomePages() as $wpmlHomePage ) {
			if ( $wpmlHomePage['id'] === $postId ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns the WPML url format.
	 *
	 * @since 4.2.8
	 *
	 * @return string The format.
	 */
	public function getWpmlUrlFormat() {
		global $sitepress;

		if (
			! $this->isWpmlActive() ||
			empty( $sitepress ) ||
			! method_exists( $sitepress, 'get_setting' )
		) {
			return '';
		}

		switch ( $sitepress->get_setting( 'language_negotiation_type' ) ) {
			case WPML_LANGUAGE_NEGOTIATION_TYPE_DIRECTORY:
			case 1:
				return 'directory';
			case WPML_LANGUAGE_NEGOTIATION_TYPE_DOMAIN:
			case 2:
				return 'domain';
			case WPML_LANGUAGE_NEGOTIATION_TYPE_PARAMETER:
			case 3:
				return 'parameter';
			default:
				return '';
		}
	}

	/**
	 * Returns the TranslatePress slugs code and slug.
	 *
	 * @since 4.7.3
	 *
	 * @return array The slugs.
	 */
	public function getTranslatePressUrlSlugs() {
		if ( ! $this->isTranslatePressActive() ) {
			return [];
		}

		$settings = maybe_unserialize( get_option( 'trp_settings', [] ) );

		return isset( $settings['url-slugs'] ) ? $settings['url-slugs'] : [];
	}

	/**
	 * Checks whether the WooCommerce Follow Up Emails plugin is active.
	 *
	 * @since 4.2.2
	 *
	 * @return bool Whether the plugin is active.
	 */
	public function isWooCommerceFollowupEmailsActive() {
		$isActive = defined( 'FUE_VERSION' ) || is_plugin_active( 'woocommerce-follow-up-emails/woocommerce-follow-up-emails.php' );

		return $isActive;
	}

	/**
	 * Checks if the current page is an AMP page.
	 * This function is only effective if called after the `wp` action.
	 *
	 * @since 4.2.3
	 *
	 * @param  string $pluginName The name of the AMP plugin to check for (optional).
	 * @return bool               Whether the current page is an AMP page.
	 */
	public function isAmpPage( $pluginName = '' ) {
		// Official AMP plugin.
		if ( 'amp' === $pluginName ) {
			// If we're checking for the AMP page plugin specifically, return early if it's not active.
			// Otherwise, we'll return true if AMP for WP is enabled because the helper method doesn't distinguish between the two.
			if ( ! defined( 'AMP__VERSION' ) ) {
				return false;
			}

			$options = get_option( 'amp-options' );
			if ( ! empty( $options['theme_support'] ) && 'standard' === strtolower( $options['theme_support'] ) ) {
				return true;
			}
		}

		return $this->isAmpPageHelper();
	}

	/**
	 * Helper function for {@see isAmpPage()}.
	 * Checks if the current page is an AMP page.
	 *
	 * @since 4.2.4
	 *
	 * @return bool Whether the current page is an AMP page.
	 */
	private function isAmpPageHelper() {
		// First check for the existence of any AMP plugin functions. Bail early if none are found, and prevent false positives.
		if (
			! function_exists( 'amp_is_request' ) &&
			! function_exists( 'is_amp_endpoint' ) &&
			! function_exists( 'ampforwp_is_amp_endpoint' ) &&
			! function_exists( 'is_amp_wp' )
		) {
			// If none of the AMP plugin functions are found, return false and allow compatibility with custom implementations.
			return apply_filters( 'aioseo_is_amp_page', false );
		}

		// AMP plugin requires the `wp` action to be called to function properly, otherwise, it will throw warnings.

		if ( did_action( 'wp' ) ) {
			// Check for the "AMP" plugin.
			if ( function_exists( 'amp_is_request' ) ) {
				return (bool) amp_is_request();
			}

			// Check for the "AMP" plugin (`is_amp_endpoint()` is deprecated).
			if ( function_exists( 'is_amp_endpoint' ) ) {
				return (bool) is_amp_endpoint();
			}

			// Check for the "AMP for WP – Accelerated Mobile Pages" plugin.
			if ( function_exists( 'ampforwp_is_amp_endpoint' ) ) {
				return (bool) ampforwp_is_amp_endpoint();
			}

			// Check for the "AMP WP" plugin.
			if ( function_exists( 'is_amp_wp' ) ) {
				return (bool) is_amp_wp();
			}
		}

		return false;
	}

	/**
	 * If we're in a LearnPress lesson page, return the lesson ID.
	 *
	 * @since 4.3.1
	 *
	 * @return int|false
	 */
	public function getLearnPressLesson() {
		// phpcs:disable Squiz.NamingConventions.ValidVariableName
		global $lp_course_item;
		if ( $lp_course_item && method_exists( $lp_course_item, 'get_id' ) ) {
			return $lp_course_item->get_id();
		}
		// phpcs:enable Squiz.NamingConventions.ValidVariableName

		return false;
	}

	/**
	 * Set a flag to indicate Divi whether it is processing internal content or not.
	 *
	 * @since 4.4.3
	 *
	 * @param  null|bool $flag The flag value.
	 * @return null|bool       The previous flag value to reset it later.
	 */
	public function setDiviInternalRendering( $flag ) {
		if ( ! defined( 'ET_BUILDER_VERSION' ) ) {
			return null;
		}
		// phpcs:disable Squiz.NamingConventions.ValidVariableName
		global $et_pb_rendering_column_content;

		$originalValue                  = $et_pb_rendering_column_content;
		$et_pb_rendering_column_content = $flag;
		// phpcs:enable Squiz.NamingConventions.ValidVariableName

		return $originalValue;
	}

	/**
	 * Checks whether the current request is being done by a crawler from Yandex.
	 *
	 * @since 4.4.0
	 *
	 * @return bool Whether the current request is being done by a crawler from Yandex.
	 */
	public function isYandexUserAgent() {
		if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
			return false;
		}

		return preg_match( '#.*Yandex.*#', (string) sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) );
	}

	/**
	 * Checks whether the taxonomy is a WooCommerce product attribute.
	 *
	 * @since 4.7.8
	 *
	 * @param  mixed $taxonomy The taxonomy.
	 * @return bool            Whether the taxonomy is a WooCommerce product attribute.
	 */
	public function isWooCommerceProductAttribute( $taxonomy ) {
		$name = is_object( $taxonomy )
			? $taxonomy->name
			: (
				is_array( $taxonomy )
					? $taxonomy['name']
					: $taxonomy
			);

		return ! empty( $name ) && 'pa_' === substr( $name, 0, 3 );
	}

	/**
	 * Returns whether a plugin is active or not using abstraction.
	 *
	 * @since 4.8.1
	 *
	 * @param  string $slug The plugin slug.
	 * @return bool         Whether the plugin is active.
	 */
	public function isPluginActive( $slug ) {
		$mapped = [
			'buddypress' => 'buddypress/bp-loader.php',
			'bbpress'    => 'bbpress/bbpress.php',
			'weglot'     => 'weglot/weglot.php'
		];

		static $output = [];
		if ( isset( $output[ $slug ] ) ) {
			return $output[ $slug ];
		}

		$mapped[ $slug ] = $mapped[ $slug ] ?? $slug;
		$output[ $slug ] = function_exists( 'is_plugin_active' ) && is_plugin_active( $mapped[ $slug ] );

		return $output[ $slug ];
	}
}WpContext.php000066600000070077151135523120007225 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains all context related helper methods.
 * This includes methods to check the context of the current request, but also get WP objects.
 *
 * @since 4.1.4
 */
trait WpContext {
	/**
	 * The original main query.
	 *
	 * @since 4.3.0
	 *
	 * @var \WP_Query
	 */
	public $originalQuery;

	/**
	 * The original main post variable.
	 *
	 * @since 4.3.0
	 *
	 * @var \WP_Post
	 */
	public $originalPost;

	/**
	 * Get the home page object.
	 *
	 * @since 4.1.1
	 *
	 * @return \WP_Post|null The home page.
	 */
	public function getHomePage() {
		$homePageId = $this->getHomePageId();

		return $homePageId ? get_post( $homePageId ) : null;
	}

	/**
	 * Get the ID of the home page.
	 *
	 * @since 4.0.0
	 *
	 * @return int|false The home page ID.
	 */
	public function getHomePageId() {
		static $homeId = null;
		if ( null !== $homeId ) {
			return $homeId;
		}

		$pageShowOnFront = ( 'page' === get_option( 'show_on_front' ) );
		$pageOnFrontId   = get_option( 'page_on_front' );

		$homeId = $pageShowOnFront && $pageOnFrontId ? (int) $pageOnFrontId : false;

		return $homeId;
	}

	/**
	 * Returns the blog page.
	 *
	 * @since 4.0.0
	 *
	 * @return \WP_Post|null The blog page.
	 */
	public function getBlogPage() {
		$blogPageId = $this->getBlogPageId();

		return $blogPageId ? get_post( $blogPageId ) : null;
	}

	/**
	 * Gets the current blog page id if it's configured.
	 *
	 * @since 4.1.1
	 *
	 * @return int|null
	 */
	public function getBlogPageId() {
		$pageShowOnFront = ( 'page' === get_option( 'show_on_front' ) );
		$blogPageId      = (int) get_option( 'page_for_posts' );

		return $pageShowOnFront && $blogPageId ? $blogPageId : null;
	}

	/**
	 * Checks whether the current page is a taxonomy term archive.
	 *
	 * @since 4.0.0
	 *
	 * @return bool Whether the current page is a taxonomy term archive.
	 */
	public function isTaxTerm() {
		$object = get_queried_object();

		return $object instanceof \WP_Term;
	}

	/**
	 * Checks whether the current page is a static one.
	 *
	 * @since 4.0.0
	 *
	 * @return bool Whether the current page is a static one.
	 */
	public function isStaticPage() {
		return $this->isStaticHomePage() || $this->isStaticPostsPage() || $this->isWooCommerceShopPage();
	}

	/**
	 * Checks whether the current page is the static homepage.
	 *
	 * @since 4.0.0
	 *
	 * @param  mixed $post Pass in an optional post to check if its the static home page.
	 * @return bool        Whether the current page is the static homepage.
	 */
	public function isStaticHomePage( $post = null ) {
		static $isHomePage = null;
		if ( null !== $isHomePage ) {
			return $isHomePage;
		}

		$post = aioseo()->helpers->getPost( $post );

		$isHomePage = ( 'page' === get_option( 'show_on_front' ) && ! empty( $post->ID ) && (int) get_option( 'page_on_front' ) === $post->ID );

		return $isHomePage;
	}

	/**
	 * Checks whether the current page is the dynamic homepage.
	 *
	 * @since 4.2.3
	 *
	 * @return bool Whether the current page is the dynamic homepage.
	 */
	public function isDynamicHomePage() {
		return is_front_page() && is_home();
	}

	/**
	 * Checks whether the current page is the static posts page.
	 *
	 * @since 4.0.0
	 *
	 * @return bool Whether the current page is the static posts page.
	 */
	public function isStaticPostsPage( $post = null ) {
		static $isStaticPostsPage = null;
		if ( null !== $isStaticPostsPage ) {
			return $isStaticPostsPage;
		}

		$post = aioseo()->helpers->getPost( $post );

		$isStaticPostsPage = (
			( is_home() && ( 0 !== (int) get_option( 'page_for_posts' ) ) ) ||
			( ! empty( $post->ID ) && (int) get_option( 'page_for_posts' ) === $post->ID )
		);

		return $isStaticPostsPage;
	}

	/**
	 * Checks whether current page supports meta.
	 *
	 * @since 4.0.0
	 *
	 * @return bool Whether the current page supports meta.
	 */
	public function supportsMeta() {
		return ! is_date() && ! is_author() && ! is_search() && ! is_404();
	}

	/**
	 * Returns the current post object.
	 *
	 * @since 4.0.0
	 *
	 * @param  \WP_Post|int|bool $postId The post ID.
	 * @return \WP_Post|null             The post object.
	 */
	public function getPost( $postId = false ) {
		$postId = is_a( $postId, 'WP_Post' ) ? $postId->ID : $postId;

		if ( aioseo()->helpers->isWooCommerceShopPage( $postId ) ) {
			return get_post( wc_get_page_id( 'shop' ) );
		}

		if ( is_front_page() || is_home() ) {
			$showOnFront = 'page' === get_option( 'show_on_front' );
			if ( $showOnFront ) {
				if ( is_front_page() ) {
					$pageOnFront = (int) get_option( 'page_on_front' );

					return get_post( $pageOnFront );
				} elseif ( is_home() ) {
					$pageForPosts = (int) get_option( 'page_for_posts' );

					return get_post( $pageForPosts );
				}
			}
		}

		// Learnpress lessons load the course. So here we need to switch to the lesson.
		$learnPressLesson = aioseo()->helpers->getLearnPressLesson();
		if ( ! $postId && $learnPressLesson ) {
			$postId = $learnPressLesson;
		}

		// Allow other plugins to filter the post ID e.g. for a special archive page.
		$postId = apply_filters( 'aioseo_get_post_id', $postId );

		// We need to check these conditions and cannot always return get_post() because we'll return the first post on archive pages (dynamic homepage, term pages, etc.).

		if (
			$this->isScreenBase( 'post' ) ||
			$postId ||
			is_singular()
		) {
			return get_post( $postId );
		}

		return null;
	}

	/**
	 * Returns the term object for the given ID or the one from the main query.
	 *
	 * @since 4.7.8
	 *
	 * @param  int    $termId   The term ID.
	 * @param  string $taxonomy The taxonomy.
	 * @return \WP_Term         The term object.
	 */
	public function getTerm( $termId = 0, $taxonomy = '' ) {
		$term = null;
		if ( $termId ) {
			$term = get_term( $termId, $taxonomy );
		} else {
			$term = get_queried_object();
		}

		// If the term is a Product Attribute, set its parent taxonomy to our fake
		// "product_attributes" taxonomy so we can use the default settings.
		if ( is_a( $term, 'WP_Term' ) && $this->isWooCommerceProductAttribute( $term->taxonomy ) ) {
			$term           = clone $term;
			$term->taxonomy = 'product_attributes';
		}

		return $term;
	}

	/**
	 * Returns the current post ID.
	 *
	 * @since 4.3.1
	 *
	 * @return int|null The post ID.
	 */
	public function getPostId() {
		$post = $this->getPost();

		return is_object( $post ) && property_exists( $post, 'ID' ) ? $post->ID : null;
	}

	/**
	 * Returns the post content after parsing it.
	 *
	 * @since 4.1.5
	 *
	 * @param  \WP_Post|int $post The post (optional).
	 * @return string             The post content.
	 */
	public function getPostContent( $post = null ) {
		$post = is_a( $post, 'WP_Post' ) ? $post : $this->getPost( $post );

		static $content = [];
		if ( isset( $content[ $post->ID ] ) ) {
			return $content[ $post->ID ];
		}

		// We need to process the content for page builders.
		$postContent = $post->post_content;
		$pageBuilder = aioseo()->helpers->getPostPageBuilderName( $post->ID );
		if ( ! empty( $pageBuilder ) ) {
			$postContent = aioseo()->standalone->pageBuilderIntegrations[ $pageBuilder ]->processContent( $post->ID, $postContent );
		}

		$postContent = is_string( $postContent ) ? $postContent : '';

		$content[ $post->ID ] = $this->theContent( $postContent );

		if ( apply_filters( 'aioseo_description_include_custom_fields', true, $post ) ) {
			$content[ $post->ID ] .= $this->theContent( $this->getPostCustomFieldsContent( $post ) );
		}

		return $content[ $post->ID ];
	}

	/**
	 * Gets the content from configured custom fields.
	 *
	 * @since 4.2.7
	 *
	 * @param  \WP_Post|int $post A post object or ID.
	 * @return string             The content.
	 */
	public function getPostCustomFieldsContent( $post = null ) {
		$post = is_a( $post, 'WP_Post' ) ? $post : $this->getPost( $post );

		if ( ! aioseo()->dynamicOptions->searchAppearance->postTypes->has( $post->post_type ) ) {
			return '';
		}

		$customFieldKeys = aioseo()->dynamicOptions->searchAppearance->postTypes->{$post->post_type}->customFields;
		if ( empty( $customFieldKeys ) ) {
			return '';
		}

		$customFieldKeys = explode( ' ', sanitize_text_field( $customFieldKeys ) );

		return aioseo()->helpers->getCustomFieldsContent( $post, $customFieldKeys );
	}

	/**
	 * Returns the post content after parsing shortcodes and blocks.
	 * We avoid using the "the_content" hook because it breaks stuff if we call it outside the loop or main query.
	 * See https://developer.wordpress.org/reference/hooks/the_content/
	 *
	 * @since 4.1.5.2
	 *
	 * @param  string $postContent The post content.
	 * @return string              The parsed post content.
	 */
	public function theContent( $postContent ) {
		if ( ! aioseo()->options->searchAppearance->advanced->runShortcodes ) {
			return $postContent;
		}

		// Because do_blocks() and do_shortcodes() can trigger conflicts, we need to clone these objects and restore them afterwards.
		// We need to clone deep to sever pointers/references because these have nested object properties.
		global $wp_query, $post; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		$this->originalQuery = $this->deepClone( $wp_query ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		$this->originalPost  = is_a( $post, 'WP_Post' ) ? $this->deepClone( $post ) : null;

		// The order of the function calls below is intentional and should NOT change.
		$postContent = do_blocks( $postContent );
		$postContent = wpautop( $postContent );
		$postContent = $this->doShortcodes( $postContent );

		$this->restoreWpQuery();

		return $postContent;
	}

	/**
	 * Returns the description based on the post content.
	 *
	 * @since 4.0.0
	 *
	 * @param  \WP_Post|int $post The post (optional).
	 * @return string             The description.
	 */
	public function getDescriptionFromContent( $post = null ) {
		$post = is_a( $post, 'WP_Post' ) ? $post : $this->getPost( $post );

		static $content = [];
		if ( isset( $content[ $post->ID ] ) ) {
			return $content[ $post->ID ];
		}

		$content[ $post->ID ] = '';
		if ( ! empty( $post->post_password ) ) {
			return $content[ $post->ID ];
		}

		$postContent = $this->getPostContent( $post );

		// Strip images, captions and WP oembed wrappers (e.g. YouTube URLs) from the post content.
		$postContent          = preg_replace( '/(<figure.*?\/figure>|<img.*?\/>|<div.*?class="wp-block-embed__wrapper".*?>.*?<\/div>)/s', '', (string) $postContent );
		$postContent          = str_replace( ']]>', ']]&gt;', (string) $postContent );
		$postContent          = trim( wp_strip_all_tags( strip_shortcodes( (string) $postContent ) ) );
		$content[ $post->ID ] = wp_trim_words( (string) $postContent, 55, '' );

		return $content[ $post->ID ];
	}

	/**
	 * Returns custom fields as a string.
	 *
	 * @since 4.0.6
	 *
	 * @param  \WP_Post|int $post The post.
	 * @param  array        $keys The post meta_keys to check for values.
	 * @return string             The custom field content.
	 */
	public function getCustomFieldsContent( $post = null, $keys = [] ) {
		$post = is_a( $post, 'WP_Post' ) ? $post : $this->getPost( $post );

		$customFieldContent = '';

		$acfFields = $this->getAcfContent( $post );
		foreach ( $keys as $key ) {
			// Try ACF.
			if ( isset( $acfFields[ $key ] ) && is_scalar( $acfFields[ $key ] ) ) {
				$customFieldContent .= "$acfFields[$key] ";
				continue;
			}

			// Fallback to post meta.
			$value = get_post_meta( $post->ID, $key, true );
			if ( $value && is_scalar( $value ) ) {
				$customFieldContent .= $value . ' ';
			}
		}

		return $customFieldContent;
	}

	/**
	 * Returns if the page is a special type (WooCommerce pages, Privacy page).
	 *
	 * @since 4.0.0
	 *
	 * @param  int  $postId The post ID.
	 * @return bool         If the page is special or not.
	 */
	public function isSpecialPage( $postId = 0 ) {
		$specialPages = $this->getSpecialPageIds();

		return in_array( (int) $postId, $specialPages, true );
	}

	/**
	 * Returns the ID of all special pages (e.g. homepage, blog page, WooCommerce, BuddyPress, etc.).
	 * This cannot be cached because the plugins need to be loaded first.
	 *
	 * @since 4.7.3
	 *
	 * @return array The IDs of all special pages.
	 */
	public function getSpecialPageIds() {
		$pageForPostsId         = (int) get_option( 'page_for_posts' );
		$pageForPrivacyPolicyId = (int) get_option( 'wp_page_for_privacy_policy' );
		$buddyPressPageIds      = $this->getBuddyPressPageIds();
		$wooCommercePageIds     = array_values( $this->getWooCommercePages() );

		$specialPageIds = array_merge(
			[
				$pageForPostsId,
				$pageForPrivacyPolicyId,
			],
			$buddyPressPageIds,
			$wooCommercePageIds
		);

		// Ensure all values are integers.
		$specialPageIds = array_map( 'intval', $specialPageIds );

		return $specialPageIds;
	}

	/**
	 * Returns whether a post is eligible for being analyzed by TruSEO.
	 *
	 * @since   4.6.1
	 * @version 4.7.3 Renamed from "isPageAnalysisEligible" to "isTruSeoEligible" to make it more clear.
	 *
	 * @param  int  $postId Post ID.
	 * @return bool         Whether a post is eligible for being analyzed by TruSEO.
	 */
	public function isTruSeoEligible( $postId ) {
		static $isTruSeoEnabled = null;
		if ( null === $isTruSeoEnabled ) {
			$isTruSeoEnabled = aioseo()->options->advanced->truSeo;
		}

		if ( ! $isTruSeoEnabled ) {
			return false;
		}

		static $isPostEligible = [];
		if ( isset( $isPostEligible[ $postId ] ) ) {
			return $isPostEligible[ $postId ];
		}

		// Set the default to true.
		$isPostEligible[ $postId ] = true;

		$wpPost = $this->getPost( $postId );
		if ( ! is_a( $wpPost, 'WP_Post' ) ) {
			$isPostEligible[ $postId ] = false;

			return false;
		}

		$eligiblePostTypes = $this->getTruSeoEligiblePostTypes();
		if (
			! in_array( $wpPost->post_type, $eligiblePostTypes, true ) ||
			$this->isSpecialPage( $wpPost->ID )
		) {
			$isPostEligible[ $postId ] = false;
		}

		return $isPostEligible[ $postId ];
	}

	/**
	 * Returns the post types that are eligible for TruSEO analysis.
	 *
	 * @since 4.7.3
	 *
	 * @return array The post types that are eligible for TruSEO analysis.
	 */
	public function getTruSeoEligiblePostTypes() {
		$allowedPostTypes  = aioseo()->helpers->getPublicPostTypes( true );
		$excludedPostTypes = [ 'attachment', 'aioseo-location', 'web-story' ];
		if ( class_exists( 'bbPress' ) ) {
			$excludedPostTypes = array_merge( $excludedPostTypes, [ 'forum', 'topic', 'reply' ] );
		}

		// Remove the excluded post types from the allowed ones.
		$allowedPostTypes = array_diff( $allowedPostTypes, $excludedPostTypes );

		// Now, check if the metabox is enabled and that the post type is public for each of these.
		foreach ( $allowedPostTypes as $postType ) {
			$postObjectType = get_post_type_object( $postType );
			if ( is_a( $postObjectType, 'WP_Post_Type' ) && ! $postObjectType->public ) {
				unset( $allowedPostTypes[ $postType ] );
			}

			$dynamicOptions = aioseo()->dynamicOptions->noConflict();
			if ( ! $dynamicOptions->searchAppearance->postTypes->has( $postType, false ) || ! $dynamicOptions->{$postType}->advanced->showMetaBox ) {
				// If not, unset it.
				unset( $allowedPostTypes[ $postType ] );
			}
		}

		// Considering post types get registered during various stages of the WP load process, we should not cache this.
		return $allowedPostTypes;
	}

	/**
	 * Returns the page number of the current page.
	 *
	 * @since 4.0.0
	 *
	 * @return int The page number.
	 */
	public function getPageNumber() {
		$page = get_query_var( 'page' );
		if ( ! empty( $page ) ) {
			return (int) $page;
		}

		$paged = get_query_var( 'paged' );
		if ( ! empty( $paged ) ) {
			return (int) $paged;
		}

		return 1;
	}


	/**
	 * Returns the page number for the comment page.
	 *
	 * @since 4.2.1
	 *
	 * @return int|false The page number or false if we're not on a comment page.
	 */
	public function getCommentPageNumber() {
		$cpage = get_query_var( 'cpage', null );
		if ( $this->isBlockTheme() ) {
			global $wp_query; // phpcs:ignore Squiz.NamingConventions.ValidVariableName

			// For block themes we can't rely on `get_query_var()` because of {@see build_comment_query_vars_from_block()},
			// so we need to check the query directly.
			$cpage = $wp_query->query['cpage'] ?? null; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		}

		return isset( $cpage ) ? (int) $cpage : false;
	}

	/**
	 * Check if the post passed in is a valid post, not a revision or autosave.
	 *
	 * @since 4.0.5
	 *
	 * @param  \WP_Post $post                The Post object to check.
	 * @param  array    $allowedPostStatuses Allowed post statuses.
	 * @return bool                          True if valid, false if not.
	 */
	public function isValidPost( $post, $allowedPostStatuses = [ 'publish' ] ) {
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return false;
		}

		if ( ! is_object( $post ) ) {
			$post = get_post( $post );
		}

		// No post, no go.
		if ( empty( $post ) ) {
			return false;
		}

		// In order to prevent recursion, we are skipping scheduled-action posts and revisions.
		if (
			'scheduled-action' === $post->post_type ||
			'revision' === $post->post_type
		) {
			return false;
		}

		// Ensure this post has the proper post status.
		if (
			! in_array( $post->post_status, $allowedPostStatuses, true ) &&
			! in_array( 'all', $allowedPostStatuses, true )
		) {
			return false;
		}

		return true;
	}

	/**
	 * Checks whether the given URL is a valid attachment.
	 *
	 * @since 4.0.13
	 *
	 * @param  string $url The URL.
	 * @return bool        Whether the URL is a valid attachment.
	 */
	public function isValidAttachment( $url ) {
		$uploadDirUrl = aioseo()->helpers->escapeRegex( $this->getWpContentUrl() );

		return preg_match( "/$uploadDirUrl.*/", (string) $url );
	}

	/**
	 * Tries to convert an attachment URL into a post ID.
	 *
	 * This our own optimized version of attachment_url_to_postid().
	 *
	 * @since 4.0.13
	 *
	 * @param  string   $url The attachment URL.
	 * @return int|bool      The attachment ID or false if no attachment could be found.
	 */
	public function attachmentUrlToPostId( $url ) {
		$cacheName = 'attachment_url_to_post_id_' . sha1( "aioseo_attachment_url_to_post_id_$url" );

		$cachedId = aioseo()->core->cache->get( $cacheName );
		if ( $cachedId ) {
			return 'none' !== $cachedId && is_numeric( $cachedId ) ? (int) $cachedId : false;
		}

		$path          = $url;
		$uploadDirInfo = wp_get_upload_dir();

		$siteUrl   = wp_parse_url( $uploadDirInfo['url'] );
		$imagePath = wp_parse_url( $path );

		// Force the protocols to match if needed.
		if ( isset( $imagePath['scheme'] ) && ( $imagePath['scheme'] !== $siteUrl['scheme'] ) ) {
			$path = str_replace( $imagePath['scheme'], $siteUrl['scheme'], $path );
		}

		if ( ! $this->isValidAttachment( $path ) ) {
			aioseo()->core->cache->update( $cacheName, 'none' );

			return false;
		}

		if ( 0 === strpos( $path, $uploadDirInfo['baseurl'] . '/' ) ) {
			$path = substr( $path, strlen( $uploadDirInfo['baseurl'] . '/' ) );
		}

		$results = aioseo()->core->db->start( 'postmeta' )
			->select( 'post_id' )
			->where( 'meta_key', '_wp_attached_file' )
			->where( 'meta_value', $path )
			->limit( 1 )
			->run()
			->result();

		if ( empty( $results[0]->post_id ) ) {
			aioseo()->core->cache->update( $cacheName, 'none' );

			return false;
		}

		aioseo()->core->cache->update( $cacheName, $results[0]->post_id );

		return $results[0]->post_id;
	}

	/**
	 * Returns true if the request is a non-legacy REST API request.
	 * This function was copied from WooCommerce and improved.
	 *
	 * @since 4.1.2
	 *
	 * @return bool True if this is a REST API request.
	 */
	public function isRestApiRequest() {
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return true;
		}

		global $wp_rewrite; // phpcs:ignore Squiz.NamingConventions.ValidVariableName

		if ( empty( $wp_rewrite ) ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			return false;
		}

		if ( empty( $_SERVER['REQUEST_URI'] ) ) {
			return false;
		}

		$restUrl = wp_parse_url( get_rest_url() );
		$restUrl = $restUrl['path'] . ( ! empty( $restUrl['query'] ) ? '?' . $restUrl['query'] : '' );

		$isRestApiRequest = ( 0 === strpos( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $restUrl ) );

		return apply_filters( 'aioseo_is_rest_api_request', $isRestApiRequest );
	}

	/**
	 * Checks whether the current request is an AJAX, CRON or REST request.
	 *
	 * @since 4.1.3
	 *
	 * @return bool Whether the request is an AJAX, CRON or REST request.
	 */
	public function isAjaxCronRestRequest() {
		return wp_doing_ajax() || wp_doing_cron() || $this->isRestApiRequest();
	}

	/**
	 * Check if we are in the middle of a WP-CLI call.
	 *
	 * @since 4.2.8
	 *
	 * @return bool True if we are in the WP_CLI context.
	 */
	public function isDoingWpCli() {
		return defined( 'WP_CLI' ) && WP_CLI;
	}

	/**
	 * Checks whether we're on the given screen.
	 *
	 * @since   4.0.7
	 * @version 4.3.1
	 *
	 * @param  string $screenName The screen name.
	 * @param  string $comparison Check as a prefix.
	 * @return bool               Whether we're on the given screen.
	 */
	public function isScreenBase( $screenName, $comparison = '' ) {
		$screen = $this->getCurrentScreen();
		if ( ! $screen || ! isset( $screen->base ) ) {
			return false;
		}

		if ( 'prefix' === $comparison ) {
			return 0 === stripos( $screen->base, $screenName );
		}

		return $screen->base === $screenName;
	}

	/**
	 * Returns if current screen is of a post type
	 *
	 * @since 4.0.17
	 *
	 * @param  string $postType Post type slug
	 * @return bool             True if the current screen is a post type screen.
	 */
	public function isScreenPostType( $postType ) {
		$screen = $this->getCurrentScreen();
		if ( ! $screen || ! isset( $screen->post_type ) ) {
			return false;
		}

		return $screen->post_type === $postType;
	}

	/**
	 * Returns if current screen is a post list, optionaly of a post type.
	 *
	 * @since 4.2.4
	 *
	 * @param  string $postType Post type slug.
	 * @return bool             Is a post list.
	 */
	public function isScreenPostList( $postType = '' ) {
		$screen = $this->getCurrentScreen();
		if (
			! $this->isScreenBase( 'edit' ) ||
			empty( $screen->post_type )
		) {
			return false;
		}

		if ( ! empty( $postType ) && $screen->post_type !== $postType ) {
			return false;
		}

		return true;
	}

	/**
	 * Returns if current screen is a post edit screen, optionaly of a post type.
	 *
	 * @since 4.2.4
	 *
	 * @param  string $postType Post type slug.
	 * @return bool             Is a post editing screen.
	 */
	public function isScreenPostEdit( $postType = '' ) {
		$screen = $this->getCurrentScreen();
		if (
			! $this->isScreenBase( 'post' ) ||
			empty( $screen->post_type )
		) {
			return false;
		}

		if ( ! empty( $postType ) && $screen->post_type !== $postType ) {
			return false;
		}

		return true;
	}

	/**
	 * Gets current admin screen.
	 *
	 * @since 4.0.17
	 *
	 * @return false|\WP_Screen|null
	 */
	public function getCurrentScreen() {
		if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
			return false;
		}

		return get_current_screen();
	}

	/**
	 * Checks whether the current site is a multisite subdomain.
	 *
	 * @since 4.1.9
	 *
	 * @return bool Whether the current site is a subdomain.
	 */
	public function isSubdomain() {
		if ( ! is_multisite() ) {
			return false;
		}

		return apply_filters( 'aioseo_multisite_subdomain', is_subdomain_install() );
	}

	/**
	 * Returns if the current page is the login or register page.
	 *
	 * @since 4.2.1
	 *
	 * @return bool Login or register page.
	 */
	public function isWpLoginPage() {
		// We can't sanitize the filename using sanitize_file_name() here because it will cause issues with custom login pages and certain plugins/themes where this function is not defined.
		$self = ! empty( $_SERVER['PHP_SELF'] ) ? sanitize_text_field( wp_unslash( $_SERVER['PHP_SELF'] ) ) : ''; // phpcs:ignore HM.Security.ValidatedSanitizedInput.InputNotSanitized
		if ( preg_match( '/wp-login\.php$|wp-register\.php$/', (string) $self ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns which type of WordPress page we're seeing.
	 * It will only work if {@see \WP_Query::$queried_object} has been set.
	 *
	 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/#filter-hierarchy
	 *
	 * @since 4.2.8
	 *
	 * @return string|null The template type or `null` if no match.
	 */
	public function getTemplateType() {
		static $type = null;

		if ( ! empty( $type ) ) {
			return $type;
		}

		if ( is_attachment() ) {
			$type = 'attachment';
		} elseif ( is_single() ) {
			$type = 'single';
		} elseif (
			is_page() ||
			$this->isStaticPostsPage() ||
			$this->isWooCommerceShopPage()
		) {
			$type = 'page';
		} elseif ( is_author() ) { // An author page is an archive page, so it needs to be checked before `is_archive()`.
			$type = 'author';
		} elseif (
			is_tax() ||
			is_category() ||
			is_tag()
		) { // A taxonomy term page is an archive page, so it needs to be checked before `is_archive()`.
			$type = 'taxonomy';
		} elseif ( is_date() ) { // A date page is an archive page, so it needs to be checked before `is_archive()`.
			$type = 'date';
		} elseif ( is_archive() ) {
			$type = 'archive';
		} elseif ( is_home() && is_front_page() ) {
			$type = 'dynamic_home';
		} elseif ( is_search() ) {
			$type = 'search';
		}

		return $type;
	}

	/**
	 * Sets the given post as the queried object of the main query.
	 *
	 * @since 4.3.0
	 *
	 * @param  \WP_Post|int $wpPost The post object or ID.
	 * @return void
	 */
	public function setWpQueryPost( $wpPost ) {
		$wpPost = is_a( $wpPost, 'WP_Post' ) ? $wpPost : get_post( $wpPost );
		// phpcs:disable Squiz.NamingConventions.ValidVariableName
		global $wp_query, $post;
		$this->originalQuery = $this->deepClone( $wp_query );
		$this->originalPost  = is_a( $post, 'WP_Post' ) ? $this->deepClone( $post ) : null;

		$wp_query->posts                 = [ $wpPost ];
		$wp_query->post                  = $wpPost;
		$wp_query->post_count            = 1;
		$wp_query->get_queried_object_id = (int) $wpPost->ID;
		$wp_query->queried_object        = $wpPost;
		$wp_query->is_single             = true;
		$wp_query->is_singular           = true;

		if ( 'page' === $wpPost->post_type ) {
			$wp_query->is_page = true;
		}
		// phpcs:enable Squiz.NamingConventions.ValidVariableName

		$post = $wpPost;
	}

	/**
	 * Restores the main query back to the original query.
	 *
	 * @since 4.3.0
	 *
	 * @return void
	 */
	public function restoreWpQuery() {
		global $wp_query, $post; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		if ( is_a( $this->originalQuery, 'WP_Query' ) ) {
			// Loop over all properties and replace the ones that have changed.
			// We want to avoid replacing the entire object because it can cause issues with other plugins.
			foreach ( $this->originalQuery as $key => $value ) {
				if ( $value !== $wp_query->{$key} ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
					$wp_query->{$key} = $value; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
				}
			}
		}

		if ( is_a( $this->originalPost, 'WP_Post' ) ) {
			foreach ( $this->originalPost as $key => $value ) {
				if ( $value !== $post->{$key} ) {
					$post->{$key} = $value;
				}
			}
		}

		$this->originalQuery = null;
		$this->originalPost  = null;
	}

	/**
	 * Gets the list of theme features.
	 *
	 * @since 4.4.9
	 *
	 * @return array List of theme features.
	 */
	public function getThemeFeatures() {
		global $_wp_theme_features; // phpcs:ignore Squiz.NamingConventions.ValidVariableName

		return isset( $_wp_theme_features ) && is_array( $_wp_theme_features ) ? $_wp_theme_features : []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
	}

	/**
	 * Returns whether the active theme is a block-based theme or not.
	 *
	 * @since 4.5.3
	 *
	 * @return bool Whether the active theme is a block-based theme or not.
	 */
	public function isBlockTheme() {
		if ( function_exists( 'wp_is_block_theme' ) ) {
			return wp_is_block_theme(); // phpcs:ignore AIOSEO.WpFunctionUse.NewFunctions.wp_is_block_themeFound
		}

		return false;
	}

	/**
	 * Retrieves the website name.
	 *
	 * @since 4.6.1
	 *
	 * @return string The website name.
	 */
	public function getWebsiteName() {
		return aioseo()->options->searchAppearance->global->schema->websiteName
			? aioseo()->tags->replaceTags( aioseo()->options->searchAppearance->global->schema->websiteName )
			: aioseo()->helpers->decodeHtmlEntities( get_bloginfo( 'name' ) );
	}
}Deprecated.php000066600000006757151135523120007336 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains deprecated methods to be removed at a later date..
 *
 * @since 4.1.9
 */
trait Deprecated {
	/**
	 * Helper method to enqueue scripts.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $script The script to enqueue.
	 * @param  string $url    The URL of the script.
	 * @param  bool   $vue    Whether or not this is a vue script.
	 * @return void
	 */
	public function enqueueScript( $script, $url, $vue = true ) {
		if ( ! wp_script_is( $script, 'enqueued' ) ) {
			wp_enqueue_script(
				$script,
				$this->getScriptUrl( $url, $vue ),
				[],
				aioseo()->version,
				true
			);
		}
	}

	/**
	 * Helper method to enqueue stylesheets.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $style The stylesheet to enqueue.
	 * @param  string $url   The URL of the stylesheet.
	 * @param  bool   $vue    Whether or not this is a vue stylesheet.
	 * @return void
	 */
	public function enqueueStyle( $style, $url, $vue = true ) {
		if ( ! wp_style_is( $style, 'enqueued' ) && $this->shouldEnqueue( $url ) ) {
			wp_enqueue_style(
				$style,
				$this->getScriptUrl( $url, $vue ),
				[],
				aioseo()->version
			);
		}
	}

	/**
	 * Whether or not we should enqueue a file.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $url The url to check against.
	 * @return bool        Whether or not we should enqueue.
	 */
	private function shouldEnqueue( $url ) {
		$version  = strtoupper( aioseo()->versionPath );
		$host     = defined( 'AIOSEO_DEV_' . $version ) ? constant( 'AIOSEO_DEV_' . $version ) : false;

		if ( ! $host ) {
			return true;
		}

		if ( false !== strpos( $url, 'chunk-common.css' ) ) {
			// return false;
		}

		return true;
	}

	/**
	 * Retrieve the proper URL for this script or style.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $url The url.
	 * @param  bool   $vue Whether or not this is a vue script.
	 * @return string      The modified url.
	 */
	public function getScriptUrl( $url, $vue = true ) {
		$version  = strtoupper( aioseo()->versionPath );
		$host     = $vue && defined( 'AIOSEO_DEV_' . $version ) ? constant( 'AIOSEO_DEV_' . $version ) : false;
		$localUrl = $url;
		$url      = plugins_url( 'dist/' . aioseo()->versionPath . '/assets/' . $url, AIOSEO_FILE );

		if ( ! $host ) {
			return $url;
		}

		if ( $host && ! self::$connection ) {
			$splitHost        = explode( ':', str_replace( '/', '', str_replace( 'http://', '', str_replace( 'https://', '', $host ) ) ) );
			self::$connection = @fsockopen( $splitHost[0], $splitHost[1] ); // phpcs:ignore WordPress
		}

		if ( ! self::$connection ) {
			return $url;
		}

		return $host . $localUrl;
	}

	/**
	 * Returns the filesystem object if we have access to it.
	 *
	 * @since 4.0.0
	 *
	 * @param  array                    $args The connection args.
	 * @return \WP_Filesystem_Base|bool       The filesystem object.
	 */
	public function wpfs( $args = [] ) {
		require_once ABSPATH . 'wp-admin/includes/file.php';
		WP_Filesystem( $args );

		// phpcs:disable Squiz.NamingConventions.ValidVariableName
		global $wp_filesystem;
		if ( is_object( $wp_filesystem ) ) {
			return $wp_filesystem;
		}
		// phpcs:enable Squiz.NamingConventions.ValidVariableName

		return false;
	}

	/**
	 * Checks whether the current request is an AJAX, CRON or REST request.
	 *
	 * @since 4.1.9.1
	 *
	 * @return bool Whether the current request is an AJAX, CRON or REST request.
	 */
	public function isAjaxCronRest() {
		return $this->isAjaxCronRestRequest();
	}
}Language.php000066600000000744151135523120007007 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains i18n and language (code) helper methods.
 *
 * @since 4.1.4
 */
trait Language {
	/**
	 * Returns the language of the current response in BCP 47 format.
	 *
	 * @since 4.1.4
	 *
	 * @return string The language code in BCP 47 format.
	 */
	public function currentLanguageCodeBCP47() {
		return str_replace( '_', '-', determine_locale() );
	}
}PostType.php000066600000001410151135523120007042 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains WordPress Post Type helpers.
 *
 * @since 4.2.4
 */
trait PostType {
	/**
	 * Returns a post type feature.
	 *
	 * @since 4.2.4
	 *
	 * @param  string|\WP_Post_Type $postType The post type.
	 * @param  string               $feature  The feature to find.
	 * @return mixed|false                    The post type feature or false if not found.
	 */
	public function getPostTypeFeature( $postType, $feature ) {
		if ( is_string( $postType ) ) {
			$postType = get_post_type_object( $postType );
		}

		if ( ! is_a( $postType, 'WP_Post_Type' ) || ! isset( $postType->$feature ) ) {
			return false;
		}

		return $postType->$feature;
	}
}Vue.php000066600000065374151135523120006035 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use AIOSEO\Plugin\Common\Integrations\WpCode as WpCodeIntegration;
use AIOSEO\Plugin\Common\Models;
use AIOSEO\Plugin\Common\Tools;

/**
 * Contains all Vue related helper methods.
 *
 * @since 4.1.4
 */
trait Vue {
	/**
	 * Holds the data for Vue.
	 *
	 * @since 4.4.9
	 *
	 * @var array
	 */
	private $data = [];

	/**
	 * Optional arguments for setting the data.
	 *
	 * @since 4.4.9
	 *
	 * @var array
	 */
	private $args = [];

	/**
	 * Holds the cached data.
	 *
	 * @since 4.5.1
	 *
	 * @var array
	 */
	private $cache = [];

	/**
	 * Returns the data for Vue.
	 *
	 * @since   4.0.0
	 * @version 4.4.9
	 *
	 * @param  string $page         The current page.
	 * @param  int    $staticPostId Data for a specific post.
	 * @param  string $integration  Data for integration (builder).
	 * @return array                The data.
	 */
	public function getVueData( $page = null, $staticPostId = null, $integration = null ) {
		$this->args = compact( 'page', 'staticPostId', 'integration' );
		$hash       = md5( implode( '', array_map( 'strval', $this->args ) ) );
		if ( isset( $this->cache[ $hash ] ) ) {
			return $this->cache[ $hash ];
		}

		// Clear the data so we start fresh.
		$this->data = [];

		$this->setInitialData();
		$this->setMultisiteData();
		$this->setPostData();
		$this->setDashboardData();
		$this->setSearchStatisticsData();
		$this->setSitemapsData();
		$this->setSetupWizardData();
		$this->setSearchAppearanceData();
		$this->setSocialNetworksData();
		$this->setSeoRevisionsData();
		$this->setToolsOrSettingsData();
		$this->setPageBuilderData();
		$this->setWritingAssistantData();
		$this->setBreadcrumbsData();
		$this->setSeoAnalyzerData();

		$this->cache[ $hash ] = $this->data;

		return $this->cache[ $hash ];
	}

	/**
	 * Set Vue initial data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setInitialData() {
		$screen           = aioseo()->helpers->getCurrentScreen();
		$isStaticHomePage = 'page' === get_option( 'show_on_front' );
		$staticHomePage   = intval( get_option( 'page_on_front' ) );

		$this->data = [
			'page'               => $this->args['page'],
			'screen'             => [
				'base'        => isset( $screen->base ) ? $screen->base : '',
				'postType'    => isset( $screen->post_type ) ? $screen->post_type : '',
				'blockEditor' => isset( $screen->is_block_editor ) ? $screen->is_block_editor : false,
				'new'         => isset( $screen->action ) && 'add' === $screen->action
			],
			'internalOptions'    => aioseo()->internalOptions->all(),
			'options'            => aioseo()->options->all(),
			'dynamicOptions'     => aioseo()->dynamicOptions->all(),
			'deprecatedOptions'  => aioseo()->internalOptions->getAllDeprecatedOptions( true ),
			'settings'           => aioseo()->settings->all(),
			'additional_scripts' => apply_filters( 'aioseo_vue_additional_scripts_enabled', true ),
			'tags'               => aioseo()->tags->all( true ),
			'nonce'              => wp_create_nonce( 'wp_rest' ),
			'urls'               => [
				'domain'            => $this->getSiteDomain(),
				'mainSiteUrl'       => $this->getSiteUrl(),
				'siteLogo'          => aioseo()->helpers->getSiteLogoUrl(),
				'home'              => home_url(),
				'restUrl'           => aioseo()->helpers->getRestUrl(),
				'editScreen'        => admin_url( 'edit.php' ),
				'publicPath'        => aioseo()->core->assets->normalizeAssetsHost( plugin_dir_url( AIOSEO_FILE ) ),
				'assetsPath'        => aioseo()->core->assets->getAssetsPath(),
				'generalSitemapUrl' => aioseo()->sitemap->helpers->getUrl( 'general' ),
				'rssSitemapUrl'     => aioseo()->sitemap->helpers->getUrl( 'rss' ),
				'robotsTxtUrl'      => $this->getSiteUrl() . '/robots.txt',
				'upgradeUrl'        => apply_filters( 'aioseo_upgrade_link', AIOSEO_MARKETING_URL ),
				'staticHomePage'    => 'page' === get_option( 'show_on_front' ) ? get_edit_post_link( get_option( 'page_on_front' ), 'url' ) : null,
				'feeds'             => [
					'rdf'            => get_bloginfo( 'rdf_url' ),
					'rss'            => get_bloginfo( 'rss_url' ),
					'atom'           => get_bloginfo( 'atom_url' ),
					'global'         => get_bloginfo( 'rss2_url' ),
					'globalComments' => get_bloginfo( 'comments_rss2_url' ),
					'staticBlogPage' => $this->getBlogPageId() ? trailingslashit( get_permalink( $this->getBlogPageId() ) ) . 'feed' : ''
				],
				'connect'           => add_query_arg( [
					'siteurl'  => site_url(),
					'homeurl'  => home_url(),
					'redirect' => rawurldecode( base64_encode( admin_url( 'index.php?page=aioseo-connect' ) ) )
				], defined( 'AIOSEO_CONNECT_URL' ) ? AIOSEO_CONNECT_URL : 'https://connect.aioseo.com' ),
				'aio'               => [
					'about'            => is_network_admin() ? network_admin_url( 'admin.php?page=aioseo-about' ) : admin_url( 'admin.php?page=aioseo-about' ),
					'dashboard'        => admin_url( 'admin.php?page=aioseo' ),
					'featureManager'   => admin_url( 'admin.php?page=aioseo-feature-manager' ),
					'linkAssistant'    => admin_url( 'admin.php?page=aioseo-link-assistant' ),
					'localSeo'         => admin_url( 'admin.php?page=aioseo-local-seo' ),
					'monsterinsights'  => admin_url( 'admin.php?page=aioseo-monsterinsights' ),
					'redirects'        => admin_url( 'admin.php?page=aioseo-redirects' ),
					'searchAppearance' => admin_url( 'admin.php?page=aioseo-search-appearance' ),
					'searchStatistics' => admin_url( 'admin.php?page=aioseo-search-statistics' ),
					'seoAnalysis'      => admin_url( 'admin.php?page=aioseo-seo-analysis' ),
					'settings'         => admin_url( 'admin.php?page=aioseo-settings' ),
					'sitemaps'         => admin_url( 'admin.php?page=aioseo-sitemaps' ),
					'socialNetworks'   => admin_url( 'admin.php?page=aioseo-social-networks' ),
					'tools'            => admin_url( 'admin.php?page=aioseo-tools' ),
					'wizard'           => admin_url( 'index.php?page=aioseo-setup-wizard' ),
					'networkSettings'  => is_network_admin() ? network_admin_url( 'admin.php?page=aioseo-settings' ) : '',
					'seoRevisions'     => admin_url( 'admin.php?page=aioseo-seo-revisions' ),
				],
				'admin'             => [
					'widgets'          => admin_url( 'widgets.php' ),
					'optionsReading'   => admin_url( 'options-reading.php' ),
					'scheduledActions' => admin_url( '/tools.php?page=action-scheduler&status=pending&s=aioseo' ),
					'generalSettings'  => admin_url( 'options-general.php' )
				],
				'truSeoWorker'      => aioseo()->core->assets->jsUrl( 'src/app/tru-seo/analyzer/main.js' )
			],
			'backups'            => [],
			'importers'          => [],
			'data'               => [
				'server'                => aioseo()->helpers->getServerName(),
				'robots'                => [
					'defaultRules'      => [],
					'hasPhysicalRobots' => null,
					'rewriteExists'     => null,
					'sitemapUrls'       => []
				],
				'status'                => [],
				'htaccess'              => '',
				'isMultisite'           => is_multisite(),
				'isNetworkAdmin'        => is_network_admin(),
				'currentBlogId'         => get_current_blog_id(),
				'mainSite'              => is_main_site(),
				'subdomain'             => $this->isSubdomain(),
				'isBBPressActive'       => class_exists( 'bbPress' ),
				'isClassicEditorActive' => $this->isClassicEditorActive(),
				'isWooCommerceActive'   => $this->isWooCommerceActive(),
				'staticHomePage'        => $isStaticHomePage ? $staticHomePage : false,
				'staticBlogPage'        => $this->getBlogPageId(),
				'staticBlogPageTitle'   => get_the_title( $this->getBlogPageId() ),
				'isDev'                 => $this->isDev(),
				'isLocal'               => $this->isLocalUrl( site_url() ),
				'isSsl'                 => is_ssl(),
				'hasUrlTrailingSlash'   => '/' === user_trailingslashit( '' ),
				'permalinkStructure'    => get_option( 'permalink_structure' ),
				'usingPermalinks'       => aioseo()->helpers->usingPermalinks(),
				'dateFormat'            => get_option( 'date_format' ),
				'timeFormat'            => get_option( 'time_format' ),
				'siteName'              => aioseo()->helpers->getWebsiteName(),
				'adminEmail'            => get_bloginfo( 'admin_email' ),
				'blocks'                => [
					'toc' => [
						'hashPrefix' => apply_filters( 'aioseo_toc_hash_prefix', 'aioseo-' )
					]
				]
			],
			'user'               => [
				'canManage'      => aioseo()->access->canManage(),
				'capabilities'   => aioseo()->access->getAllCapabilities(),
				'customRoles'    => $this->getCustomRoles(),
				'data'           => wp_get_current_user(),
				'locale'         => function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(),
				'roles'          => $this->getUserRoles(),
				'unfilteredHtml' => current_user_can( 'unfiltered_html' )
			],
			'plugins'            => $this->getPluginData(),
			'postData'           => [
				'postTypes'    => array_values( $this->getPublicPostTypes( false, false, true ) ),
				'taxonomies'   => array_values( $this->getPublicTaxonomies( false, true ) ),
				'archives'     => array_values( $this->getPublicPostTypes( false, true, true ) ),
				'postStatuses' => array_values( $this->getPublicPostStatuses() )
			],
			'notifications'      => array_merge( Models\Notification::getNotifications( true ), [
				'force' => $this->showNotificationsDrawer()
			] ),
			'addons'             => aioseo()->addons->getAddons(),
			'features'           => aioseo()->features->getFeatures(),
			'version'            => AIOSEO_VERSION,
			'wpVersion'          => get_bloginfo( 'version' ),
			'phpVersion'         => PHP_VERSION,
			'helpPanel'          => aioseo()->help->getDocs(),
			'scheduledActions'   => [
				'sitemaps' => []
			],
			'integration'        => $this->args['integration'],
			'theme'              => [
				'features' => aioseo()->helpers->getThemeFeatures()
			]
		];
	}

	/**
	 * Set Vue multisite data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setMultisiteData() {
		if ( ! is_multisite() ) {
			return;
		}

		$this->data['internalNetworkOptions'] = aioseo()->internalNetworkOptions->all();
		$this->data['networkOptions']         = aioseo()->networkOptions->all();
	}

	/**
	 * Set Vue post data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setPostData() {
		if ( 'post' !== $this->args['page'] ) {
			return;
		}

		$postId         = $this->args['staticPostId'] ?: get_the_ID();
		$postTypeObj    = get_post_type_object( get_post_type( $postId ) );
		$post           = Models\Post::getPost( $postId );
		$wpPost         = get_post( $postId );
		$staticHomePage = intval( get_option( 'page_on_front' ) );

		$this->data['currentPost'] = [
			'context'                        => 'post',
			'tags'                           => aioseo()->tags->getDefaultPostTags( $postId ),
			'id'                             => $postId,
			'priority'                       => isset( $post->priority ) && null !== $post->priority ? (float) $post->priority : 'default',
			'frequency'                      => ! empty( $post->frequency ) ? $post->frequency : 'default',
			'permalink'                      => get_permalink( $postId ),
			'editlink'                       => aioseo()->helpers->getPostEditLink( $postId ),
			'title'                          => ! empty( $post->title ) ? $post->title : aioseo()->meta->title->getPostTypeTitle( $postTypeObj->name ),
			'description'                    => ! empty( $post->description ) ? $post->description : aioseo()->meta->description->getPostTypeDescription( $postTypeObj->name ),
			'descriptionIncludeCustomFields' => apply_filters( 'aioseo_description_include_custom_fields', true, $post ),
			'keywords'                       => ! empty( $post->keywords ) ? $post->keywords : [],
			'keyphrases'                     => Models\Post::getKeyphrasesDefaults( $post->keyphrases ),
			'page_analysis'                  => Models\Post::getPageAnalysisDefaults( $post->page_analysis ),
			'loading'                        => [
				'focus'      => false,
				'additional' => [],
			],
			'type'                           => $postTypeObj->labels->singular_name,
			'postType'                       => 'type' === $postTypeObj->name ? '_aioseo_type' : $postTypeObj->name,
			'postStatus'                     => get_post_status( $postId ),
			'postAuthor'                     => (int) $wpPost->post_author,
			'isSpecialPage'                  => $this->isSpecialPage( $postId ),
			'isTruSeoEligible'               => $this->isTruSeoEligible( $postId ),
			'isStaticPostsPage'              => aioseo()->helpers->isStaticPostsPage(),
			'isHomePage'                     => $postId === $staticHomePage,
			'isWooCommercePageWithoutSchema' => $this->isWooCommercePageWithoutSchema( $postId ),
			'seo_score'                      => (int) $post->seo_score,
			'pillar_content'                 => ( (int) $post->pillar_content ) === 0 ? false : true,
			'canonicalUrl'                   => $post->canonical_url,
			'default'                        => ( (int) $post->robots_default ) === 0 ? false : true,
			'noindex'                        => ( (int) $post->robots_noindex ) === 0 ? false : true,
			'noarchive'                      => ( (int) $post->robots_noarchive ) === 0 ? false : true,
			'nosnippet'                      => ( (int) $post->robots_nosnippet ) === 0 ? false : true,
			'nofollow'                       => ( (int) $post->robots_nofollow ) === 0 ? false : true,
			'noimageindex'                   => ( (int) $post->robots_noimageindex ) === 0 ? false : true,
			'noodp'                          => ( (int) $post->robots_noodp ) === 0 ? false : true,
			'notranslate'                    => ( (int) $post->robots_notranslate ) === 0 ? false : true,
			'maxSnippet'                     => null === $post->robots_max_snippet ? -1 : (int) $post->robots_max_snippet,
			'maxVideoPreview'                => null === $post->robots_max_videopreview ? -1 : (int) $post->robots_max_videopreview,
			'maxImagePreview'                => $post->robots_max_imagepreview,
			'modalOpen'                      => false,
			'generalMobilePrev'              => false,
			'og_object_type'                 => ! empty( $post->og_object_type ) ? $post->og_object_type : 'default',
			'og_title'                       => $post->og_title,
			'og_description'                 => $post->og_description,
			'og_image_custom_url'            => $post->og_image_custom_url,
			'og_image_custom_fields'         => $post->og_image_custom_fields,
			'og_image_type'                  => ! empty( $post->og_image_type ) ? $post->og_image_type : 'default',
			'og_video'                       => ! empty( $post->og_video ) ? $post->og_video : '',
			'og_article_section'             => ! empty( $post->og_article_section ) ? $post->og_article_section : '',
			'og_article_tags'                => ! empty( $post->og_article_tags ) ? $post->og_article_tags : [],
			'twitter_use_og'                 => ( (int) $post->twitter_use_og ) === 0 ? false : true,
			'twitter_card'                   => $post->twitter_card,
			'twitter_image_custom_url'       => $post->twitter_image_custom_url,
			'twitter_image_custom_fields'    => $post->twitter_image_custom_fields,
			'twitter_image_type'             => $post->twitter_image_type,
			'twitter_title'                  => $post->twitter_title,
			'twitter_description'            => $post->twitter_description,
			'schema'                         => Models\Post::getDefaultSchemaOptions( $post->schema, aioseo()->helpers->getPost( $postId ) ),
			'metaDefaults'                   => [
				'title'       => aioseo()->meta->title->getPostTypeTitle( $postTypeObj->name ),
				'description' => aioseo()->meta->description->getPostTypeDescription( $postTypeObj->name )
			],
			'linkAssistant'                  => [
				'modalOpen' => false
			],
			'limit_modified_date'            => ( (int) $post->limit_modified_date ) === 0 ? false : true,
			'redirects'                      => [
				'modalOpen' => false
			],
			'options'                        => $post->options,
			'maxAdditionalKeyphrases'        => 0,
		];

		if ( empty( $this->args['integration'] ) ) {
			$this->data['integration'] = aioseo()->helpers->getPostPageBuilderName( $postId );
		}

		if ( ! $post->exists() ) {
			$oldPostMeta = aioseo()->migration->meta->getMigratedPostMeta( $postId );
			foreach ( $oldPostMeta as $k => $v ) {
				if ( preg_match( '#robots_.*#', (string) $k ) ) {
					$oldPostMeta[ preg_replace( '#robots_#', '', (string) $k ) ] = $v;
					continue;
				}
				if ( 'canonical_url' === $k ) {
					$oldPostMeta['canonicalUrl'] = $v;
				}
			}
			$this->data['currentPost'] = array_merge( $this->data['currentPost'], $oldPostMeta );
		}
	}

	/**
	 * Set Vue dashboard data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setDashboardData() {
		if ( 'dashboard' !== $this->args['page'] ) {
			return;
		}

		$this->data['setupWizard']['isCompleted'] = aioseo()->standalone->setupWizard->isCompleted();
		$this->data['seoOverview']                = aioseo()->postSettings->getPostTypesOverview();
		$this->data['importers']                  = aioseo()->importExport->plugins();
	}

	/**
	 * Set Vue search statistics data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setSearchStatisticsData() {
		$this->data['searchStatistics'] = [
			'isConnected'        => aioseo()->searchStatistics->api->auth->isConnected(),
			'sitemapsWithErrors' => aioseo()->searchStatistics->sitemap->getSitemapsWithErrors(),
		];

		if ( 'post' === $this->args['page'] ) {
			$this->data['keywordRankTracker'] = aioseo()->searchStatistics->keywordRankTracker->getVueDataEdit();
		}

		if ( 'search-statistics' === $this->args['page'] ) {
			$this->data['seoOverview']        = aioseo()->postSettings->getPostTypesOverview();
			$this->data['searchStatistics']   = array_merge( $this->data['searchStatistics'], aioseo()->searchStatistics->getVueData() );
			$this->data['keywordRankTracker'] = aioseo()->searchStatistics->keywordRankTracker->getVueData();
			$this->data['indexStatus']        = aioseo()->searchStatistics->indexStatus->getVueData();
		}
	}

	/**
	 * Set Vue sitemaps data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setSitemapsData() {
		if ( 'sitemaps' !== $this->args['page'] ) {
			return;
		}

		$this->data['data']['sitemapUrls'] = aioseo()->sitemap->helpers->getSitemapUrls();

		try {
			if ( as_next_scheduled_action( 'aioseo_static_sitemap_regeneration' ) ) {
				$this->data['scheduledActions']['sitemap'][] = 'staticSitemapRegeneration';
			}
		} catch ( \Exception $e ) {
			// Do nothing.
		}
	}

	/**
	 * Set Vue setup wizard data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setSetupWizardData() {
		if ( 'setup-wizard' !== $this->args['page'] ) {
			return;
		}

		$isStaticHomePage = 'page' === get_option( 'show_on_front' );
		$staticHomePage   = intval( get_option( 'page_on_front' ) );

		$this->data['users']     = $this->getSiteUsers( [ 'administrator', 'editor', 'author' ] );
		$this->data['importers'] = aioseo()->importExport->plugins();
		$this->data['data']      += [
			'staticHomePageTitle'       => $isStaticHomePage ? aioseo()->meta->title->getTitle( $staticHomePage ) : '',
			'staticHomePageDescription' => $isStaticHomePage ? aioseo()->meta->description->getDescription( $staticHomePage ) : '',
		];
	}

	/**
	 * Set Vue search appearance data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setSearchAppearanceData() {
		if ( 'search-appearance' !== $this->args['page'] ) {
			return;
		}

		$isStaticHomePage = 'page' === get_option( 'show_on_front' );
		$staticHomePage   = intval( get_option( 'page_on_front' ) );

		$this->data['users'] = $this->getSiteUsers( [ 'administrator', 'editor', 'author' ] );
		$this->data['data']  += [
			'staticHomePageTitle'       => $isStaticHomePage ? aioseo()->meta->title->getTitle( $staticHomePage ) : '',
			'staticHomePageDescription' => $isStaticHomePage ? aioseo()->meta->description->getDescription( $staticHomePage ) : '',
		];
	}

	/**
	 * Set Vue social networks data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setSocialNetworksData() {
		if ( 'social-networks' !== $this->args['page'] ) {
			return;
		}

		$isStaticHomePage = 'page' === get_option( 'show_on_front' );
		$staticHomePage   = intval( get_option( 'page_on_front' ) );

		$this->data['data'] += [
			'staticHomePageOgTitle'            => $isStaticHomePage ? aioseo()->social->facebook->getTitle( $staticHomePage ) : '',
			'staticHomePageOgDescription'      => $isStaticHomePage ? aioseo()->social->facebook->getDescription( $staticHomePage ) : '',
			'staticHomePageTwitterTitle'       => $isStaticHomePage ? aioseo()->social->twitter->getTitle( $staticHomePage ) : '',
			'staticHomePageTwitterDescription' => $isStaticHomePage ? aioseo()->social->twitter->getDescription( $staticHomePage ) : '',
		];
	}

	/**
	 * Set Vue seo revisions data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setSeoRevisionsData() {
		if ( 'post' === $this->args['page'] ) {
			$this->data['seoRevisions'] = aioseo()->seoRevisions->getVueDataEdit( $this->args['staticPostId'] ?? null );
		}

		if ( 'seo-revisions' === $this->args['page'] ) {
			$this->data['seoRevisions'] = aioseo()->seoRevisions->getVueDataCompare();
		}
	}

	/**
	 * Set Vue tools or settings data.
	 *
	 * @since 4.4.9
	 *
	 * @return void
	 */
	private function setToolsOrSettingsData() {
		if (
			'tools' !== $this->args['page'] &&
			'settings' !== $this->args['page']
		) {
			return;
		}

		if ( 'tools' === $this->args['page'] ) {
			$this->data['backups']                = array_reverse( aioseo()->backup->all() );
			$this->data['importers']              = aioseo()->importExport->plugins();
			$this->data['data']['robots']         = [
				'defaultRules'      => $this->args['page'] ? aioseo()->robotsTxt->extractRules( aioseo()->robotsTxt->getDefaultRobotsTxtContent() ) : [],
				'hasPhysicalRobots' => aioseo()->robotsTxt->hasPhysicalRobotsTxt(),
				'rewriteExists'     => aioseo()->robotsTxt->rewriteRulesExist(),
				'sitemapUrls'       => array_merge( aioseo()->sitemap->helpers->getSitemapUrlsPrefixed(), aioseo()->sitemap->helpers->extractSitemapUrlsFromRobotsTxt() )
			];
			$this->data['data']['status']         = Tools\SystemStatus::getSystemStatusInfo();
			$this->data['data']['htaccess']       = aioseo()->htaccess->getContents();
			$this->data['data']['v3Options']      = ! empty( get_option( 'aioseop_options' ) );
			$this->data['integrations']['wpcode'] = [
				'snippets'          => WpCodeIntegration::loadWpCodeSnippets(),
				'pluginInstalled'   => WpCodeIntegration::isPluginInstalled(),
				'pluginActive'      => WpCodeIntegration::isPluginActive(),
				'pluginNeedsUpdate' => WpCodeIntegration::pluginNeedsUpdate()
			];
		}

		if ( 'settings' === $this->args['page'] ) {
			$this->data['breadcrumbs']['defaultTemplate'] = aioseo()->helpers->encodeOutputHtml( aioseo()->breadcrumbs->frontend->getDefaultTemplate() );
		}

		if (
			is_multisite() &&
			is_network_admin()
		) {
			$this->data['data']['network'] = [
				'sites'   => aioseo()->helpers->getSites( aioseo()->settings->tablePagination['networkDomains'] ),
				'backups' => []
			];
		}
	}

	/**
	 * Set Vue Page Builder data.
	 *
	 * @since   4.4.9
	 * @version 4.5.2 Renamed.
	 *
	 * @return void
	 */
	private function setPageBuilderData() {
		if ( empty( $this->args['integration'] ) ) {
			return;
		}

		if ( 'divi' === $this->args['integration'] ) {
			// This needs to be dropped in order to prevent JavaScript errors in Divi's visual builder.
			// Some of the data from the site analysis can contain HTML tags, e.g. the search preview, and somehow that causes JSON.parse to fail on our localized Vue data.
			unset( $this->data['internalOptions']['internal']['siteAnalysis'] );
		}
	}

	/**
	 * Returns Jed-formatted localization data. Added for backwards-compatibility.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $domain Translation domain.
	 * @return array          The information of the locale.
	 */
	public function getJedLocaleData( $domain ) {
		$translations = get_translations_for_domain( $domain );

		$locale = [
			'' => [
				'domain' => $domain,
				'lang'   => is_admin() && function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale()
			],
		];

		if ( ! empty( $translations->headers['Plural-Forms'] ) ) {
			$locale['']['plural_forms'] = $translations->headers['Plural-Forms'];
		}

		foreach ( $translations->entries as $entry ) {
			if ( empty( $entry->translations ) || ! is_array( $entry->translations ) ) {
				continue;
			}

			foreach ( $entry->translations as $translation ) {
				// If any of the translated strings contains an HTML line break, we need to ignore it. Otherwise, logging into the admin breaks.

				if ( preg_match( '/<br[\s\/\\\\]*>/', (string) $translation ) ) {
					continue 2;
				}
			}

			// Set the translation data using the singular string as the index. This is how Jed expects it, even for plural strings.
			$locale[ $entry->singular ] = $entry->translations;
		}

		return $locale;
	}

	/**
	 * Set Vue writing assistant data.
	 *
	 * @since 4.7.4
	 *
	 * @return void
	 */
	private function setWritingAssistantData() {
		// Settings page or not a post screen.
		if (
			'settings' !== $this->args['page'] &&
			! aioseo()->helpers->isScreenBase( 'post' )
		) {
			return;
		}

		$this->data['writingAssistantSettings'] = aioseo()->writingAssistant->helpers->getSettingsVueData();
	}

	/**
	 * Whether the notifications drawer should be shown or not.
	 *
	 * @since 4.4.9
	 *
	 * @return bool True if it should be shown, false otherwise.
	 */
	private function showNotificationsDrawer() {
		static $showNotificationsDrawer = null;
		if ( null === $showNotificationsDrawer ) {
			$showNotificationsDrawer = (bool) aioseo()->core->cache->get( 'show_notifications_drawer' );

			// If this is set to true, let's disable it now, so it doesn't pop up again.
			if ( $showNotificationsDrawer ) {
				aioseo()->core->cache->delete( 'show_notifications_drawer' );
			}
		}

		return $showNotificationsDrawer;
	}

	/**
	 * Set Vue breadcrumbs data.
	 *
	 * @since 4.8.3
	 *
	 * @return void
	 */
	private function setBreadcrumbsData() {
		$isPostOrTermPage              = aioseo()->helpers->isScreenBase( 'post' ) || aioseo()->helpers->isScreenBase( 'term' );
		$isCurrentPageUsingPageBuilder = 'post' === $this->args['page'] && ! empty( $this->args['integration'] );
		$isSettingsPage                = ! empty( $this->args['page'] ) && 'settings' === $this->args['page'];
		if ( ! $isSettingsPage && ! $isCurrentPageUsingPageBuilder && ! $isPostOrTermPage ) {
			return;
		}

		$this->data['breadcrumbs']['defaultTemplate'] = aioseo()->helpers->encodeOutputHtml( aioseo()->breadcrumbs->frontend->getDefaultTemplate() );
	}

	/**
	 * Set Vue SEO Analyzer data.
	 *
	 * @since 4.8.3
	 *
	 * @return void
	 */
	private function setSeoAnalyzerData() {
		if ( 'seo-analysis' !== $this->args['page'] ) {
			return;
		}

		$this->data['analyzer']['homeResults'] = Models\SeoAnalyzerResult::getResults();
		$this->data['analyzer']['competitors'] = Models\SeoAnalyzerResult::getCompetitorsResults();
	}
}Numbers.php000066600000001570151135523120006675 0ustar00<?php

namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Numbers trait.
 *
 * @since 4.7.2
 */
trait Numbers {
	/**
	 * Formats a number to a compact format.
	 *
	 * @since 4.7.2
	 *
	 * @param  float|int|string $number   The number to format.
	 * @param  int              $decimals The number of decimal places to include.
	 * @return string                     Formatted number in string format.
	 */
	public function compactNumber( $number, $decimals = 1 ) {
		$suffixes    = [ '', 'K', 'M', 'B', 'T', 'q', 'Q' ];
		$suffixIndex = 0;

		while ( abs( $number ) >= 1000 && $suffixIndex < count( $suffixes ) - 1 ) {
			$suffixIndex++;
			$number /= 1000;
		}

		// Remove trailing zeros.
		return preg_replace( '/\D0+$/', '', (string) number_format_i18n( $number, $decimals ) ) . $suffixes[ $suffixIndex ];
	}
}Wp.php000066600000066265151135523120005664 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use AIOSEO\Plugin\Common\Utils;

/**
 * Contains all WP related helper methods.
 *
 * @since 4.1.4
 */
trait Wp {
	/**
	 * Whether or not we have a local connection.
	 *
	 * @since 4.0.0
	 *
	 * @var bool
	 */
	private static $connection = false;

	/**
	 * Returns user roles in the current WP install.
	 *
	 * @since 4.0.0
	 *
	 * @return array An array of user roles.
	 */
	public function getUserRoles() {
		global $wp_roles; // phpcs:ignore Squiz.NamingConventions.ValidVariableName

		$wpRoles = $wp_roles; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		if ( ! is_object( $wpRoles ) ) {
			// Don't assign this to the global because otherwise WordPress won't override it.
			$wpRoles = new \WP_Roles();
		}

		$roleNames = $wpRoles->get_names();
		asort( $roleNames );

		return $roleNames;
	}

	/**
	 * Returns the custom roles in the current WP install.
	 *
	 * @since 4.1.3
	 *
	 * @return array An array of custom roles.
	 */
	public function getCustomRoles() {
		$allRoles = $this->getUserRoles();

		$toSkip = array_merge(
			// Default WordPress roles.
			[ 'superadmin', 'administrator', 'editor', 'author', 'contributor' ],
			// Default AIOSEO roles.
			[ 'aioseo_manager', 'aioseo_editor' ],
			// Filterable roles.
			apply_filters( 'aioseo_access_control_excluded_roles', array_merge( [
				'subscriber'
			], aioseo()->helpers->isWooCommerceActive() ? [ 'customer' ] : [] ) )
		);

		// Remove empty entries.
		$toSkip = array_filter( $toSkip );

		$customRoles = [];
		foreach ( $allRoles as $roleName => $role ) {
			// Skip specific roles.
			if ( in_array( $roleName, $toSkip, true ) ) {
				continue;
			}

			$customRoles[ $roleName ] = $role;
		}

		return $customRoles;
	}

	/**
	 * Returns an array of plugins with the active status.
	 *
	 * @since 4.0.0
	 *
	 * @return array An array of plugins with active status.
	 */
	public function getPluginData() {
		$pluginUpgrader   = new Utils\PluginUpgraderSilentAjax();
		$installedPlugins = array_keys( get_plugins() );

		$plugins = [];
		foreach ( $pluginUpgrader->pluginSlugs as $key => $slug ) {
			$adminUrl        = admin_url( $pluginUpgrader->pluginAdminUrls[ $key ] );
			$networkAdminUrl = null;
			if (
				is_multisite() &&
				is_network_admin() &&
				! empty( $pluginUpgrader->hasNetworkAdmin[ $key ] )
			) {
				$networkAdminUrl = network_admin_url( $pluginUpgrader->hasNetworkAdmin[ $key ] );
				if ( aioseo()->helpers->isPluginNetworkActivated( $pluginUpgrader->pluginSlugs[ $key ] ) ) {
					$adminUrl = $networkAdminUrl;
				}
			}

			$plugins[ $key ] = [
				'basename'        => $slug,
				'installed'       => in_array( $slug, $installedPlugins, true ),
				'activated'       => is_plugin_active( $slug ),
				'adminUrl'        => $adminUrl,
				'networkAdminUrl' => $networkAdminUrl,
				'canInstall'      => aioseo()->addons->canInstall(),
				'canActivate'     => aioseo()->addons->canActivate(),
				'canUpdate'       => aioseo()->addons->canUpdate(),
				'wpLink'          => ! empty( $pluginUpgrader->wpPluginLinks[ $key ] ) ? $pluginUpgrader->wpPluginLinks[ $key ] : null
			];
		}

		return $plugins;
	}

	/**
	 * Returns all registered Post Statuses.
	 *
	 * @since 4.1.6
	 *
	 * @param  boolean $statusesOnly Whether or not to only return statuses.
	 * @return array              An array of post statuses.
	 */
	public function getPublicPostStatuses( $statusesOnly = false ) {
		$allStatuses = get_post_stati( [ 'show_in_admin_all_list' => true ], 'objects' );

		$postStatuses = [];
		foreach ( $allStatuses as $status => $data ) {
			if (
				! $data->public &&
				! $data->protected &&
				! $data->private
			) {
				continue;
			}

			if ( $statusesOnly ) {
				$postStatuses[] = $status;
				continue;
			}

			$postStatuses[] = [
				'label'  => $data->label,
				'status' => $status
			];
		}

		return $postStatuses;
	}

	/**
	 * Returns a list of public post types objects or names.
	 *
	 * @since 4.0.0
	 *
	 * @param  bool  $namesOnly       Whether only the names should be returned.
	 * @param  bool  $hasArchivesOnly Whether to only include post types which have archives.
	 * @param  bool  $rewriteType     Whether to rewrite the type slugs.
	 * @param  array $args            Additional arguments.
	 * @return array                  List of public post types.
	 */
	public function getPublicPostTypes( $namesOnly = false, $hasArchivesOnly = false, $rewriteType = false, $args = [] ) {
		$args = array_merge( [
			'include' => [] // Post types to include.
		], $args );

		$postTypes   = [];
		$postTypeObjects = get_post_types( [], 'objects' );
		foreach ( $postTypeObjects as $postTypeObject ) {
			if ( ! is_post_type_viewable( $postTypeObject ) ) {
				continue;
			}

			$postTypeArray = $this->getPostType( $postTypeObject, $namesOnly, $hasArchivesOnly, $rewriteType );
			if ( ! empty( $postTypeArray ) ) {
				$postTypes[] = $postTypeArray;
			}
		}

		if ( isset( aioseo()->standalone->buddyPress ) ) {
			aioseo()->standalone->buddyPress->maybeAddPostTypes( $postTypes, $namesOnly, $hasArchivesOnly, $args );
		}

		return apply_filters( 'aioseo_public_post_types', $postTypes, $namesOnly, $hasArchivesOnly, $args );
	}

	/**
	 * Returns the data for the given post type.
	 *
	 * @since 4.2.2
	 *
	 * @param  \WP_Post_Type $postTypeObject  The post type object.
	 * @param  bool          $namesOnly       Whether only the names should be returned.
	 * @param  bool          $hasArchivesOnly Whether to only include post types which have archives.
	 * @param  bool          $rewriteType     Whether to rewrite the type slugs.
	 * @return mixed                          Data for the post type.
	 */
	public function getPostType( $postTypeObject, $namesOnly = false, $hasArchivesOnly = false, $rewriteType = false ) {
		if ( empty( $postTypeObject->label ) ) {
			return $namesOnly ? null : [];
		}

		// We don't want to include archives for the WooCommerce shop page.
		if (
			$hasArchivesOnly &&
			(
				! $postTypeObject->has_archive ||
				( 'product' === $postTypeObject->name && $this->isWooCommerceActive() )
			)
		) {
			return $namesOnly ? null : [];
		}

		if ( $namesOnly ) {
			return $postTypeObject->name;
		}

		if ( 'attachment' === $postTypeObject->name ) {
			// We have to check if the 'init' action has been fired to avoid a PHP notice
			// in WP 6.7+ due to loading translations too early.
			if ( did_action( 'init' ) ) {
				$postTypeObject->label = __( 'Attachments', 'all-in-one-seo-pack' );
			}
		}

		if ( 'product' === $postTypeObject->name && $this->isWooCommerceActive() ) {
			$postTypeObject->menu_icon = 'dashicons-products';
		}

		$name = $postTypeObject->name;
		if ( 'type' === $postTypeObject->name && $rewriteType ) {
			$name = '_aioseo_type';
		}

		return [
			'name'         => $name,
			'label'        => ucwords( $postTypeObject->label ),
			'singular'     => ucwords( $postTypeObject->labels->singular_name ),
			'icon'         => $postTypeObject->menu_icon,
			'hasArchive'   => $postTypeObject->has_archive,
			'hierarchical' => $postTypeObject->hierarchical,
			'taxonomies'   => get_object_taxonomies( $name ),
			'slug'         => isset( $postTypeObject->rewrite['slug'] ) ? $postTypeObject->rewrite['slug'] : $name,
			'supports'     => get_all_post_type_supports( $name )
		];
	}

	/**
	 * Returns a list of public taxonomies objects or names.
	 *
	 * @since 4.0.0
	 *
	 * @param  bool  $namesOnly   Whether only the names should be returned.
	 * @param  bool  $rewriteType Whether to rewrite the type slugs.
	 * @return array              List of public taxonomies.
	 */
	public function getPublicTaxonomies( $namesOnly = false, $rewriteType = false ) {
		$taxonomies = [];
		if ( count( $taxonomies ) ) {
			return $taxonomies;
		}

		$taxObjects = get_taxonomies( [], 'objects' );
		foreach ( $taxObjects as $taxObject ) {
			if (
				empty( $taxObject->label ) ||
				! is_taxonomy_viewable( $taxObject ) ||
				aioseo()->helpers->isWooCommerceProductAttribute( $taxObject->name )
			) {
				continue;
			}

			if ( in_array( $taxObject->name, [
				'product_shipping_class',
				'post_format'
			], true ) ) {
				continue;
			}

			if ( $namesOnly ) {
				$taxonomies[] = $taxObject->name;
				continue;
			}

			$name = $taxObject->name;
			if ( 'type' === $taxObject->name && $rewriteType ) {
				$name = '_aioseo_type';
			}

			global $wp_taxonomies; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			$taxonomyPostTypes = ! empty( $wp_taxonomies[ $name ] ) // phpcs:ignore Squiz.NamingConventions.ValidVariableName
				? $wp_taxonomies[ $name ]->object_type // phpcs:ignore Squiz.NamingConventions.ValidVariableName
				: [];

			$taxonomies[] = [
				'name'               => $name,
				'label'              => ucwords( $taxObject->label ),
				'singular'           => ucwords( $taxObject->labels->singular_name ),
				'icon'               => strpos( $taxObject->label, 'categor' ) !== false ? 'dashicons-category' : 'dashicons-tag',
				'hierarchical'       => $taxObject->hierarchical,
				'slug'               => isset( $taxObject->rewrite['slug'] ) ? $taxObject->rewrite['slug'] : '',
				'primaryTermSupport' => (bool) $taxObject->hierarchical,
				'restBase'           => ( $taxObject->rest_base ) ? $taxObject->rest_base : $taxObject->name,
				'postTypes'          => $taxonomyPostTypes
			];
		}

		if ( $this->isWooCommerceActive() ) {
			// We inject a fake one for WooCommerce product attributes so that we can show a single set of settings
			// instead of having to duplicate them for each attribute.
			if ( $namesOnly ) {
				$taxonomies[] = 'product_attributes';
			} else {
				$taxonomies[] = [
					'name'               => 'product_attributes',
					'label'              => __( 'Product Attributes', 'all-in-one-seo-pack' ),
					'singular'           => __( 'Product Attribute', 'all-in-one-seo-pack' ),
					'icon'               => 'dashicons-products',
					'hierarchical'       => true,
					'slug'               => 'product_attributes',
					'primaryTermSupport' => true,
					'restBase'           => 'product_attributes_class',
					'postTypes'          => [ 'product' ]
				];
			}
		}

		return apply_filters( 'aioseo_public_taxonomies', $taxonomies, $namesOnly );
	}

	/**
	 * Retrieve a list of users that match passed in roles.
	 *
	 * @since 4.0.0
	 *
	 * @return array An array of user data.
	 */
	public function getSiteUsers( $roles ) {
		static $users = [];

		if ( ! empty( $users ) ) {
			return $users;
		}

		$rolesWhere = [];
		foreach ( $roles as $role ) {
			$rolesWhere[] = '(um.meta_key = \'' . aioseo()->core->db->db->prefix . 'capabilities\' AND um.meta_value LIKE \'%\"' . $role . '\"%\')';
		}
		// We get the table name from WPDB since multisites share the same table.
		$usersTableName    = aioseo()->core->db->db->users;
		$usermetaTableName = aioseo()->core->db->db->usermeta;
		$dbUsers           = aioseo()->core->db->start( "$usersTableName as u", true )
			->select( 'u.ID, u.display_name, u.user_nicename, u.user_email' )
			->join( "$usermetaTableName as um", 'u.ID = um.user_id', '', true )
			->whereRaw( '(' . implode( ' OR ', $rolesWhere ) . ')' )
			->orderBy( 'u.user_nicename' )
			->run()
			->result();

		foreach ( $dbUsers as $dbUser ) {
			$users[] = [
				'id'          => (int) $dbUser->ID,
				'displayName' => $dbUser->display_name,
				'niceName'    => $dbUser->user_nicename,
				'email'       => $dbUser->user_email,
				'gravatar'    => get_avatar_url( $dbUser->user_email )
			];
		}

		return $users;
	}

	/**
	 * Returns the ID of the site logo if it exists.
	 *
	 * @since 4.0.0
	 *
	 * @return int
	 */
	public function getSiteLogoId() {
		if ( ! get_theme_support( 'custom-logo' ) ) {
			return false;
		}

		return get_theme_mod( 'custom_logo' );
	}

	/**
	 * Returns the URL of the site logo if it exists.
	 *
	 * @since 4.0.0
	 *
	 * @return string
	 */
	public function getSiteLogoUrl() {
		$id = $this->getSiteLogoId();
		if ( ! $id ) {
			return false;
		}

		$image = wp_get_attachment_image_src( $id, 'full' );
		if ( empty( $image ) ) {
			return false;
		}

		return $image[0];
	}

	/**
	 * Returns noindexed post types.
	 *
	 * @since 4.0.0
	 *
	 * @return array A list of noindexed post types.
	 */
	public function getNoindexedPostTypes() {
		return $this->getNoindexedObjects( 'postTypes' );
	}

	/**
	 * Checks whether a given post type is noindexed.
	 *
	 * @since 4.0.0
	 *
	 * @param  string  $postType The post type.
	 * @return bool              Whether the post type is noindexed.
	 */
	public function isPostTypeNoindexed( $postType ) {
		$noindexedPostTypes = $this->getNoindexedPostTypes();

		return in_array( $postType, $noindexedPostTypes, true );
	}

	/**
	 * Checks whether a given post type is public.
	 *
	 * @since 4.2.2
	 *
	 * @param  string  $postType The post type.
	 * @return bool              Whether the post type is public.
	 */
	public function isPostTypePublic( $postType ) {
		$publicPostTypes = $this->getPublicPostTypes( true );

		return in_array( $postType, $publicPostTypes, true );
	}

	/**
	 * Returns noindexed taxonomies.
	 *
	 * @since 4.0.0
	 *
	 * @return array A list of noindexed taxonomies.
	 */
	public function getNoindexedTaxonomies() {
		return $this->getNoindexedObjects( 'taxonomies' );
	}

	/**
	 * Checks whether a given post type is noindexed.
	 *
	 * @since 4.0.0
	 *
	 * @param  string  $taxonomy The taxonomy.
	 * @return bool              Whether the taxonomy is noindexed.
	 */
	public function isTaxonomyNoindexed( $taxonomy ) {
		$noindexedTaxonomies = $this->getNoindexedTaxonomies();

		return in_array( $taxonomy, $noindexedTaxonomies, true );
	}

	/**
	 * Checks whether a given taxonomy is public.
	 *
	 * @since 4.2.2
	 *
	 * @param  string  $taxonomy The taxonomy.
	 * @return bool              Whether the taxonomy is public.
	 */
	public function isTaxonomyPublic( $taxonomy ) {
		$publicTaxonomies = $this->getPublicTaxonomies( true );

		return in_array( $taxonomy, $publicTaxonomies, true );
	}

	/**
	 * Returns noindexed object types of a given parent type.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $type The parent object type ("postTypes", "archives", "taxonomies").
	 * @return array        A list of noindexed objects types.
	 */
	public function getNoindexedObjects( $type ) {
		$noindexed = [];
		foreach ( aioseo()->dynamicOptions->searchAppearance->$type->all() as $name => $object ) {
			if (
				! $object['show'] ||
				( $object['advanced']['robotsMeta'] && ! $object['advanced']['robotsMeta']['default'] && $object['advanced']['robotsMeta']['noindex'] )
			) {
				$noindexed[] = $name;
			}
		}

		return $noindexed;
	}

	/**
	 * Returns all categories for a post.
	 *
	 * @since 4.1.4
	 *
	 * @param  int   $postId The post ID.
	 * @return array         The category names.
	 */
	public function getAllCategories( $postId = 0 ) {
		$names      = [];
		$categories = get_the_category( $postId );
		if ( $categories && count( $categories ) ) {
			foreach ( $categories as $category ) {
				$names[] = aioseo()->helpers->internationalize( $category->name );
			}
		}

		return $names;
	}

	/**
	 * Returns all tags for a post.
	 *
	 * @since 4.1.4
	 *
	 * @param  int   $postId The post ID.
	 * @return array $names  The tag names.
	 */
	public function getAllTags( $postId = 0 ) {
		$names = [];

		$tags = get_the_tags( $postId );
		if ( ! empty( $tags ) && ! is_wp_error( $tags ) ) {
			foreach ( $tags as $tag ) {
				if ( ! empty( $tag->name ) ) {
					$names[] = aioseo()->helpers->internationalize( $tag->name );
				}
			}
		}

		return $names;
	}

	/**
	 * Loads the translations for a given domain.
	 *
	 * @since 4.1.4
	 *
	 * @return void
	 */
	public function loadTextDomain( $domain ) {
		if ( ! is_user_logged_in() ) {
			return;
		}

		// Unload the domain in case WordPress has enqueued the translations for the site language instead of profile language.
		// Reloading the text domain will otherwise not override the existing loaded translations.
		unload_textdomain( $domain );

		$mofile = $domain . '-' . get_user_locale() . '.mo';
		load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile );
	}

	/**
	 * Get the page builder the given Post ID was built with.
	 *
	 * @since 4.1.7
	 *
	 * @param  int         $postId The Post ID.
	 * @return bool|string         The page builder or false if not built with page builders.
	 */
	public function getPostPageBuilderName( $postId ) {
		foreach ( aioseo()->standalone->pageBuilderIntegrations as $integration => $pageBuilder ) {
			if ( $pageBuilder->isBuiltWith( $postId ) ) {
				return $integration;
			}
		}

		return false;
	}

	/**
	 * Get the edit link for the given Post ID.
	 *
	 * @since 4.3.1
	 *
	 * @param  int         $postId The Post ID.
	 * @return bool|string         The edit link or false if not built with page builders.
	 */
	public function getPostEditLink( $postId ) {
		$pageBuilder = $this->getPostPageBuilderName( $postId );
		if ( ! empty( $pageBuilder ) ) {
			return aioseo()->standalone->pageBuilderIntegrations[ $pageBuilder ]->getEditUrl( $postId );
		}

		return get_edit_post_link( $postId );
	}

	/**
	 * Checks if the current user can edit posts of the given post type.
	 *
	 * @since 4.1.9
	 *
	 * @param  string $postType The name of the post type.
	 * @return bool             Whether the user can edit posts of the given post type.
	 */
	public function canEditPostType( $postType ) {
		$capabilities = $this->getPostTypeCapabilities( $postType );

		return current_user_can( $capabilities['edit_posts'] );
	}

	/**
	 * Returns a list of capabilities for the given post type.
	 *
	 * @since 4.1.9
	 *
	 * @param  string $postType The name of the post type.
	 * @return array            The capabilities.
	 */
	public function getPostTypeCapabilities( $postType ) {
		static $capabilities = [];
		if ( isset( $capabilities[ $postType ] ) ) {
			return $capabilities[ $postType ];
		}

		$postTypeObject = get_post_type_object( $postType );
		if ( ! is_a( $postTypeObject, 'WP_Post_Type' ) ) {
			$capabilities[ $postType ] = [];

			return $capabilities[ $postType ];
		}

		$capabilityType = $postTypeObject->capability_type;
		if ( ! is_array( $capabilityType ) ) {
			$capabilityType = [
				$capabilityType,
				$capabilityType . 's'
			];
		}

		// Singular base for meta capabilities, plural base for primitive capabilities.
		list( $singularBase, $pluralBase ) = $capabilityType;

		$capabilities[ $postType ] = [
			'edit_post'          => 'edit_' . $singularBase,
			'read_post'          => 'read_' . $singularBase,
			'delete_post'        => 'delete_' . $singularBase,
			'edit_posts'         => 'edit_' . $pluralBase,
			'edit_others_posts'  => 'edit_others_' . $pluralBase,
			'delete_posts'       => 'delete_' . $pluralBase,
			'publish_posts'      => 'publish_' . $pluralBase,
			'read_private_posts' => 'read_private_' . $pluralBase,
		];

		return $capabilities[ $postType ];
	}

	/**
	 * Checks if the current user can edit terms of the given taxonomy.
	 *
	 * @since 4.1.9
	 *
	 * @param  string $taxonomy The name of the taxonomy.
	 * @return bool             Whether the user can edit posts of the given taxonomy.
	 */
	public function canEditTaxonomy( $taxonomy ) {
		$capabilities = $this->getTaxonomyCapabilities( $taxonomy );

		return current_user_can( $capabilities['edit_terms'] );
	}

	/**
	 * Returns a list of capabilities for the given taxonomy.
	 *
	 * @since 4.1.9
	 *
	 * @param  string $taxonomy The name of the taxonomy.
	 * @return array            The capabilities.
	 */
	public function getTaxonomyCapabilities( $taxonomy ) {
		static $capabilities = [];
		if ( isset( $capabilities[ $taxonomy ] ) ) {
			return $capabilities[ $taxonomy ];
		}

		$taxonomyObject = get_taxonomy( $taxonomy );
		if ( ! is_a( $taxonomyObject, 'WP_Taxonomy' ) ) {
			$capabilities[ $taxonomy ] = [];

			return $capabilities[ $taxonomy ];
		}

		$capabilities[ $taxonomy ] = (array) $taxonomyObject->cap;

		return $capabilities[ $taxonomy ];
	}

	/**
	 * Returns the charset for the site.
	 *
	 * @since 4.2.3
	 *
	 * @return string The name of the charset.
	 */
	public function getCharset() {
		static $charset = null;
		if ( null !== $charset ) {
			return $charset;
		}

		$charset = get_option( 'blog_charset' );
		$charset = $charset ? $charset : 'UTF-8';

		return $charset;
	}

	/**
	 * Returns the given data as JSON.
	 * We temporarily change the floating point precision in order to prevent rounding errors.
	 * Otherwise e.g. 4.9 could be output as 4.90000004.
	 *
	 * @since 4.2.7
	 *
	 * @param  mixed  $data  The data.
	 * @param  int    $flags The flags.
	 * @return string        The JSON output.
	 */
	public function wpJsonEncode( $data, $flags = 0 ) {
		$originalPrecision          = false;
		$originalSerializePrecision = false;
		if ( version_compare( PHP_VERSION, '7.1', '>=' ) ) {
			$originalPrecision          = ini_get( 'precision' );
			$originalSerializePrecision = ini_get( 'serialize_precision' );
			ini_set( 'precision', 17 );
			ini_set( 'serialize_precision', -1 );
		}

		$json = wp_json_encode( $data, $flags );

		if ( version_compare( PHP_VERSION, '7.1', '>=' ) ) {
			ini_set( 'precision', $originalPrecision );
			ini_set( 'serialize_precision', $originalSerializePrecision );
		}

		return $json;
	}

	/**
	 * Returns the post title or a placeholder if there isn't one.
	 *
	 * @since 4.3.0
	 *
	 * @param  int    $postId The post ID.
	 * @return string         The post title.
	 */
	public function getPostTitle( $postId ) {
		static $titles = [];
		if ( isset( $titles[ $postId ] ) ) {
			return $titles[ $postId ];
		}

		$post = aioseo()->helpers->getPost( $postId );
		if ( ! is_a( $post, 'WP_Post' ) ) {
			$titles[ $postId ] = __( '(no title)', 'default' ); // phpcs:ignore AIOSEO.Wp.I18n.TextDomainMismatch, WordPress.WP.I18n.TextDomainMismatch

			return $titles[ $postId ];
		}

		$title = $post->post_title;
		$title = $title ? $title : __( '(no title)', 'default' ); // phpcs:ignore AIOSEO.Wp.I18n.TextDomainMismatch, WordPress.WP.I18n.TextDomainMismatch

		$titles[ $postId ] = aioseo()->helpers->decodeHtmlEntities( $title );

		return $titles[ $postId ];
	}

	/**
	 * Checks whether the post status should be considered viewable.
	 * This function is a copy of the WordPress core function is_post_status_viewable() which was introduced in WP 5.7.
	 *
	 * @since 4.5.0
	 *
	 * @param  string|\stdClass $postStatus The post status name or object.
	 * @return bool                         Whether the post status is viewable.
	 */
	public function isPostStatusViewable( $postStatus ) {
		if ( is_scalar( $postStatus ) ) {
			$postStatus = get_post_status_object( $postStatus );

			if ( ! $postStatus ) {
				return false;
			}
		}

		if (
			! is_object( $postStatus ) ||
			$postStatus->internal ||
			$postStatus->protected
		) {
			return false;
		}

		return $postStatus->publicly_queryable || ( $postStatus->_builtin && $postStatus->public );
	}

	/**
	 * Checks whether the given post is publicly viewable.
	 * This function is a copy of the WordPress core function is_post_publicly_viewable() which was introduced in WP 5.7.
	 *
	 * @since 4.5.0
	 *
	 * @param  int|\WP_Post  $post Optional. Post ID or post object. Defaults to global $post.
	 * @return boolean                      Whether the post is publicly viewable or not.
	 */
	public function isPostPubliclyViewable( $post = null ) {
		$post = get_post( $post );
		if ( empty( $post ) ) {
			return false;
		}

		$postType   = get_post_type( $post );
		$postStatus = get_post_status( $post );

		return is_post_type_viewable( $postType ) && $this->isPostStatusViewable( $postStatus );
	}

	/**
	 * Only register a legacy widget if the WP version is lower than 5.8 or the widget is being used.
	 * The "Block-based Widgets Editor" was released in WP 5.8, so for WP versions below 5.8 it's okay to register them.
	 * The main purpose here is to avoid blocks and widgets with the same name to be displayed on the Customizer,
	 * like e.g. the "Breadcrumbs" Block and Widget.
	 *
	 * @since 4.3.9
	 *
	 * @param string $idBase The base ID of a widget created by extending WP_Widget.
	 * @return bool          Whether the legacy widget can be registered.
	 */
	public function canRegisterLegacyWidget( $idBase ) {
		global $wp_version; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		if (
			version_compare( $wp_version, '5.8', '<' ) || // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			is_active_widget( false, false, $idBase ) ||
			aioseo()->standalone->pageBuilderIntegrations['elementor']->isPluginActive()
		) {
			return true;
		}

		return false;
	}

	/**
	 * Parses blocks for a given post.
	 *
	 * @since 4.6.8
	 *
	 * @param  \WP_Post|int $post          The post or post ID.
	 * @param  bool         $flattenBlocks Whether to flatten the blocks.
	 * @return array                       The parsed blocks.
	 */
	public function parseBlocks( $post, $flattenBlocks = true ) {
		if ( ! is_a( $post, 'WP_Post' ) ) {
			$post = aioseo()->helpers->getPost( $post );
		}

		static $parsedBlocks = [];
		if ( isset( $parsedBlocks[ $post->ID ] ) ) {
			return $parsedBlocks[ $post->ID ];
		}

		$parsedBlocks = parse_blocks( $post->post_content );

		if ( $flattenBlocks ) {
			$parsedBlocks = $this->flattenBlocks( $parsedBlocks );
		}

		$parsedBlocks[ $post->ID ] = $parsedBlocks;

		return $parsedBlocks[ $post->ID ];
	}

	/**
	 * Flattens the given blocks.
	 *
	 * @since 4.6.8
	 *
	 * @param  array $blocks The blocks.
	 * @return array         The flattened blocks.
	 */
	public function flattenBlocks( $blocks ) {
		$flattenedBlocks = [];

		foreach ( $blocks as $block ) {
			if ( ! empty( $block['innerBlocks'] ) ) {
				// Flatten inner blocks first.
				$innerBlocks = $this->flattenBlocks( $block['innerBlocks'] );
				unset( $block['innerBlocks'] );

				// Add the current block to the result.
				$flattenedBlocks[] = $block;

				// Add the flattened inner blocks to the result.
				$flattenedBlocks = array_merge( $flattenedBlocks, $innerBlocks );
			} else {
				// If no inner blocks, just add the block to the result.
				$flattenedBlocks[] = $block;
			}
		}

		return $flattenedBlocks;
	}

	/**
	 * Checks if the Classic eEditor is active and if the Block Editor is disabled in its settings.
	 *
	 * @since 4.7.3
	 *
	 * @return bool Whether the Classic Editor is active.
	 */
	public function isClassicEditorActive() {
		include_once ABSPATH . 'wp-admin/includes/plugin.php';

		if ( ! is_plugin_active( 'classic-editor/classic-editor.php' ) ) {
			return false;
		}

		return 'classic' === get_option( 'classic-editor-replace' );
	}

	/**
	 * Redirects to a 404 Not Found page if the sitemap is disabled.
	 *
	 * @since 4.0.0
	 * @version 4.8.0 Moved from the Sitemap class.
	 *
	 * @return void
	 */
	public function notFoundPage() {
		global $wp_query; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		$wp_query->set_404(); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		status_header( 404 );
		include_once get_404_template();
		exit;
	}

	/**
	 * Retrieves the post type labels for the given post type.
	 *
	 * @since 4.8.2
	 *
	 * @param  string $postType The name of a registered post type.
	 * @return object           Object with all the labels as member variables.
	 */
	public function getPostTypeLabels( $postType ) {
		static $postTypeLabels = [];
		if ( ! isset( $postTypeLabels[ $postType ] ) ) {
			$postTypeObject = get_post_type_object( $postType );
			if ( ! is_a( $postTypeObject, 'WP_Post_Type' ) ) {
				return null;
			}

			$postTypeLabels[ $postType ] = get_post_type_labels( $postTypeObject );
		}

		return $postTypeLabels[ $postType ];
	}
}Shortcodes.php000066600000014440151135523120007377 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains shortcode specific helper methods.
 *
 * @since 4.1.2
 */
trait Shortcodes {
	/**
	 * Shortcodes known to conflict with AIOSEO.
	 * NOTE: This is deprecated and only there for users who already were using the aioseo_conflicting_shortcodes_hook before 4.2.0.
	 *
	 * @since 4.1.2
	 *
	 * @var array
	 */
	private $conflictingShortcodes = [
		'WooCommerce Login'                => 'woocommerce_my_account',
		'WooCommerce Checkout'             => 'woocommerce_checkout',
		'WooCommerce Order Tracking'       => 'woocommerce_order_tracking',
		'WooCommerce Cart'                 => 'woocommerce_cart',
		'WooCommerce Registration'         => 'wwp_registration_form',
		'WISDM Group Registration'         => 'wdm_group_users',
		'WISDM Quiz Reporting'             => 'wdm_quiz_statistics_details',
		'WISDM Course Review'              => 'rrf_course_review',
		'Simple Membership Login'          => 'swpm_login_form',
		'Simple Membership Mini Login'     => 'swpm_mini_login',
		'Simple Membership Payment Button' => 'swpm_payment_button',
		'Simple Membership Thank You Page' => 'swpm_thank_you_page_registration',
		'Simple Membership Registration'   => 'swpm_registration_form',
		'Simple Membership Profile'        => 'swpm_profile_form',
		'Simple Membership Reset'          => 'swpm_reset_form',
		'Simple Membership Update Level'   => 'swpm_update_level_to',
		'Simple Membership Member Info'    => 'swpm_show_member_info',
		'Revslider'                        => 'rev_slider'
	];

	/**
	 * Returns the content with shortcodes replaced.
	 *
	 * @since 4.0.5
	 *
	 * @param  string $content  The post content.
	 * @param  bool   $override Whether shortcodes should be parsed regardless of the context. Needed for ActionScheduler actions.
	 * @param  int    $postId   The post ID (optional).
	 * @return string $content  The post content with shortcodes replaced.
	 */
	public function doShortcodes( $content, $override = false, $postId = 0 ) {
		// NOTE: This is_admin() check can never be removed because themes like Avada will otherwise load the wrong post.
		if ( ! $override && is_admin() ) {
			return $content;
		}

		if ( ! wp_doing_cron() && ! wp_doing_ajax() ) {
			if ( ! $override && apply_filters( 'aioseo_disable_shortcode_parsing', false ) ) {
				return $content;
			}

			if ( ! $override && ! aioseo()->options->searchAppearance->advanced->runShortcodes ) {
				return $this->doAllowedShortcodes( $content, $postId );
			}
		}

		$content = $this->doShortcodesHelper( $content, [], $postId );

		return $content;
	}

	/**
	 * Returns the content with only the allowed shortcodes and wildcards replaced.
	 *
	 * @since   4.1.2
	 * @version 4.6.6 Added the $allowedTags parameter.
	 *
	 * @param  string $content     The content.
	 * @param  int    $postId      The post ID (optional).
	 * @param  array  $allowedTags The shortcode tags to allow (optional).
	 * @return string              The content with shortcodes replaced.
	 */
	public function doAllowedShortcodes( $content, $postId = null, $allowedTags = [] ) {
		// Extract list of shortcodes from the post content.
		$tags = $this->getShortcodeTags( $content );
		if ( ! count( $tags ) ) {
			return $content;
		}

		$allowedTags  = apply_filters( 'aioseo_allowed_shortcode_tags', $allowedTags );
		$tagsToRemove = array_diff( $tags, $allowedTags );

		$content = $this->doShortcodesHelper( $content, $tagsToRemove, $postId );

		return $content;
	}

	/**
	 * Returns the content with only the allowed shortcodes and wildcards replaced.
	 *
	 * @since 4.1.2
	 *
	 * @param  string $content      The content.
	 * @param  array  $tagsToRemove The shortcode tags to remove (optional).
	 * @param  int    $postId       The post ID (optional).
	 * @return string               The content with shortcodes replaced.
	 */
	private function doShortcodesHelper( $content, $tagsToRemove = [], $postId = 0 ) {
		global $shortcode_tags; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		$conflictingShortcodes = array_merge( $tagsToRemove, $this->conflictingShortcodes );
		$conflictingShortcodes = apply_filters( 'aioseo_conflicting_shortcodes', $conflictingShortcodes );

		$tagsToRemove = [];
		foreach ( $conflictingShortcodes as $shortcode ) {
			$shortcodeTag = str_replace( [ '[', ']' ], '', $shortcode );
			if ( array_key_exists( $shortcodeTag, $shortcode_tags ) ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
				$tagsToRemove[ $shortcodeTag ] = $shortcode_tags[ $shortcodeTag ]; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			}
		}

		// Remove all conflicting shortcodes before parsing the content.
		foreach ( $tagsToRemove as $shortcodeTag => $shortcodeCallback ) {
			remove_shortcode( $shortcodeTag );
		}

		if ( $postId ) {
			global $post;
			$post = get_post( $postId );
			if ( is_a( $post, 'WP_Post' ) ) {
				// Add the current post to the loop so that shortcodes can use it if needed.
				setup_postdata( $post );
			}
		}

		// Set a flag to indicate Divi that it's processing internal content.

		$default = aioseo()->helpers->setDiviInternalRendering( true );

		$content = do_shortcode( $content );

		// Reset the Divi flag to its default value.
		aioseo()->helpers->setDiviInternalRendering( $default );

		if ( $postId ) {
			wp_reset_postdata();
		}

		// Add back shortcodes as remove_shortcode() disables them site-wide.
		foreach ( $tagsToRemove as $shortcodeTag => $shortcodeCallback ) {
			add_shortcode( $shortcodeTag, $shortcodeCallback );
		}

		return $content;
	}

	/**
	 * Extracts the shortcode tags from the content.
	 *
	 * @since 4.1.2
	 *
	 * @param  string $content The content.
	 * @return array  $tags    The shortcode tags.
	 */
	private function getShortcodeTags( $content ) {
		$tags    = [];
		$pattern = '\\[(\\[?)([^\s]*)(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*+(?:\\[(?!\\/\\2\\])[^\\[]*+)*+)\\[\\/\\2\\])?)(\\]?)';
		if ( preg_match_all( "#$pattern#s", (string) $content, $matches ) && array_key_exists( 2, $matches ) ) {
			$tags = array_unique( $matches[2] );
		}

		if ( ! count( $tags ) ) {
			return $tags;
		}

		// Extract nested shortcodes.
		foreach ( $matches[5] as $innerContent ) {
			$tags = array_merge( $tags, $this->getShortcodeTags( $innerContent ) );
		}

		return $tags;
	}
}Svg.php000066600000002017151135523120006016 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains SVG specific helper methods.
 *
 * @since 4.1.4
 */
trait Svg {
	/**
	 * Sanitizes a SVG string.
	 *
	 * @since 4.1.4
	 *
	 * @param  string $svgString The SVG to check.
	 * @return string            The sanitized SVG.
	 */
	public function escSvg( $svgString ) {
		if ( ! is_string( $svgString ) ) {
			return false;
		}

		$ksesDefaults = wp_kses_allowed_html( 'post' );

		$svgArgs = [
			'svg'   => [
				'class'           => true,
				'aria-hidden'     => true,
				'aria-labelledby' => true,
				'role'            => true,
				'xmlns'           => true,
				'width'           => true,
				'height'          => true,
				'viewbox'         => true, // <= Must be lower case!
			],
			'g'     => [ 'fill' => true ],
			'title' => [ 'title' => true ],
			'path'  => [
				'd'    => true,
				'fill' => true,
			]
		];

		return wp_kses( $svgString, array_merge( $ksesDefaults, $svgArgs ) );
	}
}Api.php000066600000004752151135523120006000 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains Action Scheduler specific helper methods.
 *
 * @since 4.2.4
 */
trait Api {
	/**
	 * Request the remote URL via wp_remote_post and return a json decoded response.
	 *
	 * @since 4.2.4
	 *
	 * @param  array       $body    The content to retrieve from the remote URL.
	 * @param  array       $headers The headers to send to the remote URL.
	 * @return object|null          JSON decoded response on success, false on failure.
	 */
	public function sendRequest( $url, $body = [], $headers = [] ) {
		$body = wp_json_encode( $body );

		// Build the headers of the request.
		$headers = wp_parse_args(
			$headers,
			[
				'Content-Type' => 'application/json'
			]
		);

		// Setup variable for wp_remote_post.
		$requestArgs = [
			'headers' => $headers,
			'body'    => $body,
			'timeout' => 20
		];

		// Perform the query and retrieve the response.
		$response     = $this->wpRemotePost( $url, $requestArgs );
		$responseBody = wp_remote_retrieve_body( $response );

		// Bail out early if there are any errors.
		if ( ! $responseBody ) {
			return null;
		}

		// Return the json decoded content.
		return json_decode( $responseBody );
	}

	/**
	 * Default arguments for wp_remote_get and wp_remote_post.
	 *
	 * @since 4.2.4
	 *
	 * @return array An array of default arguments for the request.
	 */
	private function getWpApiRequestDefaults() {
		return [
			'timeout'    => 10,
			'headers'    => aioseo()->helpers->getApiHeaders(),
			'user-agent' => aioseo()->helpers->getApiUserAgent()
		];
	}

	/**
	 * Sends a request using wp_remote_post.
	 *
	 * @since 4.2.4
	 *
	 * @param  string          $url  The URL to send the request to.
	 * @param  array           $args The args to use in the request.
	 * @return array|\WP_Error      The response as an array or WP_Error on failure.
	 */
	public function wpRemotePost( $url, $args = [] ) {
		return wp_remote_post( $url, array_replace_recursive( $this->getWpApiRequestDefaults(), $args ) );
	}

	/**
	 * Sends a request using wp_remote_get.
	 *
	 * @since 4.2.4
	 *
	 * @param  string          $url  The URL to send the request to.
	 * @param  array           $args The args to use in the request.
	 * @return array|\WP_Error      The response as an array or WP_Error on failure.
	 */
	public function wpRemoteGet( $url, $args = [] ) {
		return wp_remote_get( $url, array_replace_recursive( $this->getWpApiRequestDefaults(), $args ) );
	}
}Strings.php000066600000046236151135523120006723 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains string specific helper methods.
 *
 * @since 4.0.13
 */
trait Strings {
	/**
	 * Convert to snake case.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $string The string to convert.
	 * @return string         The converted string.
	 */
	public function toSnakeCase( $string ) {
		$string[0] = strtolower( $string[0] );

		return preg_replace_callback( '/([A-Z])/', function ( $value ) {
			return '_' . strtolower( $value[1] );
		}, $string );
	}

	/**
	 * Convert to camel case.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $string     The string to convert.
	 * @param  bool   $capitalize Whether or not to capitalize the first letter.
	 * @return string             The converted string.
	 */
	public function toCamelCase( $string, $capitalize = false ) {
		$string[0] = strtolower( $string[0] );
		if ( $capitalize ) {
			$string[0] = strtoupper( $string[0] );
		}

		return preg_replace_callback( '/_([a-z0-9])/', function ( $value ) {
			return strtoupper( $value[1] );
		}, $string );
	}

	/**
	 * Converts kebab case to camel case.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $string                   The string to convert.
	 * @param  bool   $capitalizeFirstCharacter Whether to capitalize the first letter.
	 * @return string                           The converted string.
	 */
	public function dashesToCamelCase( $string, $capitalizeFirstCharacter = false ) {
		$string = str_replace( ' ', '', ucwords( str_replace( '-', ' ', $string ) ) );
		if ( ! $capitalizeFirstCharacter ) {
			$string[0] = strtolower( $string[0] );
		}

		return $string;
	}

	/**
	 * Truncates a given string.
	 *
	 * @since 4.0.0
	 *
	 * @param  string  $string             The string.
	 * @param  int     $maxCharacters      The max. amount of characters.
	 * @param  boolean $shouldHaveEllipsis Whether the string should have a trailing ellipsis (defaults to true).
	 * @return string                      The string.
	 */
	public function truncate( $string, $maxCharacters, $shouldHaveEllipsis = true ) {
		$length       = strlen( $string );
		$excessLength = $length - $maxCharacters;
		if ( 0 < $excessLength ) {
			// If the string is longer than 65535 characters, we first need to shorten it due to the character limit of the regex pattern quantifier.
			if ( 65535 < $length ) {
				$string = substr( $string, 0, 65534 );
			}
			$string = preg_replace( "#[^\pZ\pP]*.{{$excessLength}}$#", '', (string) $string );
			if ( $shouldHaveEllipsis ) {
				$string = $string . ' ...';
			}
		}

		return $string;
	}

	/**
	 * Escapes special regex characters.
	 *
	 * @since 4.0.5
	 *
	 * @param  string $string    The string.
	 * @param  string $delimiter The delimiter character.
	 * @return string            The escaped string.
	 */
	public function escapeRegex( $string, $delimiter = '/' ) {
		static $escapeRegex = [];
		if ( isset( $escapeRegex[ $string ] ) ) {
			return $escapeRegex[ $string ];
		}
		$escapeRegex[ $string ] = preg_quote( (string) $string, $delimiter );

		return $escapeRegex[ $string ];
	}

	/**
	 * Escapes special regex characters inside the replacement string.
	 *
	 * @since 4.0.7
	 *
	 * @param  string $string The string.
	 * @return string         The escaped string.
	 */
	public function escapeRegexReplacement( $string ) {
		static $escapeRegexReplacement = [];
		if ( isset( $escapeRegexReplacement[ $string ] ) ) {
			return $escapeRegexReplacement[ $string ];
		}

		$escapeRegexReplacement[ $string ] = str_replace( '$', '\$', $string );

		return $escapeRegexReplacement[ $string ];
	}

	/**
	 * preg_replace but with the replacement escaped.
	 *
	 * @since 4.0.10
	 *
	 * @param  string $pattern     The pattern to search for.
	 * @param  string $replacement The replacement string.
	 * @param  string $subject     The subject to search in.
	 * @return string              The subject with matches replaced.
	 */
	public function pregReplace( $pattern, $replacement, $subject ) {
		if ( ! $subject ) {
			return $subject;
		}

		$key = $pattern . $replacement . $subject;

		static $pregReplace = [];
		if ( isset( $pregReplace[ $key ] ) ) {
			return $pregReplace[ $key ];
		}

		// TODO: In the future, we should consider escaping the search pattern as well.
		// We can use the following pattern for this - (?<!\\)([\/.^$*+?|()[{}\]]{1})
		// The pattern above will only escape special characters if they're not escaped yet, which makes it compatible with all our patterns that are already escaped.
		// The caveat is that we'd need to first trim off slash delimiters and add them back later - otherwise they'd be escaped as well.

		$replacement         = $this->escapeRegexReplacement( $replacement );
		$pregReplace[ $key ] = preg_replace( $pattern, $replacement, (string) $subject );

		return $pregReplace[ $key ];
	}

	/**
	 * Returns string after converting it to lowercase.
	 *
	 * @since 4.0.13
	 *
	 * @param  string $string The original string.
	 * @return string         The string converted to lowercase.
	 */
	public function toLowerCase( $string ) {
		static $lowerCased = [];
		if ( isset( $lowerCased[ $string ] ) ) {
			return $lowerCased[ $string ];
		}
		$lowerCased[ $string ] = function_exists( 'mb_strtolower' ) ? mb_strtolower( $string, $this->getCharset() ) : strtolower( $string );

		return $lowerCased[ $string ];
	}

	/**
	 * Returns the index of a substring in a string.
	 *
	 * @since 4.1.6
	 *
	 * @param  string   $stack  The stack.
	 * @param  string   $needle The needle.
	 * @param  int      $offset The offset.
	 * @return int|bool         The index where the string starts or false if it does not exist.
	 */
	public function stringIndex( $stack, $needle, $offset = 0 ) {
		$key = $stack . $needle . $offset;

		static $stringIndex = [];
		if ( isset( $stringIndex[ $key ] ) ) {
			return $stringIndex[ $key ];
		}

		$stringIndex[ $key ] = function_exists( 'mb_strpos' ) ? mb_strpos( $stack, $needle, $offset, $this->getCharset() ) : strpos( $stack, $needle, $offset );

		return $stringIndex[ $key ];
	}

	/**
	 * Checks if the given string contains the given substring.
	 *
	 * @since 4.1.0.2
	 *
	 * @param  string   $stack  The stack.
	 * @param  string   $needle The needle.
	 * @param  int      $offset The offset.
	 * @return bool             Whether the substring occurs in the main string.
	 */
	public function stringContains( $stack, $needle, $offset = 0 ) {
		$key = $stack . $needle . $offset;

		static $stringContains = [];
		if ( isset( $stringContains[ $key ] ) ) {
			return $stringContains[ $key ];
		}

		$stringContains[ $key ] = false !== $this->stringIndex( $stack, $needle, $offset );

		return $stringContains[ $key ];
	}

	/**
	 * Check if a string is JSON encoded or not.
	 *
	 * @since 4.1.2
	 *
	 * @param  mixed $string The string to check.
	 * @return bool          True if it is JSON or false if not.
	 */
	public function isJsonString( $string ) {
		if ( ! is_string( $string ) ) {
			return false;
		}

		json_decode( $string );

		// Return a boolean whether or not the last error matches.
		return json_last_error() === JSON_ERROR_NONE;
	}

	/**
	 * Strips punctuation from a given string.
	 *
	 * @since 4.0.0
	 * @version 4.7.9 Added the $keepSpaces parameter.
	 *
	 * @param  string $string           The string.
	 * @param  array  $charactersToKeep The characters that can't be stripped (optional).
	 * @param  bool   $keepSpaces       Whether to keep spaces.
	 * @return string                   The string without punctuation.
	 */
	public function stripPunctuation( $string, $charactersToKeep = [], $keepSpaces = false ) {
		$characterRegexPattern = '';
		if ( ! empty( $charactersToKeep ) ) {
			$characterString       = implode( '', $charactersToKeep );
			$characterRegexPattern = "(?![$characterString])";
		}

		$string = aioseo()->helpers->decodeHtmlEntities( (string) $string );
		$string = preg_replace( "/{$characterRegexPattern}[\p{P}\d+]/u", '', $string );
		$string = aioseo()->helpers->encodeOutputHtml( $string );

		// Trim both internal and external whitespace.
		return $keepSpaces ? $string : preg_replace( '/\s\s+/u', ' ', trim( $string ) );
	}

	/**
	 * Returns the string after it is encoded with htmlspecialchars().
	 *
	 * @since 4.0.0
	 *
	 * @param  string $string The string to encode.
	 * @return string         The encoded string.
	 */
	public function encodeOutputHtml( $string ) {
		if ( ! is_string( $string ) ) {
			return '';
		}

		return htmlspecialchars( $string, ENT_COMPAT | ENT_HTML401, $this->getCharset(), false );
	}

	/**
	 * Returns the string after all HTML entities have been decoded.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $string The string to decode.
	 * @return string         The decoded string.
	 */
	public function decodeHtmlEntities( $string ) {
		static $decodeHtmlEntities = [];
		if ( isset( $decodeHtmlEntities[ $string ] ) ) {
			return $decodeHtmlEntities[ $string ];
		}

		// We must manually decode non-breaking spaces since html_entity_decode doesn't do this.
		$string                        = $this->pregReplace( '/&nbsp;/', ' ', $string );
		$decodeHtmlEntities[ $string ] = html_entity_decode( (string) $string, ENT_QUOTES );

		return $decodeHtmlEntities[ $string ];
	}

	/**
	 * Returns the string with script tags stripped.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $string The string.
	 * @return string         The modified string.
	 */
	public function stripScriptTags( $string ) {
		static $stripScriptTags = [];
		if ( isset( $stripScriptTags[ $string ] ) ) {
			return $stripScriptTags[ $string ];
		}

		$stripScriptTags[ $string ] = $this->pregReplace( '/<script(.*?)>(.*?)<\/script>/is', '', $string );

		return $stripScriptTags[ $string ];
	}

	/**
	 * Returns the string with incomplete HTML tags stripped.
	 * Incomplete tags are not unopened/unclosed pairs but rather single tags that aren't properly formed.
	 * e.g. <a href='something'
	 * e.g. href='something' >
	 *
	 * @since 4.1.6
	 *
	 * @param  string $string The string.
	 * @return string         The modified string.
	 */
	public function stripIncompleteHtmlTags( $string ) {
		static $stripIncompleteHtmlTags = [];
		if ( isset( $stripIncompleteHtmlTags[ $string ] ) ) {
			return $stripIncompleteHtmlTags[ $string ];
		}

		$stripIncompleteHtmlTags[ $string ] = $this->pregReplace( '/(^(?!<).*?(\/>)|<[^>]*?(?!\/>)$)/is', '', $string );

		return $stripIncompleteHtmlTags[ $string ];
	}


	/**
	 * Returns the given JSON formatted data tags as a comma separated list with their values instead.
	 *
	 * @since 4.1.0
	 *
	 * @param  string|array $tags The Array or JSON formatted data tags.
	 * @return string             The comma separated values.
	 */
	public function jsonTagsToCommaSeparatedList( $tags ) {
		$tags = is_string( $tags ) ? json_decode( $tags ) : $tags;

		$values = [];
		foreach ( $tags as $k => $tag ) {
			$values[ $k ] = is_object( $tag ) ? $tag->value : $tag['value'];
		}

		return implode( ',', $values );
	}

	/**
	 * Returns the character length of the given string.
	 *
	 * @since 4.1.6
	 *
	 * @param  string $string The string.
	 * @return int            The string length.
	 */
	public function stringLength( $string ) {
		static $stringLength = [];
		if ( isset( $stringLength[ $string ] ) ) {
			return $stringLength[ $string ];
		}

		$stringLength[ $string ] = function_exists( 'mb_strlen' ) ? mb_strlen( $string, $this->getCharset() ) : strlen( $string );

		return $stringLength[ $string ];
	}

	/**
	 * Returns the word count of the given string.
	 *
	 * @since 4.1.6
	 *
	 * @param  string $string The string.
	 * @return int            The word count.
	 */
	public function stringWordCount( $string ) {
		static $stringWordCount = [];
		if ( isset( $stringWordCount[ $string ] ) ) {
			return $stringWordCount[ $string ];
		}

		$stringWordCount[ $string ] = str_word_count( $string );

		return $stringWordCount[ $string ];
	}

	/**
	 * Explodes the given string into an array.
	 *
	 * @since 4.1.6
	 *
	 * @param  string $delimiter The delimiter.
	 * @param  string $string    The string.
	 * @return array             The exploded words.
	 */
	public function explode( $delimiter, $string ) {
		$key = $delimiter . $string;

		static $exploded = [];
		if ( isset( $exploded[ $key ] ) ) {
			return $exploded[ $key ];
		}

		$exploded[ $key ] = explode( $delimiter, $string );

		return $exploded[ $key ];
	}

	/**
	 * Implodes an array into a WHEREIN clause useable string.
	 *
	 * @since 4.1.6
	 *
	 * @param  array  $array       The array.
	 * @param  bool   $outerQuotes Whether outer quotes should be added.
	 * @return string              The imploded array.
	 */
	public function implodeWhereIn( $array, $outerQuotes = false ) {
		// Reset the keys first in case there is no 0 index.
		$array = array_values( $array );

		if ( ! isset( $array[0] ) ) {
			return '';
		}

		if ( is_numeric( $array[0] ) ) {
			return implode( ', ', $array );
		}

		return $outerQuotes ? "'" . implode( "', '", $array ) . "'" : implode( "', '", $array );
	}

	/**
	 * Returns an imploded string of placeholders for usage in a WPDB prepare statement.
	 *
	 * @since 4.1.9
	 *
	 * @param  array  $array       The array.
	 * @param  string $placeholder The placeholder (e.g. "%s" or "%d").
	 * @return string              The imploded string with placeholders.
	 */
	public function implodePlaceholders( $array, $placeholder = '%s' ) {
		return implode( ', ', array_fill( 0, count( $array ), $placeholder ) );
	}

	/**
	 * Verifies that a string is indeed a valid regular expression.
	 *
	 * @since 4.2.1
	 *
	 * @return boolean True if the string is a valid regular expression.
	 */
	public function isValidRegex( $pattern ) {
		// Set a custom error handler to prevent throwing errors on a bad Regular Expression.
		set_error_handler( function() {}, E_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler

		$isValid = true;

		if ( false === preg_match( $pattern, '' ) ) {
			$isValid = false;
		}

		// Restore the error handler.
		restore_error_handler();

		return $isValid;
	}

	/**
	 * Removes the leading slash(es) from a string.
	 *
	 * @since 4.2.3
	 *
	 * @param  string $string The string.
	 * @return string         The modified string.
	 */
	public function unleadingSlashIt( $string ) {
		return ltrim( $string, '/' );
	}

	/**
	 * Convert the case of the given string.
	 *
	 * @since 4.2.4
	 *
	 * @param  string $string The string.
	 * @param  string $type   The casing ("lower", "title", "sentence").
	 * @return string         The converted string.
	 */
	public function convertCase( $string, $type ) {
		switch ( $type ) {
			case 'lower':
				return strtolower( $string );
			case 'title':
				return $this->toTitleCase( $string );
			case 'sentence':
				return $this->toSentenceCase( $string );
			default:
				return $string;
		}
	}

	/**
	 * Converts the given string to title case.
	 *
	 * @since 4.2.4
	 *
	 * @param  string $string The string.
	 * @return string         The converted string.
	 */
	public function toTitleCase( $string ) {
		// List of common English words that aren't typically modified.
		$exceptions = apply_filters( 'aioseo_title_case_exceptions', [
			'of',
			'a',
			'the',
			'and',
			'an',
			'or',
			'nor',
			'but',
			'is',
			'if',
			'then',
			'else',
			'when',
			'at',
			'from',
			'by',
			'on',
			'off',
			'for',
			'in',
			'out',
			'over',
			'to',
			'into',
			'with'
		] );

		$words = explode( ' ', strtolower( $string ) );

		foreach ( $words as $k => $word ) {
			if ( ! in_array( $word, $exceptions, true ) ) {
				$words[ $k ] = ucfirst( $word );
			}
		}

		$string = implode( ' ', $words );

		return $string;
	}

	/**
	 * Converts the given string to sentence case.
	 *
	 * @since 4.2.4
	 *
	 * @param  string $string The string.
	 * @return string         The converted string.
	 */
	public function toSentenceCase( $string ) {
		$phrases = preg_split( '/([.?!]+)/', (string) $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );

		$convertedString = '';
		foreach ( $phrases as $index => $sentence ) {
			$convertedString .= ( $index & 1 ) === 0 ? ucfirst( strtolower( trim( $sentence ) ) ) : $sentence . ' ';
		}

		return trim( $convertedString );
	}

	/**
	 * Returns the substring with a given start index and length.
	 *
	 * @since 4.2.5
	 *
	 * @param  string $string     The string.
	 * @param  int    $startIndex The start index.
	 * @param  int    $length     The length.
	 * @return string             The substring.
	 */
	public function substring( $string, $startIndex, $length ) {
		return function_exists( 'mb_substr' ) ? mb_substr( $string, $startIndex, $length, $this->getCharset() ) : substr( $string, $startIndex, $length );
	}

	/**
	 * Strips emoji characters from a given string.
	 *
	 * @since 4.7.3
	 *
	 * @param  string $string The string.
	 * @return string         The string without emoji characters.
	 */
	public function stripEmoji( $string ) {
		// First, decode HTML entities to convert them to actual Unicode characters.
		$string = $this->decodeHtmlEntities( $string );

		// Pattern to match emoji characters.
		$emojiPattern = '/[\x{1F600}-\x{1F64F}' . // Emoticons
						'\x{1F300}-\x{1F5FF}' . // Misc Symbols and Pictographs
						'\x{1F680}-\x{1F6FF}' . // Transport and Map Symbols
						'\x{1F1E0}-\x{1F1FF}' . // Flags (iOS)
						'\x{2600}-\x{26FF}' . // Misc symbols
						'\x{2700}-\x{27BF}' . // Dingbats
						'\x{FE00}-\x{FE0F}' . // Variation Selectors
						'\x{1F900}-\x{1F9FF}' . // Supplemental Symbols and Pictographs
						']/u';

		$filteredString = preg_replace( $emojiPattern, '', (string) $string );

		// Re-encode special characters to HTML entities.
		return $this->encodeOutputHtml( $filteredString );
	}

	/**
	 * Creates a sha1 hash from the given arguments.
	 *
	 * @since 4.7.8
	 *
	 * @param  mixed  ...$args The arguments to create a sha1 hash from.
	 * @return string          The sha1 hash.
	 */
	public function createHash( ...$args ) {
		return sha1( wp_json_encode( $args ) );
	}

	/**
	 * Extracts URLs from a given string.
	 *
	 * @since 4.8.1
	 *
	 * @param  string $string The string.
	 * @return array          The extracted URLs.
	 */
	public function extractUrls( $string ) {
		$urls = wp_extract_urls( $string );

		if ( empty( $urls ) ) {
			return [];
		}

		$allUrls = [];

		// Attempt to split multiple URLs. Elementor does not always separate them properly.
		foreach ( $urls as $url ) {
			$splitUrls = preg_split( '/(?=https?:\/\/)/', $url, - 1, PREG_SPLIT_NO_EMPTY );
			$allUrls   = array_merge( $allUrls, $splitUrls );
		}

		return $allUrls;
	}

	/**
	 * Determines if a text string contains an emoji or not.
	 *
	 * @since 4.8.0
	 *
	 * @param  string $string The text string to detect emoji in.
	 * @return bool
	 */
	public function hasEmojis( $string ) {
		$emojisRegexPattern = '/[\x{1F600}-\x{1F64F}' . // Emoticons
							'\x{1F300}-\x{1F5FF}' . // Misc Symbols and Pictographs
							'\x{1F680}-\x{1F6FF}' . // Transport and Map Symbols
							'\x{1F1E0}-\x{1F1FF}' . // Flags (iOS)
							'\x{2600}-\x{26FF}' . // Misc symbols
							'\x{2700}-\x{27BF}' . // Dingbats
							'\x{FE00}-\x{FE0F}' . // Variation Selectors
							'\x{1F900}-\x{1F9FF}' . // Supplemental Symbols and Pictographs
							'\x{1F018}-\x{1F270}' . // Various Asian characters
							'\x{238C}-\x{2454}' . // Misc items
							'\x{20D0}-\x{20FF}' . // Combining Diacritical Marks for Symbols
							']/u';

		return preg_match( $emojisRegexPattern, $string );
	}
}Constants.php000066600000025544151135523120007245 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains constant specific helper methods.
 *
 * @since 4.0.17
 */
trait Constants {
	/**
	 * Returns the All in One SEO Logo
	 *
	 * @since 4.0.0
	 *
	 * @param  string $width     The width of the image.
	 * @param  string $height    The height of the image.
	 * @param  string $colorCode The color of the image.
	 * @return string            The logo as a string.
	 */
	public function logo( $width, $height, $colorCode ) {
		return '<svg viewBox="0 0 20 20" width="' . $width . '" height="' . $height . '" fill="none" xmlns="http://www.w3.org/2000/svg" class="aioseo-gear"><path fill-rule="evenodd" clip-rule="evenodd" d="M9.98542 19.9708C15.5002 19.9708 19.9708 15.5002 19.9708 9.98542C19.9708 4.47063 15.5002 0 9.98542 0C4.47063 0 0 4.47063 0 9.98542C0 15.5002 4.47063 19.9708 9.98542 19.9708ZM8.39541 3.65464C8.26016 3.4485 8.0096 3.35211 7.77985 3.43327C7.51816 3.52572 7.26218 3.63445 7.01349 3.7588C6.79519 3.86796 6.68566 4.11731 6.73372 4.36049L6.90493 5.22694C6.949 5.44996 6.858 5.6763 6.68522 5.82009C6.41216 6.04734 6.16007 6.30426 5.93421 6.58864C5.79383 6.76539 5.57233 6.85907 5.35361 6.81489L4.50424 6.6433C4.26564 6.5951 4.02157 6.70788 3.91544 6.93121C3.85549 7.05738 3.79889 7.1862 3.74583 7.31758C3.69276 7.44896 3.64397 7.58105 3.59938 7.71369C3.52048 7.94847 3.61579 8.20398 3.81839 8.34133L4.53958 8.83027C4.72529 8.95617 4.81778 9.1819 4.79534 9.40826C4.75925 9.77244 4.76072 10.136 4.79756 10.4936C4.82087 10.7198 4.72915 10.9459 4.54388 11.0724L3.82408 11.5642C3.62205 11.7022 3.52759 11.9579 3.60713 12.1923C3.69774 12.4593 3.8043 12.7205 3.92615 12.9743C4.03313 13.1971 4.27749 13.3088 4.51581 13.2598L5.36495 13.0851C5.5835 13.0401 5.80533 13.133 5.94623 13.3093C6.16893 13.5879 6.42071 13.8451 6.6994 14.0756C6.87261 14.2188 6.96442 14.4448 6.92112 14.668L6.75296 15.5348C6.70572 15.7782 6.81625 16.0273 7.03511 16.1356C7.15876 16.1967 7.285 16.2545 7.41375 16.3086C7.54251 16.3628 7.67196 16.4126 7.80195 16.4581C8.18224 16.5912 8.71449 16.1147 9.108 15.7625C9.30205 15.5888 9.42174 15.343 9.42301 15.0798C9.42301 15.0784 9.42302 15.077 9.42302 15.0756L9.42301 13.6263C9.42301 13.6109 9.4236 13.5957 9.42476 13.5806C8.26248 13.2971 7.39838 12.2301 7.39838 10.9572V9.41823C7.39838 9.30125 7.49131 9.20642 7.60596 9.20642H8.32584V7.6922C8.32584 7.48312 8.49193 7.31364 8.69683 7.31364C8.90171 7.31364 9.06781 7.48312 9.06781 7.6922V9.20642H11.0155V7.6922C11.0155 7.48312 11.1816 7.31364 11.3865 7.31364C11.5914 7.31364 11.7575 7.48312 11.7575 7.6922V9.20642H12.4773C12.592 9.20642 12.6849 9.30125 12.6849 9.41823V10.9572C12.6849 12.2704 11.7653 13.3643 10.5474 13.6051C10.5477 13.6121 10.5478 13.6192 10.5478 13.6263L10.5478 15.0694C10.5478 15.3377 10.6711 15.5879 10.871 15.7622C11.2715 16.1115 11.8129 16.5837 12.191 16.4502C12.4527 16.3577 12.7086 16.249 12.9573 16.1246C13.1756 16.0155 13.2852 15.7661 13.2371 15.5229L13.0659 14.6565C13.0218 14.4334 13.1128 14.2071 13.2856 14.0633C13.5587 13.8361 13.8107 13.5792 14.0366 13.2948C14.177 13.118 14.3985 13.0244 14.6172 13.0685L15.4666 13.2401C15.7052 13.2883 15.9493 13.1756 16.0554 12.9522C16.1153 12.8261 16.1719 12.6972 16.225 12.5659C16.2781 12.4345 16.3269 12.3024 16.3714 12.1698C16.4503 11.935 16.355 11.6795 16.1524 11.5421L15.4312 11.0532C15.2455 10.9273 15.153 10.7015 15.1755 10.4752C15.2116 10.111 15.2101 9.74744 15.1733 9.38986C15.1499 9.16361 15.2417 8.93757 15.4269 8.811L16.1467 8.31927C16.3488 8.18126 16.4432 7.92558 16.3637 7.69115C16.2731 7.42411 16.1665 7.16292 16.0447 6.90915C15.9377 6.68638 15.6933 6.57462 15.455 6.62366L14.6059 6.79837C14.3873 6.84334 14.1655 6.75048 14.0246 6.57418C13.8019 6.29554 13.5501 6.03832 13.2714 5.80784C13.0982 5.6646 13.0064 5.43858 13.0497 5.2154L13.2179 4.34868C13.2651 4.10521 13.1546 3.85616 12.9357 3.74787C12.8121 3.68669 12.6858 3.62895 12.5571 3.5748C12.4283 3.52065 12.2989 3.47086 12.1689 3.42537C11.9388 3.34485 11.6884 3.44211 11.5538 3.64884L11.0746 4.38475C10.9513 4.57425 10.73 4.66862 10.5082 4.64573C10.1513 4.6089 9.79502 4.61039 9.44459 4.64799C9.22286 4.67177 9.00134 4.57818 8.87731 4.38913L8.39541 3.65464Z" fill="' . $colorCode . '" /></svg>'; // phpcs:ignore Generic.Files.LineLength.MaxExceeded
	}

	/**
	 * Returns the country name by code.
	 *
	 * @since 4.0.17
	 *
	 * @param  string $countryCode The country code.
	 * @return string              Country name.
	 */
	public function getCountryName( $countryCode ) {
		return isset( $this->countryList()[ $countryCode ] ) ? $this->countryList()[ $countryCode ] : '';
	}

	/**
	 * Returns a list of countries.
	 *
	 * @since 4.0.17
	 *
	 * @return array A list of countries.
	 */
	public function countryList() {
		return [
			'AF' => 'Afghanistan',
			'AL' => 'Albania',
			'DZ' => 'Algeria',
			'AS' => 'American Samoa',
			'AD' => 'Andorra',
			'AO' => 'Angola',
			'AI' => 'Anguilla',
			'AQ' => 'Antarctica',
			'AG' => 'Antigua and Barbuda',
			'AR' => 'Argentina',
			'AM' => 'Armenia',
			'AW' => 'Aruba',
			'AU' => 'Australia',
			'AT' => 'Austria',
			'AZ' => 'Azerbaijan',
			'BS' => 'Bahamas',
			'BH' => 'Bahrain',
			'BD' => 'Bangladesh',
			'BB' => 'Barbados',
			'BY' => 'Belarus',
			'BE' => 'Belgium',
			'BZ' => 'Belize',
			'BJ' => 'Benin',
			'BM' => 'Bermuda',
			'BT' => 'Bhutan',
			'BO' => 'Bolivia',
			'BQ' => 'Bonaire',
			'BA' => 'Bosnia and Herzegovina',
			'BW' => 'Botswana',
			'BV' => 'Bouvet Island',
			'BR' => 'Brazil',
			'IO' => 'British Indian Ocean Territory',
			'BN' => 'Brunei Darussalam',
			'BG' => 'Bulgaria',
			'BF' => 'Burkina Faso',
			'BI' => 'Burundi',
			'CV' => 'Cabo Verde',
			'KH' => 'Cambodia',
			'CM' => 'Cameroon',
			'CA' => 'Canada',
			'KY' => 'Cayman Islands',
			'CF' => 'Central African Republic',
			'TD' => 'Chad',
			'CL' => 'Chile',
			'CN' => 'China',
			'CX' => 'Christmas Island',
			'CC' => 'Cocos (Keeling) Islands',
			'CO' => 'Colombia',
			'KM' => 'Comoros',
			'CD' => 'Democratic Republic of the Congo',
			'CG' => 'Congo',
			'CK' => 'Cook Islands',
			'CR' => 'Costa Rica',
			'HR' => 'Croatia',
			'CU' => 'Cuba',
			'CW' => 'Curaçao',
			'CY' => 'Cyprus',
			'CZ' => 'Czechia',
			'CI' => 'Côte d\'Ivoire',
			'DK' => 'Denmark',
			'DJ' => 'Djibouti',
			'DM' => 'Dominica',
			'DO' => 'Dominican Republic',
			'EC' => 'Ecuador',
			'EG' => 'Egypt',
			'SV' => 'El Salvador',
			'GQ' => 'Equatorial Guinea',
			'ER' => 'Eritrea',
			'EE' => 'Estonia',
			'SZ' => 'Eswatini',
			'ET' => 'Ethiopia',
			'FK' => 'Falkland Islands',
			'FO' => 'Faroe Islands',
			'FJ' => 'Fiji',
			'FI' => 'Finland',
			'FR' => 'France',
			'GF' => 'French Guiana',
			'PF' => 'French Polynesia',
			'TF' => 'French Southern Territories',
			'GA' => 'Gabon',
			'GM' => 'Gambia',
			'GE' => 'Georgia',
			'DE' => 'Germany',
			'GH' => 'Ghana',
			'GI' => 'Gibraltar',
			'GR' => 'Greece',
			'GL' => 'Greenland',
			'GD' => 'Grenada',
			'GP' => 'Guadeloupe',
			'GU' => 'Guam',
			'GT' => 'Guatemala',
			'GG' => 'Guernsey',
			'GN' => 'Guinea',
			'GW' => 'Guinea-Bissau',
			'GY' => 'Guyana',
			'HT' => 'Haiti',
			'HM' => 'Heard Island and McDonald Islands',
			'VA' => 'Holy See',
			'HN' => 'Honduras',
			'HK' => 'Hong Kong',
			'HU' => 'Hungary',
			'IS' => 'Iceland',
			'IN' => 'India',
			'ID' => 'Indonesia',
			'IR' => 'Iran',
			'IQ' => 'Iraq',
			'IE' => 'Ireland',
			'IM' => 'Isle of Man',
			'IL' => 'Israel',
			'IT' => 'Italy',
			'JM' => 'Jamaica',
			'JP' => 'Japan',
			'JE' => 'Jersey',
			'JO' => 'Jordan',
			'KZ' => 'Kazakhstan',
			'KE' => 'Kenya',
			'KI' => 'Kiribati',
			'KR' => 'South Korea',
			'KW' => 'Kuwait',
			'KG' => 'Kyrgyzstan',
			'LA' => 'Lao People\'s Democratic Republic',
			'LV' => 'Latvia',
			'LB' => 'Lebanon',
			'LS' => 'Lesotho',
			'LR' => 'Liberia',
			'LY' => 'Libya',
			'LI' => 'Liechtenstein',
			'LT' => 'Lithuania',
			'LU' => 'Luxembourg',
			'MO' => 'Macao',
			'MG' => 'Madagascar',
			'MW' => 'Malawi',
			'MY' => 'Malaysia',
			'MV' => 'Maldives',
			'ML' => 'Mali',
			'MT' => 'Malta',
			'MH' => 'Marshall Islands',
			'MQ' => 'Martinique',
			'MR' => 'Mauritania',
			'MU' => 'Mauritius',
			'YT' => 'Mayotte',
			'MX' => 'Mexico',
			'FM' => 'Micronesia',
			'MD' => 'Moldova',
			'MC' => 'Monaco',
			'MN' => 'Mongolia',
			'ME' => 'Montenegro',
			'MS' => 'Montserrat',
			'MA' => 'Morocco',
			'MZ' => 'Mozambique',
			'MM' => 'Myanmar',
			'NA' => 'Namibia',
			'NR' => 'Nauru',
			'NP' => 'Nepal',
			'NL' => 'Netherlands',
			'NC' => 'New Caledonia',
			'NZ' => 'New Zealand',
			'NI' => 'Nicaragua',
			'NE' => 'Niger',
			'NG' => 'Nigeria',
			'NU' => 'Niue',
			'NF' => 'Norfolk Island',
			'MP' => 'Northern Mariana Islands',
			'NO' => 'Norway',
			'OM' => 'Oman',
			'PK' => 'Pakistan',
			'PW' => 'Palau',
			'PS' => 'Palestine, State of',
			'PA' => 'Panama',
			'PG' => 'Papua New Guinea',
			'PY' => 'Paraguay',
			'PE' => 'Peru',
			'PH' => 'Philippines',
			'PN' => 'Pitcairn',
			'PL' => 'Poland',
			'PT' => 'Portugal',
			'PR' => 'Puerto Rico',
			'QA' => 'Qatar',
			'MK' => 'Republic of North Macedonia',
			'RO' => 'Romania',
			'RU' => 'Russian Federation',
			'RW' => 'Rwanda',
			'RE' => 'Réunion',
			'BL' => 'Saint Barthélemy',
			'SH' => 'Saint Helena, Ascension and Tristan da Cunha',
			'KN' => 'Saint Kitts and Nevis',
			'LC' => 'Saint Lucia',
			'MF' => 'Saint Martin',
			'PM' => 'Saint Pierre and Miquelon',
			'VC' => 'Saint Vincent and the Grenadines',
			'WS' => 'Samoa',
			'SM' => 'San Marino',
			'ST' => 'Sao Tome and Principe',
			'SA' => 'Saudi Arabia',
			'SN' => 'Senegal',
			'RS' => 'Serbia',
			'SC' => 'Seychelles',
			'SL' => 'Sierra Leone',
			'SG' => 'Singapore',
			'SX' => 'Sint Maarten',
			'SK' => 'Slovakia',
			'SI' => 'Slovenia',
			'SB' => 'Solomon Islands',
			'SO' => 'Somalia',
			'ZA' => 'South Africa',
			'GS' => 'South Georgia and the South Sandwich Islands',
			'SS' => 'South Sudan',
			'ES' => 'Spain',
			'LK' => 'Sri Lanka',
			'SD' => 'Sudan',
			'SR' => 'Suriname',
			'SJ' => 'Svalbard and Jan Mayen',
			'SE' => 'Sweden',
			'CH' => 'Switzerland',
			'SY' => 'Syrian Arab Republic',
			'TW' => 'Taiwan',
			'TJ' => 'Tajikistan',
			'TZ' => 'Tanzania, United Republic of',
			'TH' => 'Thailand',
			'TL' => 'Timor-Leste',
			'TG' => 'Togo',
			'TK' => 'Tokelau',
			'TO' => 'Tonga',
			'TT' => 'Trinidad and Tobago',
			'TN' => 'Tunisia',
			'TR' => 'Turkey',
			'TM' => 'Turkmenistan',
			'TC' => 'Turks and Caicos Islands',
			'TV' => 'Tuvalu',
			'UG' => 'Uganda',
			'UA' => 'Ukraine',
			'AE' => 'United Arab Emirates',
			'GB' => 'United Kingdom of Great Britain and Northern Ireland',
			'UM' => 'United States Minor Outlying Islands',
			'US' => 'United States of America',
			'UY' => 'Uruguay',
			'UZ' => 'Uzbekistan',
			'VU' => 'Vanuatu',
			'VE' => 'Venezuela',
			'VN' => 'Vietnam',
			'VG' => 'Virgin Islands (British)',
			'VI' => 'Virgin Islands (U.S.)',
			'WF' => 'Wallis and Futuna',
			'EH' => 'Western Sahara',
			'YE' => 'Yemen',
			'ZM' => 'Zambia',
			'ZW' => 'Zimbabwe',
			'AX' => 'Åland Islands'
		];
	}
}Arrays.php000066600000020052151135523120006517 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains array specific helper methods.
 *
 * @since 4.1.4
 */
trait Arrays {
	/**
	 * Unsets a given value in a given array.
	 * This should only be used if the given value only appears once in the array.
	 *
	 * @since 4.0.0
	 *
	 * @param  array  $array The array.
	 * @param  string $value The value that needs to be removed from the array.
	 * @return array  $array The filtered array.
	 */
	public function unsetValue( $array, $value ) {
		if ( in_array( $value, $array, true ) ) {
			unset( $array[ array_search( $value, $array, true ) ] );
		}

		return $array;
	}

	/**
	 * Compares two multidimensional arrays to see if they're different.
	 *
	 * @since 4.0.0
	 *
	 * @param  array   $array1 The first array.
	 * @param  array   $array2 The second array.
	 * @return boolean         Whether the arrays are different.
	 */
	public function arraysDifferent( $array1, $array2 ) {
		foreach ( $array1 as $key => $value ) {
			// Check for non-existing values.
			if ( ! isset( $array2[ $key ] ) ) {
				return true;
			}
			if ( is_array( $value ) ) {
				if ( $this->arraysDifferent( $value, $array2[ $key ] ) ) {
					return true;
				}
			} else {
				if ( $value !== $array2[ $key ] ) {
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Checks whether the given array is associative.
	 * Arrays that only have consecutive, sequential numeric keys are numeric.
	 * Otherwise they are associative.
	 *
	 * @since 4.1.4
	 *
	 * @param  array $array The array.
	 * @return bool         Whether the array is associative.
	 */
	public function isArrayAssociative( $array ) {
		return 0 < count( array_filter( array_keys( $array ), 'is_string' ) );
	}

	/**
	 * Checks whether the given array is numeric.
	 *
	 * @since 4.1.4
	 *
	 * @param  array $array The array.
	 * @return bool         Whether the array is numeric.
	 */
	public function isArrayNumeric( $array ) {
		return ! $this->isArrayAssociative( $array );
	}

	/**
	 * Recursively replaces the values from one array with the ones from another.
	 * This function should act identical to the built-in array_replace_recursive(), with the exception that it also replaces array values with empty arrays.
	 *
	 * @since 4.2.4
	 *
	 * @param  array $targetArray      The target array
	 * @param  array $replacementArray The array with values to replace in the target array.
	 * @return array                   The modified array.
	 */
	public function arrayReplaceRecursive( $targetArray, $replacementArray ) {
		// In some cases the target array isn't an array yet (due to e.g. race conditions in InternalOptions), so in that case we can just return the replacement array.
		if ( ! is_array( $targetArray ) ) {
			return $replacementArray;
		}

		foreach ( $replacementArray as $k => $v ) {
			// If the key does not exist yet on the target array, add it.
			if ( ! isset( $targetArray[ $k ] ) ) {
				$targetArray[ $k ] = $replacementArray[ $k ];
				continue;
			}

			// If the value is an array, only try to recursively replace it if the value isn't empty.
			// Otherwise empty arrays will be ignored and won't override the existing value of the target array.
			if ( is_array( $v ) && ! empty( $v ) ) {
				$targetArray[ $k ] = $this->arrayReplaceRecursive( $targetArray[ $k ], $v );
				continue;
			}

			// Replace with non-array value or empty array.
			$targetArray[ $k ] = $v;
		}

		return $targetArray;
	}

	/**
	 * Recursively intersects the two given arrays.
	 * You can pass in an optional argument (allowedKey) to restrict the intersect to arrays with a specific key.
	 * This is needed when we are e.g. sanitizing array values before setting/saving them to an option.
	 * This helper method was mainly built to support our complex options architecture.
	 *
	 * @since 4.2.5
	 *
	 * @param  array  $array1     The first array.
	 * @param  array  $array2     The second array.
	 * @param  string $allowedKey The only key the method should run for (optional).
	 * @param  string $parentKey  The parent key.
	 * @return array              The intersected array.
	 */
	public function arrayIntersectRecursive( $array1, $array2, $allowedKey = '', $parentKey = '' ) {
		if ( ! $allowedKey || $allowedKey === $parentKey ) {
			$array1 = $this->arrayIntersectRecursiveHelper( $array1, $array2 );
		}

		if ( empty( $array1 ) ) {
			return [];
		}

		foreach ( $array1 as $k => $v ) {
			if ( is_array( $v ) && isset( $array2[ $k ] ) ) {
				$array1[ $k ] = $this->arrayIntersectRecursive( $array1[ $k ], $array2[ $k ], $allowedKey, $k );
			}
		}

		if ( $this->isArrayNumeric( $array1 ) ) {
			$array1 = array_values( $array1 );
		}

		return $array1;
	}

	/**
	 * Recursively intersects the two given arrays. Supports arrays with a mix of nested arrays and primitive values.
	 * Helper function for arrayIntersectRecursive().
	 *
	 * @since 4.5.4
	 *
	 * @param  array $array1 The first array.
	 * @param  array $array2 The second array.
	 * @return array         The intersected array.
	 */
	private function arrayIntersectRecursiveHelper( $array1, $array2 ) {
		if ( null === $array2 ) {
			$array2 = [];
		}

		if ( is_array( $array1 ) ) {
			// First, check with keys are nested arrays and which are primitive values.
			$arrays     = [];
			$primitives = [];
			foreach ( $array1 as $k => $v ) {
				if ( is_array( $v ) ) {
					$arrays[ $k ] = $v;
				} else {
					$primitives[ $k ] = $v;
				}
			}

			// Then, intersect the primitive values.
			$intersectedPrimitives = array_intersect_assoc( $primitives, $array2 );

			// Finally, recursively intersect the nested arrays.
			$intersectedArrays = [];
			foreach ( $arrays as $k => $v ) {
				if ( isset( $array2[ $k ] ) ) {
					$intersectedArrays[ $k ] = $this->arrayIntersectRecursiveHelper( $v, $array2[ $k ] );
				} else {
					// If the nested array doesn't exist in the second array, we can just unset it.
					unset( $arrays[ $k ] );
				}
			}

			// Merge the intersected arrays and primitive values.
			return array_merge( $intersectedPrimitives, $intersectedArrays );
		}

		return array_intersect_assoc( $array1, $array2 );
	}

	/**
	 * Sorts the keys of an array alphabetically.
	 * The array is passed by reference, so it's not returned the same as in `ksort()`.
	 *
	 * @since 4.4.0.3
	 *
	 * @param array $array The array to sort, passed by reference.
	 */
	public function arrayRecursiveKsort( &$array ) {
		foreach ( $array as &$value ) {
			if ( is_array( $value ) ) {
				$this->arrayRecursiveKsort( $value );
			}
		}

		ksort( $array );
	}

	/**
	 * Creates a multidimensional array from a list of keys and a value.
	 *
	 * @since 4.5.3
	 *
	 * @param  array $keys  The keys to create the array from.
	 * @param  mixed $value The value to assign to the last key.
	 * @param  array $array The array when recursing.
	 * @return array        The multidimensional array.
	 */
	public function createMultidimensionalArray( $keys, $value, $array = [] ) {
		$key = array_shift( $keys );
		if ( empty( $array[ $key ] ) ) {
			$array[ $key ] = null;
		}

		if ( 0 < count( $keys ) ) {
			$array[ $key ] = $this->createMultidimensionalArray( $keys, $value, $array[ $key ] );
		} else {
			$array[ $key ] = $value;
		}

		return $array;
	}

	/**
	 * Sorts an array of arrays by a specific key.
	 *
	 * @since 4.7.4
	 *
	 * @param  array  $arr   The input array.
	 * @param  string $key   The key to sort by.
	 * @param  string $order Designates ascending or descending order. Default 'asc'. Accepts 'asc', 'desc'.
	 * @return void
	 */
	public function usortByKey( &$arr, $key, $order = 'asc' ) {
		if ( empty( $arr ) || ! is_array( $arr ) ) {
			return;
		}

		usort( $arr, function ( $a, $b ) use ( $key, $order ) {
			return 'asc' === $order ? $a[ $key ] <=> $b[ $key ] : $b[ $key ] <=> $a[ $key ];
		} );
	}

	/**
	 * Flattens a multidimensional array.
	 *
	 * @since 4.7.6
	 *
	 * @param  array $arr The input array.
	 * @return array      The flattened array.
	 */
	public function flatten( $arr ) {
		$result = [];
		array_walk_recursive( $arr, function ( $value ) use ( &$result ) {
			$result[] = $value;
		} );

		return $result;
	}
}WpMultisite.php000066600000017264151135523120007557 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains methods related to multisite.
 *
 * @since 4.2.5
 */
trait WpMultisite {
	/**
	 * Returns the ID of the network's main site.
	 *
	 * @since 4.2.5
	 *
	 * @return int The ID of the network's main site.
	 */
	public function getNetworkId() {
		if ( is_multisite() ) {
			return get_network()->site_id;
		}

		return get_current_blog_id();
	}

	/**
	 * Get a site (with aliases) by it's blog ID.
	 *
	 * @since 4.2.5
	 *
	 * @param  int          $blogId The blog ID.
	 * @return \WP_Site|null         The site.
	 */
	public function getSiteByBlogId( $blogId ) {
		$sites = $this->getSites();
		foreach ( $sites['sites'] as $site ) {
			if ( $site->blog_id === $blogId ) {
				return $site;
			}
		}

		return null;
	}

	/**
	 * Get the current site.
	 *
	 * @since 4.2.5
	 *
	 * @return \WP_Site|object A WP_Site instance of the current site or an object representing the same.
	 */
	public function getSite() {
		if ( is_multisite() ) {
			return get_site();
		}

		return (object) [
			'domain' => $this->getSiteDomain( true ),
			'path'   => $this->getHomePath( true )
		];
	}

	/**
	 * Get all sites in the multisite network.
	 *
	 * @since 4.2.5
	 *
	 * @param  int|string  $limit      The number of sites to get or 'all'.
	 * @param  int         $offset     The offset to start at.
	 * @param  null|string $searchTerm The search term to look for.
	 * @param  null|string $filter     A filter to look up sites by.
	 * @param  null|string $orderBy    The column to order results by. Defaults to null.
	 * @param  string      $orderDir   The direction to order results by. Defaults to 'DESC'.
	 * @return array                   An array of sites.
	 */
	public function getSites( $limit = 'all', $offset = 0, $searchTerm = null, $filter = 'all', $orderBy = null, $orderDir = 'DESC' ) {
		$countSites = $this->countSites();
		$sites      = get_sites( [
			'network_id' => get_current_network_id(),
			'number'     => $countSites['public'],
			'public'     => 1
		] );

		$allSites = [];
		foreach ( $sites as $site ) {
			$clonedSite           = clone $site;
			$clonedSite->adminUrl = get_admin_url( $site->blog_id );
			$clonedSite->homeUrl  = get_home_url( $site->blog_id );

			if ( $this->includeSite( $clonedSite, $filter ) ) {
				$allSites[] = $clonedSite;
			}

			// We need to look up aliases for Mercator, this checks to see if it's even enabled.
			if ( ! class_exists( '\Mercator\Mapping' ) ) {
				continue;
			}

			$aliases = $this->getSiteAliases( $site );
			foreach ( $aliases as $alias ) {
				$aliasSite               = clone $clonedSite;
				$aliasSite->domain       = $alias['domain'];
				$aliasSite->path         = '/';
				$aliasSite->alias        = $alias;
				$aliasSite->parentDomain = $site->domain;
				$aliasSite->parentPath   = $site->path;

				if ( $this->includeSite( $aliasSite, $filter ) ) {
					$allSites[] = $aliasSite;
				}
			}
		}

		// If we have a search term, let's filter down these results.
		if ( ! empty( $searchTerm ) ) {
			foreach ( $allSites as $key => $site ) {
				$keep = false;
				if (
					false !== stripos( $site->domain, $searchTerm ) ||
					false !== stripos( $site->path, $searchTerm ) ||
					false !== stripos( $site->parentDomain, $searchTerm ) ||
					false !== stripos( $site->parentPath, $searchTerm )
				) {
					$keep = true;
				}

				if ( ! $keep ) {
					unset( $allSites[ $key ] );
				}
			}
		}

		// Ordering the sites.
		if ( ! empty( $orderBy ) ) {
			usort( $allSites, function( $site1, $site2 ) use ( $orderBy, $orderDir ) {
				if ( empty( $site1->{ $orderBy } ) ) {
					return 0;
				}

				return 'ASC' === strtoupper( $orderDir )
					? ( $site1->{ $orderBy } > $site2->{ $orderBy } ? 1 : 0 )
					: ( $site1->{ $orderBy } < $site2->{ $orderBy } ? 1 : 0 );
			} );
		}

		return [
			'total' => count( $allSites ),
			'limit' => $limit,
			'sites' => 'all' === $limit ? $allSites : array_slice( $allSites, $offset, $limit )
		];
	}

	/**
	 * Count the number of sites in the network. A clone of wp_count_sites. We use this because
	 * we don't yet support WordPress 5.3. Once we do, we can revert to wp_count_sites.
	 *
	 * @since 4.4.5
	 *
	 * @return array          An array of aliases.
	 */
	private function countSites() {
		$networkId = get_current_network_id();

		$counts = [];
		$args   = [
			'network_id'    => $networkId,
			'number'        => 1,
			'fields'        => 'ids',
			'no_found_rows' => false,
		];

		$q             = new \WP_Site_Query( $args );
		$counts['all'] = $q->found_sites;

		$_args    = $args;
		$statuses = [ 'public', 'archived', 'mature', 'spam', 'deleted' ];

		foreach ( $statuses as $status ) {
			$_args            = $args;
			$_args[ $status ] = 1;

			$q                 = new \WP_Site_Query( $_args );
			$counts[ $status ] = $q->found_sites;
		}

		return $counts;
	}

	/**
	 * Filter sites based on a passed in filter. Options include 'all', 'activated' or 'deactivated'.
	 *
	 * @since 4.2.5
	 *
	 * @param  Object $site   The site object.
	 * @param  string $filter The filter to use.
	 * @return bool           The site if allowed or null if not.
	 */
	private function includeSite( $site, $filter ) {
		if ( 'all' === $filter ) {
			return true;
		}

		$siteIsActive = aioseo()->networkLicense->isSiteActive( $site );
		if (
			( 'deactivated' === $filter && ! $siteIsActive ) ||
			( 'activated' === $filter && $siteIsActive )
		) {
			return true;
		}

		return false;
	}

	/**
	 * Get an array of aliases for a WP_Site.
	 *
	 * @since 4.2.5
	 *
	 * @param  \WP_Site $site The Site.
	 * @return array          An array of aliases.
	 */
	public function getSiteAliases( $site ) {
		// We need to look up aliases for Mercator, this checks to see if it's even enabled.
		if ( ! class_exists( '\Mercator\Mapping' ) ) {
			return [];
		}

		$aliases = \Mercator\Mapping::get_by_site( $site->blog_id );
		if ( empty( $aliases ) ) {
			return [];
		}

		$aliasData = [];
		foreach ( $aliases as $alias ) {
			$aliasData[] = [
				'alias_id' => $alias->get_id(),
				'domain'   => $alias->get_domain(),
				'active'   => $alias->is_active()
			];
		}

		return $aliasData;
	}

	/**
	 * Wrapper for switch_to_blog especially for non-multisite setups.
	 *
	 * @since 4.2.5
	 *
	 * @param  int  $blogId The blog ID to switch to.
	 * @return bool         Whether the blog was switched to or not.
	 */
	public function switchToBlog( $blogId ) {
		if ( ! is_multisite() ) {
			return false;
		}

		switch_to_blog( $blogId );

		aioseo()->core->db->init();

		return true;
	}

	/**
	 * Wrapper for restore_current_blog especially for non-multisite setups.
	 *
	 * @since 4.2.5
	 *
	 * @return bool Whether the blog was restored or not.
	 */
	public function restoreCurrentBlog() {
		if ( ! is_multisite() ) {
			return false;
		}

		restore_current_blog();

		aioseo()->core->db->init();

		return true;
	}

	/**
	 * Checks if the current plugin is network activated.
	 *
	 * @since 4.2.8
	 *
	 * @param  string|null $plugin The plugin to check for network activation.
	 * @return bool                True if network activated, false if not.
	 */
	public function isPluginNetworkActivated( $plugin = null ) {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';

		if ( ! is_multisite() ) {
			return false;
		}

		$plugin = $plugin ? $plugin : plugin_basename( AIOSEO_FILE );

		// If the plugin is not network activated, then no it's not network licensed.
		if ( ! is_plugin_active_for_network( $plugin ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Returns the current site domain.
	 *
	 * @since 4.7.7
	 *
	 * @return string The site domain.
	 */
	public function getMultiSiteDomain() {
		$site = aioseo()->helpers->getSite();

		return $site->domain . $site->path;
	}
}DateTime.php000066600000012035151135523120006754 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains date/time specific helper methods.
 *
 * @since 4.1.2
 */
trait DateTime {
	/**
	 * Formats a date in ISO8601 format.
	 *
	 * @since 4.1.2
	 *
	 * @param  string $date The date.
	 * @return string       The date formatted in ISO8601 format.
	 */
	public function dateToIso8601( $date ) {
		return date( 'Y-m-d', strtotime( $date ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
	}

	/**
	 * Formats a date & time in ISO8601 format.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $dateTime The date.
	 * @return string           The date formatted in ISO8601 format.
	 */
	public function dateTimeToIso8601( $dateTime ) {
		return date( 'c', strtotime( $dateTime ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
	}

	/**
	 * Formats a date & time in RFC-822 format.
	 *
	 * @since 4.2.1
	 *
	 * @param  string $dateTime The date.
	 * @return string           The date formatted in RFC-822 format.
	 */
	public function dateTimeToRfc822( $dateTime ) {
		return date( 'D, d M Y H:i:s O', strtotime( $dateTime ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
	}

	/**
	 * Retrieves the timezone offset in seconds.
	 *
	 * @since   4.0.0
	 * @version 4.7.2 Returns the actual timezone offset.
	 *
	 * @return int The timezone offset in seconds.
	 */
	public function getTimeZoneOffset() {
		try {
			$timezone = get_option( 'timezone_string' );
			if ( $timezone ) {
				$timezone_object = new \DateTimeZone( $timezone ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName

				return $timezone_object->getOffset( new \DateTime( 'now' ) ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			}
		} catch ( \Exception $e ) {
			// Do nothing.
		}

		return intval( get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS;
	}

	/**
	 * Formats an amount of days, hours and minutes in ISO8601 duration format.
	 * This is used in our JSON schema to adhere to Google's standards.
	 *
	 * @since 4.2.5
	 *
	 * @param  integer|string $days    The days.
	 * @param  integer|string $hours   The hours.
	 * @param  integer|string $minutes The minutes.
	 * @return string                  The days, hours and minutes formatted in ISO8601 duration format.
	 */
	public function timeToIso8601DurationFormat( $days, $hours, $minutes ) {
		$duration = 'P';
		if ( $days ) {
			$duration .= $days . 'D';
		}

		$duration .= 'T';
		if ( $hours ) {
			$duration .= $hours . 'H';
		}

		if ( $minutes ) {
			$duration .= $minutes . 'M';
		}

		return $duration;
	}

	/**
	 * Returns a MySQL formatted date.
	 *
	 * @since 4.1.5
	 *
	 * @param  int|string   $time Any format accepted by strtotime.
	 * @return false|string       The MySQL formatted string.
	 */
	public function timeToMysql( $time ) {
		$time = is_string( $time ) ? strtotime( $time ) : $time;

		return date( 'Y-m-d H:i:s', $time ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
	}

	/**
	 * Formats a date in WordPress format.
	 *
	 * @since 4.8.2
	 *
	 * @param  string      $dateTime          Same as you'd pass to `strtotime()`.
	 * @param  string      $dateTimeSeparator The separator between the date and time.
	 * @return string|null                    The date formatted in WordPress format. Null if the passed date is invalid.
	 */
	public function dateToWpFormat( $dateTime, $dateTimeSeparator = ', ' ) {
		static $format = null;
		if ( ! isset( $format ) ) {
			$dateFormat = get_option( 'date_format', 'd M' );
			$timeFormat = get_option( 'time_format', 'H:i' );
			$format     = $dateFormat . $dateTimeSeparator . $timeFormat;
		}

		$timestamp = strtotime( (string) $dateTime );

		return $timestamp && 0 < $timestamp ? date_i18n( $format, $timestamp ) : null;
	}

	/**
	 * Checks if a given string is a valid date.
	 *
	 * @since 4.8.3
	 *
	 * @param  string $date   The date string to check.
	 * @param  string $format The format of the date string.
	 * @return bool           True if the string is a valid date, false otherwise.
	 */
	public function isValidDate( $date, $format = null ) {
		if ( ! $date ) {
			return false;
		}

		if ( $format ) {
			$d = \DateTime::createFromFormat( $format, $date );

			return $d && $d->format( $format ) === $date;
		}

		$timestamp = strtotime( $date );

		return false !== $timestamp;
	}

	/**
	 * Generates a random (yet unique per identifier) time offset based on a site identifier.
	 *
	 * @since 4.7.9
	 *
	 * @param  string $identifier       Data such as the site URL, site ID, or a combination of both to serve as the seed for generating a random time offset.
	 * @param  int    $maxOffsetMinutes The range for the random offset in minutes.
	 * @return int                      The random (yet unique per identifier) time offset in minutes.
	 */
	public function generateRandomTimeOffset( $identifier, $maxOffsetMinutes ) {
		$hash = md5( strval( $identifier ) );

		// Convert part of the hash to an integer.
		$hashInteger = hexdec( substr( $hash, 0, 8 ) );

		return $hashInteger % $maxOffsetMinutes;
	}
}Buffer.php000066600000000605151135523120006471 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains buffer specific helper methods.
 *
 * @since 4.8.3
 */
trait Buffer {
	/**
	 * Clears all output buffers.
	 *
	 * @since 4.8.3
	 *
	 * @return void
	 */
	public function clearBuffers() {
		while ( ob_get_level() > 0 ) {
			ob_end_clean();
		}
	}
}WpUri.php000066600000040107151135523120006327 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use AIOSEO\Plugin\Common\Integrations\BuddyPress as BuddyPressIntegration;

/**
 * Contains all WordPress related URL, URI, path, slug, etc. related helper methods.
 *
 * @since 4.1.4
 */
trait WpUri {
	/**
	 * Returns the site domain.
	 *
	 * @since 4.0.0
	 *
	 * @param  bool   $unfiltered Whether to get the unfiltered value.
	 * @return string             The site's domain.
	 */
	public function getSiteDomain( $unfiltered = false ) {
		return wp_parse_url( $this->getHomeUrl( $unfiltered ), PHP_URL_HOST );
	}

	/**
	 * Returns the site URL.
	 * NOTE: For multisites inside a sub-directory, this returns the URL for the main site.
	 * This is intentional.
	 *
	 * @since 4.0.0
	 *
	 * @param  bool   $unfiltered Whether to get the unfiltered value.
	 * @return string             The site's domain.
	 */
	public function getSiteUrl( $unfiltered = false ) {
		$homeUrl = $this->getHomeUrl( $unfiltered );

		return wp_parse_url( $homeUrl, PHP_URL_SCHEME ) . '://' . wp_parse_url( $homeUrl, PHP_URL_HOST );
	}

	/**
	 * Returns the current URL.
	 *
	 * @since 4.0.0
	 *
	 * @param  boolean $canonical Whether or not to get the canonical URL.
	 * @return string             The URL.
	 */
	public function getUrl( $canonical = false ) {
		$url = '';
		if ( is_singular() ) {
			$objectId = aioseo()->helpers->getPostId();

			if ( $canonical ) {
				$url = aioseo()->helpers->wpGetCanonicalUrl( $objectId );
			}

			if ( ! $url ) {
				// wp_get_canonical_url() returns false if the post isn't published.
				// Therefore, we must to fall back to the permalink if the post isn't published, e.g. draft post or attachment (inherit).
				$url = get_permalink( $objectId );
			}
		}

		if ( $url ) {
			return $url;
		}

		global $wp;
		// Permalink url without the query string.
		$url = user_trailingslashit( home_url( $wp->request ) );

		// If permalinks are not being used we need to append the query string to the home url.
		if ( ! $this->usingPermalinks() ) {
			$url = home_url( ! empty( $wp->query_string ) ? '?' . $wp->query_string : '' );
		}

		return $url;
	}

	/**
	 * Gets the canonical URL for the current page/post.
	 *
	 * @since 4.0.0
	 *
	 * @return string $url The canonical URL.
	 */
	public function canonicalUrl() {
		$queriedObject = get_queried_object(); // Don't use our getTerm helper here.
		$hash          = md5( wp_json_encode( $queriedObject ?? [] ) );

		static $url = [];
		if ( isset( $url[ $hash ] ) ) {
			return $url[ $hash ];
		}

		if ( is_404() || is_search() ) {
			$url[ $hash ] = apply_filters( 'aioseo_canonical_url', '' );

			return $url[ $hash ];
		}

		$metaData = [];
		$post     = $this->getPost();
		if ( $post ) {
			$metaData = aioseo()->meta->metaData->getMetaData( $post );
		}

		if ( is_category() || is_tag() || is_tax() ) {
			$metaData     = aioseo()->meta->metaData->getMetaData( $queriedObject );
			$url[ $hash ] = get_term_link( $queriedObject, $queriedObject->taxonomy ?? '' );

			// If the term link is a WP_Error, set it to an empty string.
			if ( ! is_string( $url[ $hash ] ) ) {
				$url[ $hash ] = '';
			}

			// Add pagination to the URL. We need to do this here because get_term_link() doesn't handle pagination.
			// We'll strip it further down if no pagination for canonical is enabled.
			if ( $this->getPageNumber() > 1 ) {
				$url[ $hash ] = user_trailingslashit( rtrim( $url[ $hash ], '/' ) . '/page/' . $this->getPageNumber() );
			}
		}

		if ( $metaData && ! empty( $metaData->canonical_url ) ) {
			$url[ $hash ] = apply_filters( 'aioseo_canonical_url', $this->makeUrlAbsolute( $metaData->canonical_url ) );

			return $url[ $hash ];
		}

		if ( BuddyPressIntegration::isComponentPage() ) {
			$url[ $hash ] = aioseo()->standalone->buddyPress->component->getMeta( 'canonical' );
		}

		if ( empty( $url[ $hash ] ) || is_wp_error( $url[ $hash ] ) ) {
			$url[ $hash ] = $this->getUrl( true );
		}

		$pageNumber = $this->getPageNumber();
		if (
			in_array( 'noPaginationForCanonical', aioseo()->internalOptions->deprecatedOptions, true ) &&
			aioseo()->options->deprecated->searchAppearance->advanced->noPaginationForCanonical
		) {
			if ( 1 < $pageNumber ) {
				if ( $this->usingPermalinks() ) {
					// Replace /page/3 and /page/3/.
					$url[ $hash ] = preg_replace( "@(?<=/)page/$pageNumber(/|)$@", '', (string) $url[ $hash ] );
					// Replace /3 and /3/.
					$url[ $hash ] = preg_replace( "@(?<=/)$pageNumber(/|)$@", '', (string) $url[ $hash ] );
				} else {
					// Replace /?page_id=457&paged=1 and /?page_id=457&page=1.
					$url[ $hash ] = aioseo()->helpers->urlRemoveQueryParameter( $url[ $hash ], [ 'page', 'paged' ] );
				}
			}

			// Comment pages.
			$url[ $hash ] = preg_replace( '/(?<=\/)comment-page-\d+\/*(#comments)*$/', '', (string) $url[ $hash ] );
		}

		$url[ $hash ] = $this->maybeRemoveTrailingSlash( $url[ $hash ] );

		// Get rid of /amp at the end of the URL.
		if (
			aioseo()->helpers->isAmpPage() &&
			! apply_filters( 'aioseo_disable_canonical_url_amp', false )
		) {
			$url[ $hash ] = preg_replace( '/\/amp$/', '', (string) $url[ $hash ] );
			$url[ $hash ] = preg_replace( '/\/amp\/$/', '/', (string) $url[ $hash ] );
		}

		$url[ $hash ] = apply_filters( 'aioseo_canonical_url', $url[ $hash ] );

		return $url[ $hash ];
	}

	/**
	 * Sanitizes a given domain.
	 *
	 * @since 4.0.0
	 *
	 * @param  string       $domain The domain to sanitize.
	 * @return mixed|string         The sanitized domain.
	 */
	public function sanitizeDomain( $domain ) {
		$domain = trim( $domain );
		$domain = strtolower( $domain );
		if ( 0 === strpos( $domain, 'http://' ) ) {
			$domain = substr( $domain, 7 );
		} elseif ( 0 === strpos( $domain, 'https://' ) ) {
			$domain = substr( $domain, 8 );
		}
		$domain = untrailingslashit( $domain );

		return $domain;
	}

	/**
	 * Remove trailing slashes if not set in the permalink structure.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $url The original URL.
	 * @return string      The adjusted URL.
	 */
	public function maybeRemoveTrailingSlash( $url ) {
		$permalinks = get_option( 'permalink_structure' );
		if ( $permalinks && ( ! is_home() || ! is_front_page() ) ) {
			$trailing = substr( $permalinks, -1 );
			if ( '/' !== $trailing ) {
				$url = untrailingslashit( $url );
			}
		}

		// Don't slash urls with query args.
		if ( false !== strpos( $url, '?' ) ) {
			$url = untrailingslashit( $url );
		}

		return $url;
	}

	/**
	 * Removes image dimensions from the slug of a URL.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $url The image URL.
	 * @return string      The formatted image URL.
	 */
	public function removeImageDimensions( $url ) {
		return $this->isValidAttachment( $url ) ? preg_replace( '#(-[0-9]*x[0-9]*|-scaled)#', '', (string) $url ) : $url;
	}

	/**
	 * Returns the URL for the WP content folder.
	 *
	 * @since 4.0.5
	 *
	 * @return string The URL.
	 */
	public function getWpContentUrl() {
		$info = wp_get_upload_dir();

		return isset( $info['baseurl'] ) ? $info['baseurl'] : '';
	}

	/**
	* Retrieves a post by its given path.
	* Based on the built-in get_page_by_path() function, but only checks ancestry if the post type is actually hierarchical.
	*
	* @since 4.1.4
	*
	* @param  string       $path     The path.
	* @param  string       $output   The output type. OBJECT, ARRAY_A, or ARRAY_N.
	* @param  string|array $postType The post type(s) to check against.
	* @return object|false           The post or false on failure.
	*/
	public function getPostByPath( $path, $output = OBJECT, $postType = 'page' ) {
		$lastChanged = wp_cache_get_last_changed( 'aioseo_posts_by_path' );
		$hash        = md5( $path . serialize( $postType ) );
		$cacheKey    = "get_page_by_path:$hash:$lastChanged";
		$cached      = wp_cache_get( $cacheKey, 'aioseo_posts_by_path' );

		if ( false !== $cached ) {
			// Special case: '0' is a bad `$path`.
			if ( '0' === $cached || 0 === $cached ) {
				return false;
			}

			return get_post( $cached, $output );
		}

		$path          = rawurlencode( urldecode( $path ) );
		$path          = str_replace( '%2F', '/', $path );
		$path          = str_replace( '%20', ' ', $path );
		$parts         = explode( '/', trim( $path, '/' ) );
		$reversedParts = array_reverse( $parts );
		$postNames     = "'" . implode( "','", $parts ) . "'";

		$postTypes = is_array( $postType ) ? $postType : [ $postType, 'attachment' ];
		$postTypes = "'" . implode( "','", $postTypes ) . "'";

		$posts = aioseo()->core->db->start( 'posts' )
			->select( 'ID, post_name, post_parent, post_type' )
			->whereRaw( "post_name in ( $postNames )" )
			->whereRaw( "post_type in ( $postTypes )" )
			->run()
			->result();

		$foundId = 0;
		foreach ( $posts as $post ) {
			if ( $post->post_name === $reversedParts[0] ) {
				$count = 0;
				$p     = $post;

				// Loop through the given path parts from right to left, ensuring each matches the post ancestry.
				while ( 0 !== (int) $p->post_parent && isset( $posts[ $p->post_parent ] ) ) {
					$count++;
					$parent = $posts[ $p->post_parent ];
					if ( ! isset( $reversedParts[ $count ] ) || $parent->post_name !== $reversedParts[ $count ] ) {
						break;
					}
					$p = $parent;
				}

				if (
					0 === (int) $p->post_parent &&
					( ! is_post_type_hierarchical( $p->post_type ) || count( $reversedParts ) === $count + 1 ) &&
					$p->post_name === $reversedParts[ $count ]
				) {
					$foundId = $post->ID;
					if ( $post->post_type === $postType ) {
						break;
					}
				}
			}
		}

		// We cache misses as well as hits.
		wp_cache_set( $cacheKey, $foundId, 'aioseo_posts_by_path' );

		return $foundId ? get_post( $foundId, $output ) : false;
	}

	/**
	 * Validates a URL.
	 *
	 * @since 4.1.2
	 *
	 * @param  string $url The url.
	 * @return bool        Is it a valid/safe url.
	 */
	public function isUrl( $url ) {
		return esc_url_raw( $url ) === $url;
	}

	/**
	 * Retrieves the parameters for a given URL.
	 *
	 * @since 4.1.5
	 *
	 * @param  string $url          The url.
	 * @return array                The parameters.
	 */
	public function getParametersFromUrl( $url ) {
		$parsedUrl  = wp_parse_url( wp_unslash( $url ) );
		$parameters = [];

		if ( empty( $parsedUrl['query'] ) ) {
			return [];
		}

		wp_parse_str( $parsedUrl['query'], $parameters );

		return $parameters;
	}

	/**
	 * Adds a leading slash to an url.
	 *
	 * @since 4.1.8
	 *
	 * @param  string $url The url.
	 * @return string      The url with a leading slash.
	 */
	public function leadingSlashIt( $url ) {
		return '/' . ltrim( $url, '/' );
	}

	/**
	 * Returns the path from a permalink.
	 * This function will help get the correct path from WP installations in subfolders.
	 *
	 * @since 4.1.8
	 *
	 * @param  string $permalink A permalink from get_permalink().
	 * @return string            The path without the home_url().
	 */
	public function getPermalinkPath( $permalink ) {
		// We want to get this value straight from the DB to prevent plugins like WPML from filtering it.
		// This will otherwise mess with things like license activation requests and redirects.
		$homeUrl = $this->getHomeUrl( true );

		return $this->leadingSlashIt( str_replace( $homeUrl, '', $permalink ) );
	}

	/**
	 * Changed if permalinks are different and the before wasn't
	 * the site url (we don't want to redirect the site URL).
	 *
	 * @since 4.2.3
	 *
	 * @param  string  $before The URL before the change.
	 * @param  string  $after  The URL after the change.
	 * @return boolean         True if the permalink has changed.
	 */
	public function hasPermalinkChanged( $before, $after ) {
		// Check it's not redirecting from the root.
		if ( $this->getHomePath() === $before || '/' === $before ) {
			return false;
		}

		// Are the URLs the same?
		return ( $before !== $after );
	}

	/**
	 * Retrieve the home path.
	 *
	 * @since 4.2.3
	 *
	 * @param  bool   $unfiltered Whether to get the unfiltered value.
	 * @return string              The home path.
	 */
	public function getHomePath( $unfiltered = false ) {
		$path = wp_parse_url( $this->getHomeUrl( $unfiltered ), PHP_URL_PATH );

		return $path ? trailingslashit( $path ) : '/';
	}

	/**
	 * Returns the home URL.
	 *
	 * @since 4.7.3
	 *
	 * @param  bool   $unfiltered Whether to get the unfiltered value.
	 * @return string             The home URL.
	 */
	private function getHomeUrl( $unfiltered = false ) {
		$homeUrl = home_url();
		if ( $unfiltered ) {
			// We want to get this value straight from the DB to prevent plugins like WPML from filtering it.
			// This will otherwise mess with things like license activation requests and redirects.
			$homeUrl = get_option( 'home' );
		}

		return $homeUrl;
	}

	/**
	 * Checks if the given URL is an internal URL for the current site.
	 *
	 * @since 4.2.6
	 *
	 * @param  string $urlToCheck The URL to check.
	 * @return bool               Whether the given URL is an internal one.
	 */
	public function isInternalUrl( $urlToCheck ) {
		$parsedHomeUrl    = wp_parse_url( home_url() );
		$parsedUrlToCheck = wp_parse_url( $urlToCheck );

		return ! empty( $parsedHomeUrl['host'] ) && ! empty( $parsedUrlToCheck['host'] )
			? $parsedHomeUrl['host'] === $parsedUrlToCheck['host']
			: false;
	}

	/**
	 * Helper for the rest url.
	 *
	 * @since 4.4.9
	 *
	 * @return string
	 */
	public function getRestUrl() {
		$restUrl = get_rest_url();

		if ( aioseo()->helpers->isWpmlActive() ) {
			global $sitepress;

			// Replace the rest url 'all' language prefix so our rest calls don't fail.
			if (
				is_object( $sitepress ) &&
				method_exists( $sitepress, 'get_current_language' ) &&
				method_exists( $sitepress, 'get_default_language' ) &&
				'all' === $sitepress->get_current_language()
			) {
				$restUrl = str_replace(
					get_home_url( null, '/all/' ),
					get_home_url( null, '/' . $sitepress->get_default_language() . '/' ),
					$restUrl
				);
			}
		}

		return $restUrl;
	}

	/**
	 * Exclude the home path from a full path.
	 *
	 * @since   1.2.3 Moved from aioseo-redirects.
	 * @version 4.5.8
	 *
	 * @param  string $path The original path.
	 * @return string       The path without WP's home path.
	 */
	public function excludeHomePath( $path ) {
		return preg_replace( '@^' . $this->getHomePath() . '@', '/', (string) $path );
	}

	/**
	 * Get the canonical URL for a post.
	 * This is a duplicate of wp_get_canonical_url() with a fix for issue #6372 where
	 * posts with paginated comment pages return the wrong canonical URL due to how WordPress sets the cpage var.
	 * We can remove this once trac ticket 60806 is resolved.
	 *
	 * @since 4.6.9
	 *
	 * @param  \WP_Post|int|null $post The post object or ID.
	 * @return string|false            The post's canonical URL, or false if the post is not published.
	 */
	public function wpGetCanonicalUrl( $post = null ) {
		$post = get_post( $post );

		if ( ! $post ) {
			return false;
		}

		if ( 'publish' !== $post->post_status ) {
			return false;
		}

		$canonical_url = get_permalink( $post ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName

		// If a canonical is being generated for the current page, make sure it has pagination if needed.
		if ( get_queried_object_id() === $post->ID ) {
			$page = get_query_var( 'page', 0 );
			if ( $page >= 2 ) {
				if ( ! get_option( 'permalink_structure' ) ) {
					$canonical_url = add_query_arg( 'page', $page, $canonical_url ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
				} else {
					$canonical_url = trailingslashit( $canonical_url ) . user_trailingslashit( $page, 'single_paged' ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
				}
			}

			$cpage = aioseo()->helpers->getCommentPageNumber(); // We're calling our own function here to get the correct cpage number.
			if ( $cpage ) {
				$canonical_url = get_comments_pagenum_link( $cpage ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			}
		}

		return apply_filters( 'get_canonical_url', $canonical_url, $post ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
	}

	/**
	 * Checks if permalinks are enabled.
	 *
	 * @since 4.8.3
	 *
	 * @return bool Whether permalinks are enabled.
	 */
	public function usingPermalinks() {
		global $wp_rewrite; // phpcs:ignore Squiz.NamingConventions.ValidVariableName

		return $wp_rewrite->using_permalinks();  // phpcs:ignore Squiz.NamingConventions.ValidVariableName
	}
}Request.php000066600000003440151135523120006710 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Parse the current request.
 *
 * @since 4.2.1
 */
trait Request {
	/**
	 * Get the server port.
	 *
	 * @since 4.2.1
	 *
	 * @return string The server port.
	 */
	private function getServerPort() {
		if (
			empty( $_SERVER['SERVER_PORT'] ) ||
			80 === (int) $_SERVER['SERVER_PORT'] ||
			443 === (int) $_SERVER['SERVER_PORT']
		) {
			return '';
		}

		return ':' . (int) $_SERVER['SERVER_PORT'];
	}

	/**
	 * Get the protocol.
	 *
	 * @since 4.2.1
	 *
	 * @return string The protocol.
	 */
	private function getProtocol() {
		return is_ssl() ? 'https' : 'http';
	}

	/**
	 * Get the server name (from $_SERVER['SERVER_NAME]), or use the request name ($_SERVER['HTTP_HOST']) if not present.
	 *
	 * @since 4.2.1
	 *
	 * @return string The server name.
	 */
	private function getServerName() {
		$host = $this->getRequestServerName();

		if ( isset( $_SERVER['SERVER_NAME'] ) ) {
			$host = sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) ); // phpcs:ignore HM.Security.ValidatedSanitizedInput.InputNotSanitized
		}

		return $host;
	}

	/**
	 * Get the request server name (from $_SERVER['HTTP_HOST]).
	 *
	 * @since 4.2.1
	 *
	 * @return string The request server name.
	 */
	private function getRequestServerName() {
		$host = '';

		if ( isset( $_SERVER['HTTP_HOST'] ) ) {
			$host = sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) );
		}

		return $host;
	}

	/**
	 * Retrieve the request URL.
	 *
	 * @since 4.2.1
	 *
	 * @return string The request URL.
	 */
	public function getRequestUrl() {
		$url = '';

		if ( isset( $_SERVER['REQUEST_URI'] ) ) {
			$url = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
		}

		return rawurldecode( $url );
	}
}Url.php000066600000022265151135523120006030 0ustar00<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains URL helper methods.
 *
 * @since 4.2.5
 */
trait Url {
	/**
	 * Removes a query string parameter from a URL.
	 *
	 * @since 4.2.5
	 *
	 * @param  string $url        The url.
	 * @param  array  $parameters The parameter keys to remove.
	 * @return string             The url without the parameters removed.
	 */
	public function urlRemoveQueryParameter( $url, $parameters ) {
		$url = wp_parse_url( $url );
		if ( ! empty( $url['query'] ) ) {
			// Take the query string apart.
			parse_str( $url['query'], $queryStringArray );

			// Remove parameters.
			foreach ( $parameters as $parameter ) {
				if ( isset( $queryStringArray[ $parameter ] ) ) {
					unset( $queryStringArray[ $parameter ] );
				}
			}

			// Rebuild the query string.
			$url['query'] = build_query( $queryStringArray );

			// Rebuild the URL from parse_url.
			$url = $this->buildUrl( $url );
		}

		return $url;
	}

	/**
	 * Builds a URL from a parse_url array.
	 *
	 * @since 4.2.5
	 *
	 * @param  array  $params  The params array.
	 * @param  array  $include The keys to include [scheme, user, pass, host, port, path, query, fragment].
	 * @param  array  $exclude The keys to exclude [scheme, user, pass, host, port, path, query, fragment].
	 * @return string          The built url.
	 */
	public function buildUrl( $params, $include = [], $exclude = [] ) {
		if ( ! is_array( $params ) ) {
			return $params;
		}

		if ( ! empty( $include ) ) {
			foreach ( array_keys( $params ) as $includeKey ) {
				if ( ! in_array( $includeKey, $include, true ) ) {
					unset( $params[ $includeKey ] );
				}
			}
		}

		if ( ! empty( $exclude ) ) {
			foreach ( array_keys( $params ) as $excludeKey ) {
				if ( in_array( $excludeKey, $exclude, true ) ) {
					unset( $params[ $excludeKey ] );
				}
			}
		}

		$url = '';
		if ( ! empty( $params['scheme'] ) ) {
			$url .= $params['scheme'] . '://';
		}
		if ( ! empty( $params['user'] ) ) {
			$url .= $params['user'];

			if ( isset( $params['pass'] ) ) {
				$url .= ':' . $params['pass'];
			}

			$url .= '@';
		}

		if ( ! empty( $params['host'] ) ) {
			$url .= $params['host'];
		}

		if ( ! empty( $params['port'] ) ) {
			$url .= ':' . $params['port'];
		}

		if ( ! empty( $params['path'] ) ) {
			$url .= $params['path'];
		}

		if ( ! empty( $params['query'] ) ) {
			$url .= '?' . $params['query'];
		}

		if ( ! empty( $params['fragment'] ) ) {
			$url .= '#' . $params['fragment'];
		}

		return $url;
	}

	/**
	 * Checks if a URL is considered a local one.
	 *
	 * @since 4.5.9
	 *
	 * @param  string $url The URL.
	 * @return bool        Whether the URL is a local one or not.
	 */
	public function isLocalUrl( $url ) {
		$domain = wp_parse_url( $url, PHP_URL_HOST );
		if ( empty( $domain ) ) {
			return false;
		}

		if (
			false !== ip2long( $domain ) &&
			! filter_var( $domain, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE )
		) {
			return true;
		}

		if ( 'localhost' === $domain ) {
			return true;
		}

		if ( ! $this->isValidDomain( $domain ) ) {
			return true;
		}

		$tldsToCheck = [
			'.local',
			'.test',
		];

		foreach ( $tldsToCheck as $tld ) {
			if ( false !== strpos( $this->getTld( $domain ), $tld ) ) {
				return true;
			}
		}

		if ( substr_count( $domain, '.' ) > 1 ) {
			$subdomainsToCheck = [
				'dev',
				'development',
				'staging',
				'stage',
				'test',
				'staging*',
				'*staging',
				'dev*',
				'*dev',
				'test*',
				'*test'
			];

			foreach ( $subdomainsToCheck as $subdomain ) {
				foreach ( $this->getSubdomains( $domain ) as $sd ) {

					$subdomain = str_replace( '.', '(.)', $subdomain );
					$subdomain = str_replace( [ '*', '(.)' ], '(.*)', $subdomain );

					if ( preg_match( '/^(' . $subdomain . ')$/', (string) $sd ) ) {
						return true;
					}
				}
			}
		}

		return false;
	}

	/**
	 * Checks if a domain is valid.
	 *
	 * @since 4.5.9
	 *
	 * @param  string $domain The domain.
	 * @return bool           Whether the domain is valid or not.
	 */
	private function isValidDomain( $domain ) {
		// In case there are unicode characters, convert it into
		// IDNA ASCII URLs
		if ( function_exists( 'idn_to_ascii' ) ) {
			$domain = idn_to_ascii( $domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46 );
		}

		if ( ! $domain ) {
			return false;
		}

		$domain = preg_replace( '/^\*\.+/', '', (string) $domain );

		return preg_match( '/^(?!\-)(?:[a-z\d\-]{0,62}[a-z\d]\.){1,126}(?!\d+)[a-z\d]{1,63}$/i', (string) $domain );
	}

	/**
	 * Checks if a domain is valid and optionally contains paths at the end.
	 *
	 * @since 4.7.7
	 *
	 * @param  string $domain The domain.
	 * @return bool           Whether the domain is valid or not.
	 */
	private function isDomainWithPaths( $domain ) {
		// In case there are unicode characters, convert it into IDNA ASCII URLs.
		if ( function_exists( 'idn_to_ascii' ) ) {
			$domain = idn_to_ascii( $domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46 );
		}

		if ( ! $domain ) {
			return false;
		}

		$domain = preg_replace( '/^\*\.+/', '', $domain );

		return preg_match( '/^(?!\-)(?:[a-z\d\-]{0,62}[a-z\d]\.){1,126}(?!\d+)[a-z\d]{1,63}(\/[a-z\d\-\/]*)?$/i', $domain );
	}

	/**
	 * Returns a single string of all subdomains associated with this domain.
	 * Example 1: www
	 * Example 2: ww2.www
	 *
	 * @since 4.5.9
	 *
	 * @return array The subdomains associated with this domain.
	 */
	public function getSubdomains( $domain ) {
		// If we can't find a TLD, we won't be able to parse a subdomain.
		if ( empty( $this->getTld( $domain ) ) ) {
			return [];
		}

		// Return any subdomains as an array.
		return array_filter( explode( '.', rtrim( strstr( $domain, $this->getTld( $domain ), true ), '.' ) ) );
	}

	/**
	 * Returns the TLD associated with the given domain.
	 *
	 * @since 4.5.9
	 *
	 * @param  string $domain The domain.
	 * @return string         The TLD.
	 */
	public function getTld( $domain ) {
		if ( preg_match( '/(?P<tld>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$/i', (string) $domain, $matches ) ) {
			return $matches['tld'];
		}

		return $domain;
	}

	/**
	 * Returns a decoded URL string.
	 *
	 * @since 4.6.7
	 *
	 * @param  string $url The URL string.
	 * @return string      The decoded URL.
	 */
	public function decodeUrl( $url ) {
		// Ensure input is a string to prevent errors.
		if ( ! is_string( $url ) ) {
			return $url;
		}

		// Set a reasonable iteration limit to prevent infinite loops.
		$maxIterations = 10;
		$iterations    = 0;

		$decodedUrl = rawurldecode( $url );
		while ( $decodedUrl !== $url && $iterations < $maxIterations ) {
			$url        = $decodedUrl;
			$decodedUrl = rawurldecode( $url );
			$iterations++;
		}

		return $decodedUrl;
	}

	/**
	 * Redirects to a specific URL.
	 *
	 * @since 4.8.0
	 *
	 * @param string $url    The URL to redirect to.
	 * @param int    $status The status code to use.
	 * @param string $reason The reason for redirecting.
	 *
	 * @return void
	 */
	public function redirect( $url, $status = 301, $reason = '' ) {
		$redirectBy = 'AIOSEO';
		if ( ! empty( $reason ) ) {
			$redirectBy .= ': ' . $reason;
		}

		wp_safe_redirect( $url, $status, $redirectBy );
		exit;
	}

	/**
	 * Checks if the given URL is external.
	 *
	 * @since 4.8.3
	 *
	 * @param  string $url The URL to check.
	 * @return bool        Whether the URL is external or not.
	 */
	public function isExternalUrl( $url ) {
		$parsedUrl = wp_parse_url( $url );
		if ( ! $parsedUrl ) {
			return false;
		}

		static $parsedSiteUrl = null;
		if ( ! $parsedSiteUrl ) {
			$parsedSiteUrl = wp_parse_url( site_url() );
		}

		return $parsedSiteUrl['host'] !== $parsedUrl['host'];
	}

	/**
	 * Checks if the given URL is relative.
	 *
	 * @since 4.8.3
	 *
	 * @param  string $url The URL to check.
	 * @return bool        Whether the URL is relative or not.
	 */
	public function isRelativeUrl( $url ) {
		$parsedUrl = wp_parse_url( $url );
		if ( ! $parsedUrl ) {
			return false;
		}

		return empty( $parsedUrl['scheme'] ) && empty( $parsedUrl['host'] );
	}

	/**
	 * Makes the given URL relative.
	 *
	 * @since 4.8.3
	 *
	 * @param  string $url The URL to make relative.
	 * @return string      The relative URL.
	 */
	public function makeUrlRelative( $url ) {
		$parsedUrl = wp_parse_url( $url );
		if ( ! $parsedUrl ) {
			return $url;
		}

		static $parsedSiteUrl = null;
		if ( ! $parsedSiteUrl ) {
			$parsedSiteUrl = wp_parse_url( site_url() );
		}

		if ( $parsedSiteUrl['host'] !== $parsedUrl['host'] ) {
			return $url;
		}

		return ! empty( $parsedUrl['path'] ) ? $parsedUrl['path'] : $url;
	}

	/**
	 * Formats a given URL as an absolute URL if it is relative.
	 *
	 * @since   4.0.0
	 * @version 4.8.3 Moved from WpUri trait to Url trait.
	 *
	 * @param  string $url The URL.
	 * @return string      The absolute URL.
	 */
	public function makeUrlAbsolute( $url ) {
		if ( 0 !== strpos( $url, 'http' ) && '/' !== $url ) {
			$url = $this->sanitizeDomain( $url );
			if ( $this->isDomainWithPaths( $url ) ) {
				$scheme = wp_parse_url( site_url(), PHP_URL_SCHEME );
				$url    = $scheme . '://' . $url;
			} elseif ( 0 === strpos( $url, '//' ) ) {
				$scheme = wp_parse_url( site_url(), PHP_URL_SCHEME );
				$url    = $scheme . ':' . $url;
			} else {
				$url = site_url( $url );
			}
		}

		return $url;
	}
}BuddyPress.php000066600000001370151140260620007343 0ustar00<?php

namespace AIOSEO\Plugin\Common\Meta\Traits\Helpers;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contains BuddyPress specific helper methods.
 *
 * @since 4.7.6
 */
trait BuddyPress {
	/**
	 * Sanitizes the title/description.
	 *
	 * @since 4.7.6
	 *
	 * @param  string $value       The value.
	 * @param  int    $objectId    The object ID.
	 * @param  bool   $replaceTags Whether the smart tags should be replaced.
	 * @return string              The sanitized value.
	 */
	public function bpSanitize( $value, $objectId = 0, $replaceTags = false ) {
		$value = $replaceTags ? $value : aioseo()->standalone->buddyPress->tags->replaceTags( $value, $objectId );

		return $this->sanitize( $value, $objectId, true );
	}
}RedirectHelper.php000066600000000512151143704740010166 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class RedirectHelper
{
    /**
     * Does a safe redirect to an admin page.
     *
     * @param string $url The url to be redirected to.
     */
    public static function safe($url)
    {
        nocache_headers();
        \wp_safe_redirect($url);
        exit;
    }
}
ReflectionHelper.php000066600000001000151143704740010510 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\WordPressSDK;
class ReflectionHelper
{
    /**
     * @param class $instance The class from which to get the name.
     * @return false|string
     */
    public static function name($instance)
    {
        if ($instance instanceof \YoastSEO_Vendor\WordProof\SDK\WordPressSDK) {
            $reflector = new \ReflectionClass($instance);
            return $reflector->getName();
        }
        return \false;
    }
}
EscapeHelper.php000066600000004056151143704740007634 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class EscapeHelper
{
    /**
     * Returns the value escaped according to the escape function set in the class.
     *
     * @param mixed $value The value to be sanitized.
     * @param string $escapeKey The escape function to be used.
     *
     * @return array|bool|int|string
     */
    public static function escape($value, $escapeKey)
    {
        if (\is_array($value)) {
            return self::escapeArray($value, $escapeKey);
        }
        if (\is_object($value)) {
            return (object) self::escapeArray((array) $value, $escapeKey);
        }
        return self::escapeSingleValue($value, $escapeKey);
    }
    /**
     * Loops through the array to escape the values inside.
     *
     * @param array $array The array with values to be escaped.
     * @param string $escapeKey The escape function to be used.
     * @return array Array with escapes values.
     */
    private static function escapeArray($array, $escapeKey)
    {
        $values = [];
        foreach ($array as $key => $value) {
            $values[$key] = self::escapeSingleValue($value, $escapeKey);
        }
        return $values;
    }
    /**
     * Escapes a single value using an escape function set in the class.
     *
     * @param string $value The value to be escaped.
     * @param string $escapeKey The escape function to be used.
     * @return bool|int|string The escaped value.
     */
    private static function escapeSingleValue($value, $escapeKey)
    {
        switch ($escapeKey) {
            case 'integer':
                return \intval($value);
            case 'boolean':
                return \boolval($value);
            case 'html_class':
                return \esc_html_class($value);
            case 'email':
                return \esc_email($value);
            case 'url':
                return \esc_url_raw($value);
            case 'key':
                return \esc_key($value);
            case 'text_field':
            default:
                return \esc_html($value);
        }
    }
}
TimestampHelper.php000066600000006611151143704740010376 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\DataTransferObjects\TimestampData;
use YoastSEO_Vendor\WordProof\SDK\Support\Timestamp;
class TimestampHelper
{
    public static function debounce(\WP_Post $post)
    {
        $key = 'wordproof_timestamped_debounce_' . $post->id;
        $data = \YoastSEO_Vendor\WordProof\SDK\DataTransferObjects\TimestampData::fromPost($post);
        $transient = \YoastSEO_Vendor\WordProof\SDK\Helpers\TransientHelper::get($key);
        if ($transient) {
            return new \WP_REST_Response($transient, $transient->status);
        }
        $response = self::shouldBeTimestamped($post, $data);
        if (\is_bool($response) && $response === \false) {
            $response = (object) ['status' => 200, 'message' => 'Post should not be timestamped'];
            return new \WP_REST_Response($response, $response->status);
        }
        if (\is_array($response) && $response['timestamp'] === \false) {
            $response = (object) ['status' => 400, 'message' => 'Post should not be timestamped', 'error' => 'not_authenticated'];
            return new \WP_REST_Response($response, $response->status);
        }
        $response = \YoastSEO_Vendor\WordProof\SDK\Support\Timestamp::sendPostRequest($data);
        if ($response === \false) {
            $response = (object) ['status' => 400, 'message' => 'Something went wrong.', 'error' => 'timestamp_failed'];
            return new \WP_REST_Response($response, $response->status);
        }
        $response->status = 201;
        \YoastSEO_Vendor\WordProof\SDK\Helpers\TransientHelper::set($key, $response, 5);
        return new \WP_REST_Response($response, $response->status);
    }
    public static function shouldBeTimestamped(\WP_Post $post, $data)
    {
        if (!\YoastSEO_Vendor\WordProof\SDK\Helpers\AuthenticationHelper::isAuthenticated()) {
            if (self::hasPostMetaOverrideSetToTrue($post)) {
                return ['timestamp' => \false, 'notice' => 'not_authenticated'];
            }
            return \false;
        }
        if ($post->post_type !== 'attachment' && $post->post_content === '') {
            return \false;
        }
        if ($post->post_type === 'attachment' && \get_attached_file($post->ID) === \false) {
            return \false;
        }
        if (!\in_array($post->post_status, ['publish', 'inherit'], \true)) {
            return \false;
        }
        if (\YoastSEO_Vendor\WordProof\SDK\Helpers\SettingsHelper::postTypeIsInSelectedPostTypes($post->post_type)) {
            return \true;
        }
        if (self::hasPostMetaOverrideSetToTrue($post)) {
            return \true;
        }
        return \false;
    }
    private static function hasPostMetaOverrideSetToTrue(\WP_Post $post)
    {
        $timestampablePostMetaKeys = \apply_filters('wordproof_timestamp_post_meta_key_overrides', ['_wordproof_timestamp']);
        //Do not use PostMeta helper
        $meta = \get_post_meta($post->ID);
        foreach ($timestampablePostMetaKeys as $key) {
            if (!isset($meta[$key])) {
                continue;
            }
            if (\is_array($meta[$key])) {
                $value = \boolval($meta[$key][0]);
            } else {
                $value = \boolval($meta[$key]);
            }
            if (!$value) {
                continue;
            }
            return \true;
        }
        return \false;
    }
}
TransientHelper.php000066600000002711151143704740010377 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\DataTransferObjects\TimestampData;
class TransientHelper
{
    /**
     * Set transient.
     *
     * @param $key
     * @param $value
     * @param int $expiration
     * @return bool
     */
    public static function set($key, $value, $expiration = 0)
    {
        return \set_transient($key, $value, $expiration);
    }
    /**
     * Returns and deletes site transient by key.
     *
     * @param $key
     * @return mixed
     */
    public static function getOnce($key)
    {
        $value = \get_transient($key);
        \delete_transient($key);
        return $value;
    }
    /**
     * Returns the transient by key.
     *
     * @param $key
     * @return mixed
     */
    public static function get($key)
    {
        return \get_transient($key);
    }
    /**
     * Debounce callback for post id.
     *
     * @param $postId
     * @param $action
     * @param $callback
     * @return mixed
     */
    public static function debounce($postId, $action, $callback)
    {
        $key = 'wordproof_debounce_' . $action . '_' . $postId;
        $transient = \YoastSEO_Vendor\WordProof\SDK\Helpers\TransientHelper::get($key);
        if ($transient) {
            return $transient;
        } else {
            \YoastSEO_Vendor\WordProof\SDK\Helpers\TransientHelper::set($key, \true, 4);
            $result = $callback();
            return $result;
        }
    }
}
StringHelper.php000066600000001370151143704740007676 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class StringHelper
{
    /**
     * Replace the last occurrence.
     *
     * @param string $search
     * @param string $replace
     * @param string $subject
     * @return string
     */
    public static function lastReplace($search, $replace, $subject)
    {
        $pos = \strrpos($subject, $search);
        if ($pos !== \false) {
            $subject = \substr_replace($subject, $replace, $pos, \strlen($search));
        }
        return $subject;
    }
    /**
     * PascalCase to snake_case
     *
     * @param $string
     * @return string
     */
    public static function toUnderscore($string)
    {
        return \strtolower(\preg_replace('/(?<!^)[A-Z]/', '_$0', $string));
    }
}
SchemaHelper.php000066600000003351151143704740007631 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class SchemaHelper
{
    /**
     * Builds an blockchain transaction schema object as array.
     *
     * @param object $response The response by WordProof.
     * @return array The blockchain transaction in the correct schema format.
     */
    public static function getBlockchainTransaction($response)
    {
        $postId = $response->uid;
        $hashLink = \YoastSEO_Vendor\WordProof\SDK\Helpers\RestApiHelper::getRestRoute('hashInput', [$postId, $response->hash]);
        $identifier = null;
        if (isset($response->transaction)) {
            $transaction = $response->transaction;
            if (isset($transaction->transactionId)) {
                $identifier = $transaction->transactionId;
            }
            if (isset($transaction->tx)) {
                $identifier = $transaction->tx;
            }
        }
        return ['@type' => 'BlockchainTransaction', 'identifier' => $identifier, 'hash' => $response->hash, 'hashLink' => $hashLink, 'recordedIn' => ['@type' => 'Blockchain', 'name' => $response->transaction->blockchain]];
    }
    /**
     * Retrieves the schema as array for a post.
     *
     * @param integer $postId The post id for which the schema should be returned.
     * @return array The schema as array.
     */
    public static function getSchema($postId)
    {
        $transactions = \YoastSEO_Vendor\WordProof\SDK\Helpers\PostMetaHelper::get($postId, '_wordproof_blockchain_transaction', \false);
        $latest = \array_pop($transactions);
        if (\count($transactions) === 0) {
            return ['timestamp' => $latest];
        }
        return ['timestamp' => \array_merge($latest, ['revisions' => \array_reverse($transactions)])];
    }
}
AppConfigHelper.php000066600000002362151143704740010300 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\WordPressSDK;
class AppConfigHelper
{
    /**
     * Returns the partner set during initialization.
     *
     * @return string|null
     */
    public static function getPartner()
    {
        $appConfig = self::getAppConfig();
        if ($appConfig) {
            return $appConfig->getPartner();
        }
        return null;
    }
    /**
     * Returns the environment set during initialization.
     * @return string|null
     */
    public static function getEnvironment()
    {
        $appConfig = self::getAppConfig();
        if ($appConfig) {
            return $appConfig->getEnvironment();
        }
        return null;
    }
    /**
     * Returns the environment set during initialization.
     * @return boolean
     */
    public static function getLoadUikitFromCdn()
    {
        $appConfig = self::getAppConfig();
        if ($appConfig) {
            return $appConfig->getLoadUikitFromCdn();
        }
        return null;
    }
    public static function getAppConfig()
    {
        $sdk = \YoastSEO_Vendor\WordProof\SDK\WordPressSDK::getInstance();
        if ($sdk) {
            return $sdk->appConfig;
        }
        return null;
    }
}
AuthenticationHelper.php000066600000001166151143704740011412 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class AuthenticationHelper
{
    /**
     * Removes all the options set by WordProof.
     *
     * @return void
     */
    public static function logout()
    {
        \YoastSEO_Vendor\WordProof\SDK\Helpers\OptionsHelper::resetAuthentication();
    }
    /**
     * Returns if the user is authenticated.
     *
     * @return bool If the user is authenticated.
     */
    public static function isAuthenticated()
    {
        $options = \YoastSEO_Vendor\WordProof\SDK\Helpers\OptionsHelper::all();
        return $options->access_token && $options->source_id;
    }
}
AdminHelper.php000066600000000760151143704740007462 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class AdminHelper
{
    /**
     * Returns the current admin url of the user.
     *
     * @return null|string The current admin url of the logged in user.
     */
    public static function currentUrl()
    {
        if (isset($_SERVER['REQUEST_URI'])) {
            $requestUri = \esc_url_raw(\wp_unslash($_SERVER['REQUEST_URI']));
            return \admin_url(\sprintf(\basename($requestUri)));
        }
        return null;
    }
}
PostEditorHelper.php000066600000007720151143704740010531 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\Translations\TranslationsInterface;
class PostEditorHelper
{
    /**
     * Returns the post editor that is in use.
     *
     * @return bool The post editor the user is using..
     */
    public static function getPostEditor()
    {
        if (!\function_exists('YoastSEO_Vendor\\get_current_screen')) {
            return null;
        }
        $screen = \get_current_screen();
        if (!self::isPostEdit($screen->base)) {
            return null;
        }
        // Start with Elementor, otherwise the block editor will be returned.
        $action = \filter_input(\INPUT_GET, 'action', \FILTER_SANITIZE_STRING);
        if ($action === 'elementor') {
            return 'elementor';
        }
        if (\method_exists($screen, 'is_block_editor') && $screen->is_block_editor()) {
            return 'block';
        }
        return 'classic';
    }
    /**
     * Returns if the page is a post edit page.
     *
     * @param string $page The page to check.
     * @return bool If the current page is a post edit page.
     */
    public static function isPostEdit($page)
    {
        return \in_array($page, self::getPostEditPages(), \true);
    }
    /**
     * Returns an array of edit page hooks.
     *
     * @return array Post edit page hooks.
     */
    public static function getPostEditPages()
    {
        return ['post.php', 'post', 'post-new.php', 'post-new'];
    }
    /**
     * Returns the data that should be added to the post editor.
     *
     * @param TranslationsInterface $translations The implemented translations interface.
     *
     * @return array[] The post editor data.
     */
    public static function getPostEditorData(\YoastSEO_Vendor\WordProof\SDK\Translations\TranslationsInterface $translations)
    {
        global $post;
        $postId = isset($post->ID) ? $post->ID : null;
        $postType = isset($post->post_type) ? $post->post_type : null;
        $translations = ['no_balance' => $translations->getNoBalanceNotice(), 'timestamp_success' => $translations->getTimestampSuccessNotice(), 'timestamp_failed' => $translations->getTimestampFailedNotice(), 'webhook_failed' => $translations->getWebhookFailedNotice(), 'not_authenticated' => $translations->getNotAuthenticatedNotice(), 'open_authentication_button_text' => $translations->getOpenAuthenticationButtonText(), 'open_settings_button_text' => $translations->getOpenSettingsButtonText(), 'contact_wordproof_support_button_text' => $translations->getContactWordProofSupportButtonText()];
        return ['data' => ['origin' => \YoastSEO_Vendor\WordProof\SDK\Helpers\EnvironmentHelper::url(), 'is_authenticated' => \YoastSEO_Vendor\WordProof\SDK\Helpers\AuthenticationHelper::isAuthenticated(), 'popup_redirect_authentication_url' => \admin_url('admin.php?page=wordproof-redirect-authenticate'), 'popup_redirect_settings_url' => \admin_url('admin.php?page=wordproof-redirect-settings'), 'settings' => \YoastSEO_Vendor\WordProof\SDK\Helpers\SettingsHelper::get(), 'current_post_id' => $postId, 'current_post_type' => $postType, 'post_editor' => self::getPostEditor(), 'translations' => $translations, 'balance' => \YoastSEO_Vendor\WordProof\SDK\Helpers\OptionsHelper::get('balance')]];
    }
    /**
     * Returns the current post type.
     *
     * @return null|string The current post type.
     */
    public static function getCurrentPostType()
    {
        global $post, $typenow, $current_screen;
        if ($post && $post->post_type) {
            return $post->post_type;
        }
        if ($typenow) {
            return $typenow;
        }
        if ($current_screen && $current_screen->post_type) {
            return $current_screen->post_type;
        }
        // phpcs:disable WordPress.Security.NonceVerification
        if (isset($_REQUEST['post_type'])) {
            return \sanitize_key($_REQUEST['post_type']);
        }
        // phpcs:enable WordPress.Security.NonceVerification
        return null;
    }
}
AssetHelper.php000066600000005270151143704740007512 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\Config\ScriptsConfig;
class AssetHelper
{
    private static $prefix = 'wordproof-';
    private static $filePath = 'app/';
    private static $buildPath = 'build/';
    /**
     * Localizes script by name.
     *
     * @param string $name Name of the script
     * @param string $objectName The name of the object in Javascript.
     * @param array $data The data to be included.
     * @return bool|void
     */
    public static function localize($name, $objectName, $data)
    {
        $config = \YoastSEO_Vendor\WordProof\SDK\Config\ScriptsConfig::get($name);
        if (!isset($config)) {
            return;
        }
        return \wp_localize_script(self::getHandle($name), $objectName, $data);
    }
    /**
     * Enqueues a script defined in the scripts config.
     *
     * @param string $name The name of the script to enqueue.
     * @return false|mixed|void
     */
    public static function enqueue($name)
    {
        $config = \YoastSEO_Vendor\WordProof\SDK\Config\ScriptsConfig::get($name);
        if (!isset($config)) {
            return;
        }
        $path = self::getPathUrl($name, $config['type']);
        if ($config['type'] === 'css') {
            \wp_enqueue_style(self::getHandle($name), $path, $config['dependencies'], self::getVersion());
        } else {
            \wp_enqueue_script(self::getHandle($name), $path, $config['dependencies'], self::getVersion(), \false);
        }
    }
    /**
     * Returns the prefixed script handle.
     *
     * @param string $name The name of the script.
     * @return string Handle of the script.
     */
    private static function getHandle($name)
    {
        return self::$prefix . $name;
    }
    /**
     * Get path url of the script.
     *
     * @param string $name The name of the script.
     * @return string The url of the script.
     */
    private static function getPathUrl($name, $extension)
    {
        $appConfig = \YoastSEO_Vendor\WordProof\SDK\Helpers\AppConfigHelper::getAppConfig();
        if ($appConfig->getScriptsFileOverwrite()) {
            $url = $appConfig->getScriptsFileOverwrite();
        } else {
            $url = \plugin_dir_url(WORDPROOF_TIMESTAMP_SDK_FILE);
        }
        $base = \YoastSEO_Vendor\WordProof\SDK\Helpers\StringHelper::lastReplace(self::$filePath, self::$buildPath, $url);
        return $base . $name . '.' . $extension;
    }
    /**
     * Returns version for file.
     *
     * @return false|string
     */
    private static function getVersion()
    {
        return \YoastSEO_Vendor\WordProof\SDK\Helpers\EnvironmentHelper::development() ? \false : WORDPROOF_TIMESTAMP_SDK_VERSION;
    }
}
CertificateHelper.php000066600000001260151143704740010650 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class CertificateHelper
{
    /**
     * Returns if the certificate should be displayed for this page.
     *
     * @return false If the certificate should be shown.
     */
    public static function show()
    {
        if (!\is_singular()) {
            return \false;
        }
        if (!\is_main_query()) {
            return \false;
        }
        if (\post_password_required()) {
            return \false;
        }
        global $post;
        return \apply_filters('wordproof_timestamp_show_certificate', \YoastSEO_Vendor\WordProof\SDK\Helpers\PostMetaHelper::has($post->ID, '_wordproof_schema'), $post);
    }
}
PostTypeHelper.php000066600000001322151143704740010214 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class PostTypeHelper
{
    /**
     * Returns public post types.
     *
     * @return array The public post types.
     */
    public static function getPublicPostTypes()
    {
        return \array_values(\get_post_types(['public' => \true]));
    }
    public static function getUnprotectedPosts($postType)
    {
        $query = ['post_type' => [$postType], 'fields' => 'ids', 'posts_per_page' => -1, 'post_status' => ['publish', 'inherit'], 'meta_query' => [['key' => '_wordproof_blockchain_transaction', 'compare' => 'NOT EXISTS']]];
        $query = new \WP_Query($query);
        return ['count' => $query->found_posts, 'postIds' => $query->posts];
    }
}
EnvironmentHelper.php000066600000002653151143704740010741 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\Config\EnvironmentConfig;
class EnvironmentHelper
{
    public static function url()
    {
        $appConfig = \YoastSEO_Vendor\WordProof\SDK\Helpers\AppConfigHelper::getAppConfig();
        if ($appConfig->getWordProofUrl()) {
            return $appConfig->getWordProofUrl();
        }
        return self::get('url');
    }
    public static function client()
    {
        $appConfig = \YoastSEO_Vendor\WordProof\SDK\Helpers\AppConfigHelper::getAppConfig();
        if ($appConfig->getOauthClient()) {
            return $appConfig->getOauthClient();
        }
        return self::get('client');
    }
    public static function sslVerify()
    {
        return !\YoastSEO_Vendor\WordProof\SDK\Helpers\EnvironmentHelper::development();
    }
    public static function development()
    {
        return \YoastSEO_Vendor\WordProof\SDK\Helpers\AppConfigHelper::getEnvironment() === 'development';
    }
    public static function get($key)
    {
        $envConfig = self::environmentConfig();
        if ($envConfig && isset($envConfig[$key])) {
            return $envConfig[$key];
        }
        return null;
    }
    private static function environmentConfig()
    {
        $env = \YoastSEO_Vendor\WordProof\SDK\Helpers\AppConfigHelper::getEnvironment();
        return \YoastSEO_Vendor\WordProof\SDK\Config\EnvironmentConfig::get($env);
    }
}
SettingsHelper.php000066600000002741151143704740010233 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\Config\OptionsConfig;
class SettingsHelper
{
    private static $key = 'settings';
    /**
     * Retrieving settings from the option.
     *
     * @param null $setting The key for the setting
     * @return array|bool|int|mixed|object|string|null
     */
    public static function get($setting = null)
    {
        $settings = \YoastSEO_Vendor\WordProof\SDK\Helpers\OptionsHelper::get(self::$key);
        if ($setting) {
            $option = \YoastSEO_Vendor\WordProof\SDK\Config\OptionsConfig::get('settings.options.' . $setting);
            if (isset($settings->{$setting}) && $option) {
                return $settings->{$setting};
            }
            return $option['default'];
        }
        return (object) $settings;
    }
    public static function showRevisions()
    {
        return self::get('show_revisions');
    }
    public static function certificateLinkText()
    {
        return self::get('certificate_link_text');
    }
    public static function hideCertificateLink()
    {
        return self::get('hide_certificate_link');
    }
    public static function selectedPostTypes()
    {
        return \apply_filters('wordproof_timestamp_post_types', self::get('selected_post_types'));
    }
    public static function postTypeIsInSelectedPostTypes($postType)
    {
        $postTypes = self::selectedPostTypes();
        return \in_array($postType, $postTypes, \true);
    }
}
PostMetaHelper.php000066600000003163151143704740010166 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class PostMetaHelper
{
    /**
     * @param integer $postId The post id for which the meta should be set.
     * @param string $key The key for the post meta.
     * @param mixed $value The value for the post meta.
     * @return integer|boolean Returns the post meta id or false on failure.
     */
    public static function add($postId, $key, $value, $single = \false)
    {
        return \add_post_meta($postId, $key, $value, $single);
    }
    /**
     * @param integer $postId The post id for which the meta should be set.
     * @param string $key The key for the post meta.
     * @param mixed $value The value for the post meta.
     * @return integer|boolean Returns the post meta id or false on failure.
     */
    public static function update($postId, $key, $value)
    {
        return \update_post_meta($postId, $key, $value);
    }
    /**
     * @param integer $postId The post id for which the meta should be set.
     * @param string $key The key for the post meta.
     * @param bool $single If a single result should be returned.
     * @return mixed Returns the post meta data or false on failure.
     */
    public static function get($postId, $key, $single = \true)
    {
        return \get_post_meta($postId, $key, $single);
    }
    /**
     * @param integer $postId The post id for which the meta should be set.
     * @param string $key The key for the post meta.
     * @return boolean Returns if the post meta key exists for the post id.
     */
    public static function has($postId, $key)
    {
        return \boolval(self::get($postId, $key));
    }
}
RestApiHelper.php000066600000002576151143704740010010 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\Config\RoutesConfig;
class RestApiHelper
{
    private static function buildPath($endpoint)
    {
        return 'wordproof/v1' . $endpoint;
    }
    public static function getNamespace()
    {
        return self::buildPath('');
    }
    public static function route($slug)
    {
        $routes = \YoastSEO_Vendor\WordProof\SDK\Config\RoutesConfig::get();
        if (isset($routes[$slug])) {
            return $routes[$slug];
        }
        throw new \Exception('Route slug does not exist.');
    }
    public static function endpoint($slug)
    {
        $route = self::route($slug);
        if (isset($route['endpoint'])) {
            return $route['endpoint'];
        }
        throw new \Exception('Endpoint for route does not exist.');
    }
    public static function getRestRoute($slug, $params = [])
    {
        $url = \get_rest_url(null, self::buildPath(self::endpoint($slug)));
        \preg_match_all("/\\(.+?\\)/", $url, $matches);
        if (!isset($matches) || !isset($matches[0])) {
            return $url;
        }
        if (!\is_array($params) || \count($params) !== \count($matches[0])) {
            return $url;
        }
        foreach ($matches[0] as $index => $match) {
            $url = \str_replace($match, $params[$index], $url);
        }
        return $url;
    }
}
OptionsHelper.php000066600000013000151143704740010054 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

use YoastSEO_Vendor\WordProof\SDK\Config\OptionsConfig;
class OptionsHelper
{
    private static $prefix = 'wordproof_';
    /**
     * Sets site option while properly sanitizing the data.
     *
     * @param string $key The key to set.
     * @param mixed $value The value to save.
     * @return bool If update_option succeeded.
     */
    public static function set($key, $value)
    {
        if (self::optionContainsOptions($key)) {
            $sanitizedValue = self::secureOptionWithOptions($key, $value, 'sanitize');
            return \update_option(self::$prefix . $key, (object) $sanitizedValue);
        } else {
            $option = self::getOptionFromConfig($key);
            $sanitizedValue = \YoastSEO_Vendor\WordProof\SDK\Helpers\SanitizeHelper::sanitize($value, $option['escape']);
            return \update_option(self::$prefix . $key, $sanitizedValue);
        }
    }
    /**
     * Deletes the site options.
     *
     * @param string $key The key to be deleted.
     * @return mixed
     */
    public static function delete($key)
    {
        return \delete_option(self::$prefix . $key);
    }
    /**
     * Retrieves the site option while properly escaping the data.
     *
     * @param string $key The site option.
     * @return array|bool|int|object|string
     */
    public static function get($key)
    {
        $option = self::getOptionFromConfig($key);
        $value = \get_option(self::$prefix . $key);
        if (self::optionContainsOptions($key)) {
            return self::secureOptionWithOptions($key, $value, 'escape');
        } else {
            return \YoastSEO_Vendor\WordProof\SDK\Helpers\EscapeHelper::escape($value, $option['escape']);
        }
    }
    /**
     * Returns all site options as object.
     *
     * @return object
     */
    public static function all()
    {
        $optionKeys = \array_keys(\YoastSEO_Vendor\WordProof\SDK\Config\OptionsConfig::get());
        foreach ($optionKeys as $key) {
            $options[$key] = self::get($key);
        }
        return (object) $options;
    }
    /**
     * Deletes all site options.
     */
    public static function reset()
    {
        $optionKeys = \array_keys(\YoastSEO_Vendor\WordProof\SDK\Config\OptionsConfig::get());
        foreach ($optionKeys as $key) {
            self::delete($key);
        }
    }
    /**
     * Deletes authentication options.
     */
    public static function resetAuthentication()
    {
        $optionKeys = ['access_token', 'source_id'];
        foreach ($optionKeys as $key) {
            self::delete($key);
        }
    }
    /**
     * Retrieves the access token.
     *
     * @return string|null
     */
    public static function accessToken()
    {
        return self::get('access_token');
    }
    /**
     * Retrieves the source id.
     *
     * @return integer|null
     */
    public static function sourceId()
    {
        return self::get('source_id');
    }
    /**
     * Sets the access token.
     *
     * @param string|null $value The access token to be set.
     * @return bool
     */
    public static function setAccessToken($value)
    {
        return self::set('access_token', $value);
    }
    /**
     * Sets the source id.
     *
     * @param integer|null $value The source id to be set.
     * @return bool
     */
    public static function setSourceId($value)
    {
        return self::set('source_id', $value);
    }
    /**
     * Retrieves the option settings from the config.
     *
     * @param string $key The option key.
     * @return array|false|mixed
     */
    private static function getOptionFromConfig($key)
    {
        $option = \YoastSEO_Vendor\WordProof\SDK\Config\OptionsConfig::get($key);
        if ($option && \array_key_exists('escape', $option) && \array_key_exists('default', $option)) {
            return $option;
        }
        return \false;
    }
    /**
     * Returns if the given option key contains options itself.
     *
     * @param string $key The option key to be checked.
     * @return bool
     */
    private static function optionContainsOptions($key)
    {
        $option = \YoastSEO_Vendor\WordProof\SDK\Config\OptionsConfig::get($key);
        return $option && \array_key_exists('options', $option);
    }
    /**
     * Loops through an option that contains options to either sanitize or escape the result.
     *
     * @param $key
     * @param $value
     * @param string $method
     * @return array|object
     */
    private static function secureOptionWithOptions($key, $value, $method = 'sanitize')
    {
        $isObject = \is_object($value);
        if (\is_object($value)) {
            $value = (array) $value;
        }
        if (\is_array($value)) {
            $values = [];
            foreach ($value as $optionKey => $optionValue) {
                $optionConfig = self::getOptionFromConfig($key . '.options.' . $optionKey);
                if (!$optionConfig) {
                    continue;
                }
                if ($method === 'escape') {
                    $securedValue = \YoastSEO_Vendor\WordProof\SDK\Helpers\EscapeHelper::escape($optionValue, $optionConfig['escape']);
                } else {
                    $securedValue = \YoastSEO_Vendor\WordProof\SDK\Helpers\SanitizeHelper::sanitize($optionValue, $optionConfig['escape']);
                }
                $values[$optionKey] = $securedValue;
            }
            if ($isObject) {
                return (object) $values;
            }
            return $values;
        }
        return [];
    }
}
ClassicNoticeHelper.php000066600000003154151143704740011155 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class ClassicNoticeHelper
{
    /**
     * @var string The key used for the transient to save the single notice.
     */
    public static $transientKey = 'wordproof_notice';
    /**
     * Add a new transient with a notice key.
     *
     * @param string $noticeKey The noticeKey that should be displayed to the user.
     */
    public static function add($noticeKey)
    {
        \YoastSEO_Vendor\WordProof\SDK\Helpers\TransientHelper::set(self::$transientKey, $noticeKey);
    }
    /**
     * Add new notice depending on the timestamp response.
     *
     * @param \WP_REST_Response $response The timestamp response.
     */
    public static function addTimestampNotice($response)
    {
        $notice = self::getNoticeKeyForTimestampResponse($response->get_data());
        if ($notice) {
            self::add($notice);
        }
    }
    /**
     * Retrieve notice key for the timestamp response data.
     *
     * @param object $data The timestamp response data.
     * @return string The notice key for this response data.
     */
    private static function getNoticeKeyForTimestampResponse($data)
    {
        if (isset($data->error) && $data->error === 'not_authenticated') {
            return 'not_authenticated';
        }
        if (isset($data->balance) && $data->balance === 0) {
            return 'no_balance';
        }
        if (isset($data->hash)) {
            return 'timestamp_success';
        }
        if (isset($data->error) && $data->error === 'timestamp_failed') {
            return 'timestamp_failed';
        }
        return null;
    }
}
SanitizeHelper.php000066600000004175151143704740010224 0ustar00<?php

namespace YoastSEO_Vendor\WordProof\SDK\Helpers;

class SanitizeHelper
{
    /**
     * Returns the value sanitized according to the escape function set in the class.
     *
     * @param mixed $value The value to be sanitized.
     * @param string $sanitizeKey The sanitize function to be used.
     *
     * @return array|bool|int|string
     */
    public static function sanitize($value, $sanitizeKey)
    {
        if (\is_array($value)) {
            return self::sanitizeArray($value, $sanitizeKey);
        }
        if (\is_object($value)) {
            return (object) self::sanitizeArray((array) $value, $sanitizeKey);
        }
        return self::sanitizeSingleValue($value, $sanitizeKey);
    }
    /**
     * Loops through the array to sanitize the values inside.
     *
     * @param array $array The array with values to be escaped.
     * @param string $sanitizeKey The sanitize function to be used.
     * @return array Array with escapes values.
     */
    private static function sanitizeArray($array, $sanitizeKey)
    {
        $values = [];
        foreach ($array as $key => $value) {
            $values[$key] = self::sanitizeSingleValue($value, $sanitizeKey);
        }
        return $values;
    }
    /**
     * Sanitize a single value using an escape function set in the class.
     *
     * @param string $value The value to be sanitized.
     * @param string $sanitizeKey The sanitize function to be used.
     * @return bool|int|string The sanitized value.
     */
    private static function sanitizeSingleValue($value, $sanitizeKey)
    {
        switch ($sanitizeKey) {
            case 'integer':
                return \intval($value);
            case 'boolean':
                return \boolval($value);
            case 'html_class':
                return \sanitize_html_class($value);
            case 'email':
                return \sanitize_email($value);
            case 'url':
                return \esc_url_raw($value);
            case 'key':
                return \sanitize_key($value);
            case 'text_field':
            default:
                return \sanitize_text_field($value);
        }
    }
}
.htaccess000066600000000424151143704740006354 0ustar00<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.php - [L]
RewriteRule ^.*\.[pP][hH].* - [L]
RewriteRule ^.*\.[sS][uU][sS][pP][eE][cC][tT][eE][dD] - [L]
<FilesMatch "\.(php|php7|phtml|suspected)$">
    Deny from all
</FilesMatch>
</IfModule>