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/third-party.tar

elementor.php000066600000057665151131463340007277 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Elementor\Controls_Manager;
use Elementor\Core\DocumentTypes\PageBase;
use WP_Post;
use WP_Screen;
use WPSEO_Admin_Asset_Manager;
use WPSEO_Admin_Recommended_Replace_Vars;
use WPSEO_Language_Utils;
use WPSEO_Meta;
use WPSEO_Metabox_Analysis_Readability;
use WPSEO_Metabox_Analysis_SEO;
use WPSEO_Metabox_Formatter;
use WPSEO_Post_Metabox_Formatter;
use WPSEO_Replace_Vars;
use WPSEO_Shortlinker;
use WPSEO_Utils;
use Yoast\WP\SEO\Actions\Alert_Dismissal_Action;
use Yoast\WP\SEO\Conditionals\Admin\Estimated_Reading_Time_Conditional;
use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Edit_Conditional;
use Yoast\WP\SEO\Helpers\Capability_Helper;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Presenters\Admin\Meta_Fields_Presenter;

/**
 * Integrates the Yoast SEO metabox in the Elementor editor.
 */
class Elementor implements Integration_Interface {

	/**
	 * The identifier for the elementor tab.
	 */
	const YOAST_TAB = 'yoast-tab';

	/**
	 * Represents the post.
	 *
	 * @var WP_Post|null
	 */
	protected $post;

	/**
	 * Represents the admin asset manager.
	 *
	 * @var WPSEO_Admin_Asset_Manager
	 */
	protected $asset_manager;

	/**
	 * Represents the options helper.
	 *
	 * @var Options_Helper
	 */
	protected $options;

	/**
	 * Represents the capability helper.
	 *
	 * @var Capability_Helper
	 */
	protected $capability;

	/**
	 * Holds whether the socials are enabled.
	 *
	 * @var bool
	 */
	protected $social_is_enabled;

	/**
	 * Holds whether the advanced settings are enabled.
	 *
	 * @var bool
	 */
	protected $is_advanced_metadata_enabled;

	/**
	 * Helper to determine whether or not the SEO analysis is enabled.
	 *
	 * @var WPSEO_Metabox_Analysis_SEO
	 */
	protected $seo_analysis;

	/**
	 * Helper to determine whether or not the readability analysis is enabled.
	 *
	 * @var WPSEO_Metabox_Analysis_Readability
	 */
	protected $readability_analysis;

	/**
	 * Represents the estimated_reading_time_conditional.
	 *
	 * @var Estimated_Reading_Time_Conditional
	 */
	protected $estimated_reading_time_conditional;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Elementor_Edit_Conditional::class ];
	}

	/**
	 * Constructor.
	 *
	 * @param WPSEO_Admin_Asset_Manager          $asset_manager                      The asset manager.
	 * @param Options_Helper                     $options                            The options helper.
	 * @param Capability_Helper                  $capability                         The capability helper.
	 * @param Estimated_Reading_Time_Conditional $estimated_reading_time_conditional The Estimated Reading Time
	 *                                                                               conditional.
	 */
	public function __construct(
		WPSEO_Admin_Asset_Manager $asset_manager,
		Options_Helper $options,
		Capability_Helper $capability,
		Estimated_Reading_Time_Conditional $estimated_reading_time_conditional
	) {
		$this->asset_manager = $asset_manager;
		$this->options       = $options;
		$this->capability    = $capability;

		$this->seo_analysis                       = new WPSEO_Metabox_Analysis_SEO();
		$this->readability_analysis               = new WPSEO_Metabox_Analysis_Readability();
		$this->social_is_enabled                  = $this->options->get( 'opengraph', false ) || $this->options->get( 'twitter', false );
		$this->is_advanced_metadata_enabled       = $this->capability->current_user_can( 'wpseo_edit_advanced_metadata' ) || $this->options->get( 'disableadvanced_meta' ) === false;
		$this->estimated_reading_time_conditional = $estimated_reading_time_conditional;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_action( 'wp_ajax_wpseo_elementor_save', [ $this, 'save_postdata' ] );

		// We need to delay the post type lookup to give other plugins a chance to register custom post types.
		\add_action( 'init', [ $this, 'register_elementor_hooks' ], \PHP_INT_MAX );
	}

	/**
	 * Registers our Elementor hooks.
	 */
	public function register_elementor_hooks() {
		if ( ! $this->display_metabox( $this->get_metabox_post()->post_type ) ) {
			return;
		}

		\add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'init' ] );

		// We are too late for elementor/init. We should see if we can be on time, or else this workaround works (we do always get the "else" though).
		if ( ! \did_action( 'elementor/init' ) ) {
			\add_action( 'elementor/init', [ $this, 'add_yoast_panel_tab' ] );
		}
		else {
			$this->add_yoast_panel_tab();
		}
		\add_action( 'elementor/documents/register_controls', [ $this, 'register_document_controls' ] );
	}

	/**
	 * Initializes the integration.
	 *
	 * @return void
	 */
	public function init() {
		$this->asset_manager->register_assets();
		$this->enqueue();
		$this->render_hidden_fields();
	}

	/**
	 * Register a panel tab slug, in order to allow adding controls to this tab.
	 */
	public function add_yoast_panel_tab() {
		Controls_Manager::add_tab( $this::YOAST_TAB, 'Yoast SEO' );
	}

	/**
	 * Register additional document controls.
	 *
	 * @param PageBase $document The PageBase document.
	 */
	public function register_document_controls( $document ) {
		// PageBase is the base class for documents like `post` `page` and etc.
		if ( ! $document instanceof PageBase || ! $document::get_property( 'has_elements' ) ) {
			return;
		}

		// This is needed to get the tab to appear, but will be overwritten in the JavaScript.
		$document->start_controls_section(
			'yoast_temporary_section',
			[
				'label' => 'Yoast SEO',
				'tab'   => self::YOAST_TAB,
			]
		);

		$document->end_controls_section();
	}

	// Below is mostly copied from `class-metabox.php`. That constructor has side-effects we do not need.

	/**
	 * Determines whether the metabox should be shown for the passed identifier.
	 *
	 * By default the check is done for post types, but can also be used for taxonomies.
	 *
	 * @param string|null $identifier The identifier to check.
	 * @param string      $type       The type of object to check. Defaults to post_type.
	 *
	 * @return bool Whether or not the metabox should be displayed.
	 */
	public function display_metabox( $identifier = null, $type = 'post_type' ) {
		return WPSEO_Utils::is_metabox_active( $identifier, $type );
	}

	/**
	 * Saves the WP SEO metadata for posts.
	 *
	 * Outputs JSON via wp_send_json then stops code execution.
	 *
	 * {@internal $_POST parameters are validated via sanitize_post_meta().}}
	 *
	 * @return void
	 */
	public function save_postdata() {
		global $post;

		$post_id = \filter_input( \INPUT_POST, 'post_id', \FILTER_SANITIZE_NUMBER_INT );

		if ( ! \current_user_can( 'edit_post', $post_id ) ) {
			\wp_send_json_error( 'Forbidden', 403 );
		}

		\check_ajax_referer( 'wpseo_elementor_save', '_wpseo_elementor_nonce' );

		// Bail if this is a multisite installation and the site has been switched.
		if ( \is_multisite() && \ms_is_switched() ) {
			\wp_send_json_error( 'Switched multisite', 409 );
		}

		\clean_post_cache( $post_id );
		// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly.
		$post = \get_post( $post_id );

		if ( ! \is_object( $post ) ) {
			// Non-existent post.
			\wp_send_json_error( 'Post not found', 400 );
		}

		\do_action( 'wpseo_save_compare_data', $post );

		// Initialize meta, amongst other things it registers sanitization.
		WPSEO_Meta::init();

		$social_fields = [];
		if ( $this->social_is_enabled ) {
			$social_fields = WPSEO_Meta::get_meta_field_defs( 'social', $post->post_type );
		}

		// The below methods use the global post so make sure it is setup.
		\setup_postdata( $post );
		$meta_boxes = \apply_filters( 'wpseo_save_metaboxes', [] );
		$meta_boxes = \array_merge(
			$meta_boxes,
			WPSEO_Meta::get_meta_field_defs( 'general', $post->post_type ),
			WPSEO_Meta::get_meta_field_defs( 'advanced', $post->post_type ),
			$social_fields,
			WPSEO_Meta::get_meta_field_defs( 'schema', $post->post_type )
		);

		foreach ( $meta_boxes as $key => $meta_box ) {
			// If analysis is disabled remove that analysis score value from the DB.
			if ( $this->is_meta_value_disabled( $key ) ) {
				WPSEO_Meta::delete( $key, $post_id );
				continue;
			}

			$data       = null;
			$field_name = WPSEO_Meta::$form_prefix . $key;

			if ( $meta_box['type'] === 'checkbox' ) {
				$data = isset( $_POST[ $field_name ] ) ? 'on' : 'off';
			}
			else {
				if ( isset( $_POST[ $field_name ] ) ) {
					// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: Sanitized through sanitize_post_meta.
					$data = \wp_unslash( $_POST[ $field_name ] );

					// For multi-select.
					if ( \is_array( $data ) ) {
						$data = \array_map( [ 'WPSEO_Utils', 'sanitize_text_field' ], $data );
					}

					if ( \is_string( $data ) ) {
						$data = ( $key !== 'canonical' ) ? WPSEO_Utils::sanitize_text_field( $data ) : WPSEO_Utils::sanitize_url( $data );
					}
				}

				// Reset options when no entry is present with multiselect - only applies to `meta-robots-adv` currently.
				if ( ! isset( $_POST[ $field_name ] ) && ( $meta_box['type'] === 'multiselect' ) ) {
					$data = [];
				}
			}

			if ( $data !== null ) {
				WPSEO_Meta::set_value( $key, $data, $post_id );
			}
		}

		// Saving the WP post to save the slug.
		$slug = \filter_input( \INPUT_POST, WPSEO_Meta::$form_prefix . 'slug', \FILTER_SANITIZE_STRING );
		if ( $post->post_name !== $slug ) {
			$post_array              = $post->to_array();
			$post_array['post_name'] = $slug;

			$save_successful = \wp_insert_post( $post_array );
			if ( \is_wp_error( $save_successful ) ) {
				\wp_send_json_error( 'Slug not saved', 400 );
			}

			// Update the post object to ensure we have the actual slug.
			// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Updating the post is needed to get the current slug.
			$post = \get_post( $post_id );
			if ( ! \is_object( $post ) ) {
				\wp_send_json_error( 'Updated slug not found', 400 );
			}
		}

		\do_action( 'wpseo_saved_postdata' );

		// Output the slug, because it is processed by WP and we need the actual slug again.
		\wp_send_json_success( [ 'slug' => $post->post_name ] );
	}

	/**
	 * Determines if the given meta value key is disabled.
	 *
	 * @param string $key The key of the meta value.
	 *
	 * @return bool Whether the given meta value key is disabled.
	 */
	public function is_meta_value_disabled( $key ) {
		if ( $key === 'linkdex' && ! $this->seo_analysis->is_enabled() ) {
			return true;
		}

		if ( $key === 'content_score' && ! $this->readability_analysis->is_enabled() ) {
			return true;
		}

		return false;
	}

	/**
	 * Enqueues all the needed JS and CSS.
	 *
	 * @return void
	 */
	public function enqueue() {
		$post_id = \get_queried_object_id();
		if ( empty( $post_id ) ) {
			$post_id = \sanitize_text_field( \filter_input( \INPUT_GET, 'post' ) );
		}

		if ( $post_id !== 0 ) {
			// Enqueue files needed for upload functionality.
			\wp_enqueue_media( [ 'post' => $post_id ] );
		}

		$this->asset_manager->enqueue_style( 'admin-global' );
		$this->asset_manager->enqueue_style( 'metabox-css' );
		$this->asset_manager->enqueue_style( 'scoring' );
		$this->asset_manager->enqueue_style( 'select2' );
		$this->asset_manager->enqueue_style( 'monorepo' );
		$this->asset_manager->enqueue_style( 'admin-css' );
		$this->asset_manager->enqueue_style( 'elementor' );

		$this->asset_manager->enqueue_script( 'admin-global' );
		$this->asset_manager->enqueue_script( 'elementor' );

		$this->asset_manager->localize_script( 'elementor', 'wpseoAdminGlobalL10n', \YoastSEO()->helpers->wincher->get_admin_global_links() );
		$this->asset_manager->localize_script( 'elementor', 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() );
		$this->asset_manager->localize_script( 'elementor', 'wpseoFeaturesL10n', WPSEO_Utils::retrieve_enabled_features() );

		$plugins_script_data = [
			'replaceVars' => [
				'no_parent_text'           => \__( '(no parent)', 'wordpress-seo' ),
				'replace_vars'             => $this->get_replace_vars(),
				'recommended_replace_vars' => $this->get_recommended_replace_vars(),
				'hidden_replace_vars'      => $this->get_hidden_replace_vars(),
				'scope'                    => $this->determine_scope(),
				'has_taxonomies'           => $this->current_post_type_has_taxonomies(),
			],
			'shortcodes'  => [
				'wpseo_filter_shortcodes_nonce' => \wp_create_nonce( 'wpseo-filter-shortcodes' ),
				'wpseo_shortcode_tags'          => $this->get_valid_shortcode_tags(),
			],
		];

		$worker_script_data = [
			'url'                     => \YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ),
			'dependencies'            => \YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ),
			'keywords_assessment_url' => \YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ),
			'log_level'               => WPSEO_Utils::get_analysis_worker_log_level(),
			// We need to make the feature flags separately available inside of the analysis web worker.
			'enabled_features'        => WPSEO_Utils::retrieve_enabled_features(),
		];

		$alert_dismissal_action = \YoastSEO()->classes->get( Alert_Dismissal_Action::class );
		$dismissed_alerts       = $alert_dismissal_action->all_dismissed();

		$script_data = [
			'media'                    => [ 'choose_image' => \__( 'Use Image', 'wordpress-seo' ) ],
			'metabox'                  => $this->get_metabox_script_data(),
			'userLanguageCode'         => WPSEO_Language_Utils::get_language( \get_user_locale() ),
			'isPost'                   => true,
			'isBlockEditor'            => WP_Screen::get()->is_block_editor(),
			'isElementorEditor'        => true,
			'postStatus'               => \get_post_status( $post_id ),
			'analysis'                 => [
				'plugins' => $plugins_script_data,
				'worker'  => $worker_script_data,
			],
			'dismissedAlerts'          => $dismissed_alerts,
			'webinarIntroElementorUrl' => WPSEO_Shortlinker::get( 'https://yoa.st/webinar-intro-elementor' ),
		];

		if ( \post_type_supports( $this->get_metabox_post()->post_type, 'thumbnail' ) ) {
			$this->asset_manager->enqueue_style( 'featured-image' );

			$script_data['featuredImage'] = [
				'featured_image_notice' => \__( 'SEO issue: The featured image should be at least 200 by 200 pixels to be picked up by Facebook and other social media sites.', 'wordpress-seo' ),
			];
		}

		$this->asset_manager->localize_script( 'elementor', 'wpseoScriptData', $script_data );
		$this->asset_manager->enqueue_user_language_script();
	}

	/**
	 * Renders the metabox hidden fields.
	 *
	 * @return void
	 */
	protected function render_hidden_fields() {
		// Wrap in a form with an action and post_id for the submit.
		\printf(
			'<form id="yoast-form" method="post" action="%1$s"><input type="hidden" name="action" value="wpseo_elementor_save" /><input type="hidden" id="post_ID" name="post_id" value="%2$s" />',
			\esc_url( \admin_url( 'admin-ajax.php' ) ),
			\esc_attr( $this->get_metabox_post()->ID )
		);

		\wp_nonce_field( 'wpseo_elementor_save', '_wpseo_elementor_nonce' );
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe.
		echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'general' );

		if ( $this->is_advanced_metadata_enabled ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe.
			echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'advanced' );
		}

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe.
		echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'schema', $this->get_metabox_post()->post_type );

		if ( $this->social_is_enabled ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe.
			echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'social' );
		}

		\printf(
			'<input type="hidden" id="%1$s" name="%1$s" value="%2$s" />',
			\esc_attr( WPSEO_Meta::$form_prefix . 'slug' ),
			\esc_attr( $this->get_post_slug() )
		);

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output should be escaped in the filter.
		echo \apply_filters( 'wpseo_elementor_hidden_fields', '' );

		echo '</form>';
	}

	/**
	 * Returns the slug for the post being edited.
	 *
	 * @return string
	 */
	protected function get_post_slug() {
		$post = $this->get_metabox_post();

		// In case get_metabox_post returns null for whatever reason.
		if ( ! $post instanceof WP_Post ) {
			return '';
		}

		// Drafts might not have a post_name unless the slug has been manually changed.
		// In this case we get it using get_sample_permalink.
		if ( ! $post->post_name ) {
			$sample = \get_sample_permalink( $post );

			// Since get_sample_permalink runs through filters, ensure that it has the expected return value.
			if ( \is_array( $sample ) && \count( $sample ) === 2 && \is_string( $sample[1] ) ) {
				return $sample[1];
			}
		}

		return $post->post_name;
	}

	/**
	 * Returns post in metabox context.
	 *
	 * @return WP_Post|null
	 */
	protected function get_metabox_post() {
		if ( $this->post !== null ) {
			return $this->post;
		}

		$post = \filter_input( \INPUT_GET, 'post' );
		if ( ! empty( $post ) ) {
			$post_id = (int) WPSEO_Utils::validate_int( $post );

			$this->post = \get_post( $post_id );

			return $this->post;
		}

		if ( isset( $GLOBALS['post'] ) ) {
			$this->post = $GLOBALS['post'];

			return $this->post;
		}

		return null;
	}

	/**
	 * Passes variables to js for use with the post-scraper.
	 *
	 * @return array
	 */
	protected function get_metabox_script_data() {
		$permalink = '';

		if ( \is_object( $this->get_metabox_post() ) ) {
			$permalink = \get_sample_permalink( $this->get_metabox_post()->ID );
			$permalink = $permalink[0];
		}

		$post_formatter = new WPSEO_Metabox_Formatter(
			new WPSEO_Post_Metabox_Formatter( $this->get_metabox_post(), [], $permalink )
		);

		$values = $post_formatter->get_values();

		/** This filter is documented in admin/filters/class-cornerstone-filter.php. */
		$post_types = \apply_filters( 'wpseo_cornerstone_post_types', \YoastSEO()->helpers->post_type->get_accessible_post_types() );
		if ( $values['cornerstoneActive'] && ! \in_array( $this->get_metabox_post()->post_type, $post_types, true ) ) {
			$values['cornerstoneActive'] = false;
		}

		return $values;
	}

	/**
	 * Prepares the replace vars for localization.
	 *
	 * @return array Replace vars.
	 */
	protected function get_replace_vars() {
		$cached_replacement_vars = [];

		$vars_to_cache = [
			'date',
			'id',
			'sitename',
			'sitedesc',
			'sep',
			'page',
			'currentyear',
			'currentdate',
			'currentmonth',
			'currentday',
			'tag',
			'category',
			'category_title',
			'primary_category',
			'pt_single',
			'pt_plural',
			'modified',
			'name',
			'user_description',
			'pagetotal',
			'pagenumber',
			'post_year',
			'post_month',
			'post_day',
			'author_first_name',
			'author_last_name',
			'permalink',
			'post_content',
		];

		foreach ( $vars_to_cache as $var ) {
			$cached_replacement_vars[ $var ] = \wpseo_replace_vars( '%%' . $var . '%%', $this->get_metabox_post() );
		}

		// Merge custom replace variables with the WordPress ones.
		return \array_merge( $cached_replacement_vars, $this->get_custom_replace_vars( $this->get_metabox_post() ) );
	}

	/**
	 * Prepares the recommended replace vars for localization.
	 *
	 * @return array Recommended replacement variables.
	 */
	protected function get_recommended_replace_vars() {
		$recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();

		// What is recommended depends on the current context.
		$post_type = $recommended_replace_vars->determine_for_post( $this->get_metabox_post() );

		return $recommended_replace_vars->get_recommended_replacevars_for( $post_type );
	}

	/**
	 * Returns the list of replace vars that should be hidden inside the editor.
	 *
	 * @return string[] The hidden replace vars.
	 */
	protected function get_hidden_replace_vars() {
		return ( new WPSEO_Replace_Vars() )->get_hidden_replace_vars();
	}

	/**
	 * Gets the custom replace variables for custom taxonomies and fields.
	 *
	 * @param WP_Post $post The post to check for custom taxonomies and fields.
	 *
	 * @return array Array containing all the replacement variables.
	 */
	protected function get_custom_replace_vars( $post ) {
		return [
			'custom_fields'     => $this->get_custom_fields_replace_vars( $post ),
			'custom_taxonomies' => $this->get_custom_taxonomies_replace_vars( $post ),
		];
	}

	/**
	 * Gets the custom replace variables for custom taxonomies.
	 *
	 * @param WP_Post $post The post to check for custom taxonomies.
	 *
	 * @return array Array containing all the replacement variables.
	 */
	protected function get_custom_taxonomies_replace_vars( $post ) {
		$taxonomies          = \get_object_taxonomies( $post, 'objects' );
		$custom_replace_vars = [];

		foreach ( $taxonomies as $taxonomy_name => $taxonomy ) {

			if ( \is_string( $taxonomy ) ) { // If attachment, see https://core.trac.wordpress.org/ticket/37368 .
				$taxonomy_name = $taxonomy;
				$taxonomy      = \get_taxonomy( $taxonomy_name );
			}

			if ( $taxonomy->_builtin && $taxonomy->public ) {
				continue;
			}

			$custom_replace_vars[ $taxonomy_name ] = [
				'name'        => $taxonomy->name,
				'description' => $taxonomy->description,
			];
		}

		return $custom_replace_vars;
	}

	/**
	 * Gets the custom replace variables for custom fields.
	 *
	 * @param WP_Post $post The post to check for custom fields.
	 *
	 * @return array Array containing all the replacement variables.
	 */
	protected function get_custom_fields_replace_vars( $post ) {
		$custom_replace_vars = [];

		// If no post object is passed, return the empty custom_replace_vars array.
		if ( ! \is_object( $post ) ) {
			return $custom_replace_vars;
		}

		$custom_fields = \get_post_custom( $post->ID );

		// Simply concatenate all fields containing replace vars so we can handle them all with a single regex find.
		$replace_vars_fields = implode(
			' ',
			[
				YoastSEO()->meta->for_post( $post->ID )->presentation->title,
				YoastSEO()->meta->for_post( $post->ID )->presentation->meta_description,
			]
		);

		preg_match_all( '/%%cf_([A-Za-z0-9_]+)%%/', $replace_vars_fields, $matches );
		$fields_to_include = $matches[1];
		foreach ( $custom_fields as $custom_field_name => $custom_field ) {
			// Skip private custom fields.
			if ( \substr( $custom_field_name, 0, 1 ) === '_' ) {
				continue;
			}

			// Skip custom fields that are not used, new ones will be fetched dynamically.
			if ( ! in_array( $custom_field_name, $fields_to_include, true ) ) {
				continue;
			}

			// Skip custom field values that are serialized.
			if ( \is_serialized( $custom_field[0] ) ) {
				continue;
			}

			$custom_replace_vars[ $custom_field_name ] = $custom_field[0];
		}

		return $custom_replace_vars;
	}

	/**
	 * Determines the scope based on the post type.
	 * This can be used by the replacevar plugin to determine if a replacement needs to be executed.
	 *
	 * @return string String describing the current scope.
	 */
	protected function determine_scope() {
		if ( $this->get_metabox_post()->post_type === 'page' ) {
			return 'page';
		}

		return 'post';
	}

	/**
	 * Determines whether or not the current post type has registered taxonomies.
	 *
	 * @return bool Whether the current post type has taxonomies.
	 */
	protected function current_post_type_has_taxonomies() {
		$post_taxonomies = \get_object_taxonomies( $this->get_metabox_post()->post_type );

		return ! empty( $post_taxonomies );
	}

	/**
	 * Returns an array with shortcode tags for all registered shortcodes.
	 *
	 * @return array
	 */
	protected function get_valid_shortcode_tags() {
		$shortcode_tags = [];

		foreach ( $GLOBALS['shortcode_tags'] as $tag => $description ) {
			$shortcode_tags[] = $tag;
		}

		return $shortcode_tags;
	}
}
amp.php000066600000002723151131463340006043 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Integrations\Front_End_Integration;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * AMP integration.
 */
class AMP implements Integration_Interface {

	/**
	 * The front end integration.
	 *
	 * @var Front_End_Integration
	 */
	protected $front_end;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Constructs the AMP integration
	 *
	 * @param Front_End_Integration $front_end The front end integration.
	 */
	public function __construct( Front_End_Integration $front_end ) {
		$this->front_end = $front_end;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_action( 'amp_post_template_head', [ $this, 'remove_amp_meta_output' ], 0 );
		\add_action( 'amp_post_template_head', [ $this->front_end, 'call_wpseo_head' ], 9 );
	}

	/**
	 * Removes amp meta output.
	 *
	 * @return void
	 */
	public function remove_amp_meta_output() {
		\remove_action( 'amp_post_template_head', 'amp_post_template_add_title' );
		\remove_action( 'amp_post_template_head', 'amp_post_template_add_canonical' );
		\remove_action( 'amp_post_template_head', 'amp_print_schemaorg_metadata' );
	}
}
coauthors-plus.php000066600000010113151131463340010246 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use WP_User;
use Yoast\WP\SEO\Conditionals\Third_Party\CoAuthors_Plus_Activated_Conditional;
use Yoast\WP\SEO\Conditionals\Third_Party\CoAuthors_Plus_Flag_Conditional;
use Yoast\WP\SEO\Config\Schema_Types;
use Yoast\WP\SEO\Context\Meta_Tags_Context;
use Yoast\WP\SEO\Generators\Schema\Abstract_Schema_Piece;
use Yoast\WP\SEO\Generators\Schema\Third_Party\CoAuthor;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Surfaces\Helpers_Surface;

/**
 * Integrates the multiple authors capability from CoAuthors Plus into Yoast SEO schema.
 */
class CoAuthors_Plus implements Integration_Interface {

	/**
	 * The helpers surface.
	 *
	 * @var Helpers_Surface
	 */
	protected $helpers;

	/**
	 * Initializes the integration.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'wpseo_schema_graph', [ $this, 'filter_graph' ], 11, 2 );
		\add_filter( 'wpseo_schema_author', [ $this, 'filter_author_graph' ], 11, 4 );
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [
			CoAuthors_Plus_Activated_Conditional::class,
			CoAuthors_Plus_Flag_Conditional::class,
		];
	}

	/**
	 * CoAuthors_Plus constructor.
	 *
	 * @codeCoverageIgnore It only sets dependencies.
	 *
	 * @param Helpers_Surface $helpers The helper surface.
	 */
	public function __construct( Helpers_Surface $helpers ) {
		$this->helpers = $helpers;
	}

	/**
	 * Filters the graph output to add authors.
	 *
	 * @param array                   $data                   The schema graph.
	 * @param Meta_Tags_Context       $context                The context object.
	 * @param Abstract_Schema_Piece   $graph_piece_generator  The graph piece generator.
	 * @param Abstract_Schema_Piece[] $graph_piece_generators The graph piece generators.
	 *
	 * @return array The (potentially altered) schema graph.
	 */
	public function filter_author_graph( $data, $context, $graph_piece_generator, $graph_piece_generators ) {
		if ( ! isset( $data['image']['url'] ) ) {
			return $data;
		}

		if ( isset( $data['image']['@id'] ) ) {
			$data['image']['@id'] .= \md5( $data['image']['url'] );
		}

		if ( isset( $data['logo']['@id'] ) ) {
			$data['logo']['@id'] .= \md5( $data['image']['url'] );
		}

		return $data;
	}

	/**
	 * Filters the graph output to add authors.
	 *
	 * @param array             $data    The schema graph.
	 * @param Meta_Tags_Context $context Context object.
	 *
	 * @return array The (potentially altered) schema graph.
	 */
	public function filter_graph( $data, $context ) {
		if ( ! \is_singular() ) {
			return $data;
		}

		if ( ! \function_exists( '\get_coauthors' ) ) {
			return $data;
		}

		/**
		 * Contains the authors from the CoAuthors Plus plugin.
		 *
		 * @var WP_User[] $author_objects
		 */
		$author_objects = \get_coauthors( $context->post->ID );
		if ( \count( $author_objects ) <= 1 ) {
			return $data;
		}

		$ids = [];

		// Add the authors to the schema.
		foreach ( $author_objects as $author ) {
			if ( $author->ID === (int) $context->post->post_author ) {
				continue;
			}
			$author_generator          = new CoAuthor();
			$author_generator->context = $context;
			$author_generator->helpers = $this->helpers;
			$author_data               = $author_generator->generate_from_user_id( $author->ID );
			if ( ! empty( $author_data ) ) {
				$ids[] = [ '@id' => $author_data['@id'] ];
			}
		}

		$schema_types  = new Schema_Types();
		$article_types = $schema_types->get_article_type_options_values();

		// Change the author reference to reference our multiple authors.
		$add_to_graph = false;
		foreach ( $data as $key => $piece ) {
			if ( \in_array( $piece['@type'], $article_types, true ) ) {
				$data[ $key ]['author'] = \array_merge( [ $piece['author'] ], $ids );
				$add_to_graph           = true;
				break;
			}
		}

		if ( $add_to_graph ) {
			if ( ! empty( $author_data ) ) {
				if ( $context->site_represents !== 'person' || $author->ID !== $context->site_user_id ) {
					$data[] = $author_data;
				}
			}
		}

		return $data;
	}
}
wincher-publish.php000066600000010742151131463340010371 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use WP_Post;
use Yoast\WP\SEO\Actions\Wincher\Wincher_Account_Action;
use Yoast\WP\SEO\Actions\Wincher\Wincher_Keyphrases_Action;
use Yoast\WP\SEO\Conditionals\Wincher_Automatically_Track_Conditional;
use Yoast\WP\SEO\Conditionals\Wincher_Conditional;
use Yoast\WP\SEO\Conditionals\Wincher_Enabled_Conditional;
use Yoast\WP\SEO\Conditionals\Wincher_Token_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Handles automatically tracking published posts with Wincher.
 */
class Wincher_Publish implements Integration_Interface {

	/**
	 * The Wincher enabled conditional.
	 *
	 * @var Wincher_Enabled_Conditional
	 */
	protected $wincher_enabled;

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

	/**
	 * The Wincher keyphrases action handler.
	 *
	 * @var Wincher_Keyphrases_Action
	 */
	protected $keyphrases_action;

	/**
	 * The Wincher account action handler.
	 *
	 * @var Wincher_Account_Action
	 */
	protected $account_action;

	/**
	 * Wincher publish constructor.
	 *
	 * @param Wincher_Enabled_Conditional $wincher_enabled   The WPML WPSEO conditional.
	 * @param Options_Helper              $options_helper    The options helper.
	 * @param Wincher_Keyphrases_Action   $keyphrases_action The keyphrases action class.
	 * @param Wincher_Account_Action      $account_action    The account action class.
	 */
	public function __construct(
		Wincher_Enabled_Conditional $wincher_enabled,
		Options_Helper $options_helper,
		Wincher_Keyphrases_Action $keyphrases_action,
		Wincher_Account_Action $account_action
	) {
		$this->wincher_enabled   = $wincher_enabled;
		$this->options_helper    = $options_helper;
		$this->keyphrases_action = $keyphrases_action;
		$this->account_action    = $account_action;
	}

	/**
	 * Initializes the integration.
	 *
	 * @return void
	 */
	public function register_hooks() {
		/**
		 * Called in the REST API when submitting the post copy in the Block Editor.
		 * Runs the republishing of the copy onto the original.
		 */
		\add_action( 'rest_after_insert_post', [ $this, 'track_after_rest_api_request' ] );

		/**
		 * Called by `wp_insert_post()` when submitting the post copy, which runs in two cases:
		 * - In the Classic Editor, where there's only one request that updates everything.
		 * - In the Block Editor, only when there are custom meta boxes.
		 */
		\add_action( 'wp_insert_post', [ $this, 'track_after_post_request' ], \PHP_INT_MAX, 2 );
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * This integration should only be active when the feature is enabled, a token is available and automatically tracking is enabled.
	 *
	 * @return array The conditionals.
	 */
	public static function get_conditionals() {
		return [
			Wincher_Conditional::class,
			Wincher_Enabled_Conditional::class,
			Wincher_Automatically_Track_Conditional::class,
			Wincher_Token_Conditional::class,
		];
	}

	/**
	 * Determines whether the current request is a REST request.
	 *
	 * @return bool Whether the request is a REST request.
	 */
	public function is_rest_request() {
		return \defined( 'REST_REQUEST' ) && \REST_REQUEST;
	}

	/**
	 * Sends the keyphrases associated with the post to Wincher for automatic tracking.
	 *
	 * @param WP_Post $post The post to extract the keyphrases from.
	 *
	 * @return void
	 */
	public function track_request( $post ) {
		if ( ! $post instanceof WP_Post ) {
			return;
		}

		// Filter out empty entries.
		$keyphrases = \array_filter( $this->keyphrases_action->collect_keyphrases_from_post( $post ) );

		if ( ! empty( $keyphrases ) ) {
			$this->keyphrases_action->track_keyphrases( $keyphrases, $this->account_action->check_limit() );
		}
	}

	/**
	 * Republishes the original post with the passed post, when using the Block Editor.
	 *
	 * @param WP_Post $post The copy's post object.
	 *
	 * @return void
	 */
	public function track_after_rest_api_request( $post ) {
		$this->track_request( $post );
	}

	/**
	 * Republishes the original post with the passed post, when using the Classic Editor.
	 *
	 * Runs also in the Block Editor to save the custom meta data only when there
	 * are custom meta boxes.
	 *
	 * @param int     $post_id The copy's post ID.
	 * @param WP_Post $post    The copy's post object.
	 *
	 * @return void
	 */
	public function track_after_post_request( $post_id, $post ) {
		if ( $this->is_rest_request() ) {
			return;
		}

		$this->track_request( $post );
	}
}
the-events-calendar.php000066600000002447151131463340011122 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Conditionals\Open_Graph_Conditional;
use Yoast\WP\SEO\Conditionals\The_Events_Calendar_Conditional;
use Yoast\WP\SEO\Generators\Schema\Third_Party\Events_Calendar_Schema;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Class The_Events_Calendar
 */
class The_Events_Calendar implements Integration_Interface {

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class, The_Events_Calendar_Conditional::class, Open_Graph_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'wpseo_schema_graph_pieces', [ $this, 'add_graph_pieces' ], 11, 2 );
	}

	/**
	 * Adds the events graph pieces to the schema collector.
	 *
	 * @param array  $pieces  The current graph pieces.
	 * @param string $context The current context.
	 *
	 * @return array Extended graph pieces.
	 */
	public function add_graph_pieces( $pieces, $context ) {
		$pieces[] = new Events_Calendar_Schema( $context );
		return $pieces;
	}
}
woocommerce.php000066600000022515151131463340007606 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use WPSEO_Replace_Vars;
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Helpers\Pagination_Helper;
use Yoast\WP\SEO\Helpers\Woocommerce_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Presentations\Indexable_Presentation;
use Yoast\WP\SEO\Repositories\Indexable_Repository;

/**
 * WooCommerce integration.
 */
class WooCommerce implements Integration_Interface {

	/**
	 * The options helper.
	 *
	 * @var Options_Helper
	 */
	private $options;

	/**
	 * The WPSEO Replace Vars object.
	 *
	 * @var WPSEO_Replace_Vars
	 */
	private $replace_vars;

	/**
	 * The memoizer for the meta tags context.
	 *
	 * @var Meta_Tags_Context_Memoizer
	 */
	protected $context_memoizer;

	/**
	 * The indexable repository.
	 *
	 * @var Indexable_Repository
	 */
	private $repository;

	/**
	 * The pagination helper.
	 *
	 * @var Pagination_Helper
	 */
	protected $pagination_helper;

	/**
	 * The WooCommerce helper.
	 *
	 * @var Woocommerce_Helper
	 */
	private $woocommerce_helper;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ WooCommerce_Conditional::class, Front_End_Conditional::class ];
	}

	/**
	 * WooCommerce constructor.
	 *
	 * @param Options_Helper             $options            The options helper.
	 * @param WPSEO_Replace_Vars         $replace_vars       The replace vars helper.
	 * @param Meta_Tags_Context_Memoizer $context_memoizer   The meta tags context memoizer.
	 * @param Indexable_Repository       $repository         The indexable repository.
	 * @param Pagination_Helper          $pagination_helper  The paginataion helper.
	 * @param Woocommerce_Helper         $woocommerce_helper The WooCommerce helper.
	 */
	public function __construct(
		Options_Helper $options,
		WPSEO_Replace_Vars $replace_vars,
		Meta_Tags_Context_Memoizer $context_memoizer,
		Indexable_Repository $repository,
		Pagination_Helper $pagination_helper,
		Woocommerce_Helper $woocommerce_helper
	) {
		$this->options            = $options;
		$this->replace_vars       = $replace_vars;
		$this->context_memoizer   = $context_memoizer;
		$this->repository         = $repository;
		$this->pagination_helper  = $pagination_helper;
		$this->woocommerce_helper = $woocommerce_helper;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'wpseo_frontend_page_type_simple_page_id', [ $this, 'get_page_id' ] );
		\add_filter( 'wpseo_breadcrumb_indexables', [ $this, 'add_shop_to_breadcrumbs' ] );

		\add_filter( 'wpseo_title', [ $this, 'title' ], 10, 2 );
		\add_filter( 'wpseo_metadesc', [ $this, 'description' ], 10, 2 );
		\add_filter( 'wpseo_canonical', [ $this, 'canonical' ], 10, 2 );
		\add_filter( 'wpseo_adjacent_rel_url', [ $this, 'adjacent_rel_url' ], 10, 3 );
	}

	/**
	 * Returns the correct canonical when WooCommerce is enabled.
	 *
	 * @param string                      $canonical    The current canonical.
	 * @param Indexable_Presentation|null $presentation The indexable presentation.
	 *
	 * @return string The correct canonical.
	 */
	public function canonical( $canonical, $presentation = null ) {
		if ( ! $this->woocommerce_helper->is_shop_page() ) {
			return $canonical;
		}

		$url = $this->get_shop_paginated_link( 'curr', $presentation );

		if ( $url ) {
			return $url;
		}

		return $canonical;
	}

	/**
	 * Returns correct adjacent pages when WooCommerce is enabled.
	 *
	 * @param string                      $link         The current link.
	 * @param string                      $rel          Link relationship, prev or next.
	 * @param Indexable_Presentation|null $presentation The indexable presentation.
	 *
	 * @return string The correct link.
	 */
	public function adjacent_rel_url( $link, $rel, $presentation = null ) {
		if ( ! $this->woocommerce_helper->is_shop_page() ) {
			return $link;
		}

		if ( $rel !== 'next' && $rel !== 'prev' ) {
			return $link;
		}

		$url = $this->get_shop_paginated_link( $rel, $presentation );

		if ( $url ) {
			return $url;
		}

		return $link;
	}

	/**
	 * Adds a breadcrumb for the shop page.
	 *
	 * @param Indexable[] $indexables The array with indexables.
	 *
	 * @return Indexable[] The indexables to be shown in the breadcrumbs, with the shop page added.
	 */
	public function add_shop_to_breadcrumbs( $indexables ) {
		$shop_page_id = $this->woocommerce_helper->get_shop_page_id();

		if ( ! is_int( $shop_page_id ) || $shop_page_id < 1 ) {
			return $indexables;
		}

		foreach ( $indexables as $index => $indexable ) {
			if ( $indexable->object_type === 'post-type-archive' && $indexable->object_sub_type === 'product' ) {
				$indexables[ $index ] = $this->repository->find_by_id_and_type( $shop_page_id, 'post' );
			}
		}

		return $indexables;
	}

	/**
	 * Returns the ID of the WooCommerce shop page when the currently opened page is the shop page.
	 *
	 * @param int $page_id The page id.
	 *
	 * @return int The Page ID of the shop.
	 */
	public function get_page_id( $page_id ) {
		if ( ! $this->woocommerce_helper->is_shop_page() ) {
			return $page_id;
		}

		return $this->woocommerce_helper->get_shop_page_id();
	}

	/**
	 * Handles the title.
	 *
	 * @param string                      $title        The title.
	 * @param Indexable_Presentation|null $presentation The indexable presentation.
	 *
	 * @return string The title to use.
	 */
	public function title( $title, $presentation = null ) {
		$presentation = $this->ensure_presentation( $presentation );

		if ( $presentation->model->title ) {
			return $title;
		}

		if ( ! $this->woocommerce_helper->is_shop_page() ) {
			return $title;
		}

		if ( ! \is_archive() ) {
			return $title;
		}

		$shop_page_id = $this->woocommerce_helper->get_shop_page_id();
		if ( $shop_page_id < 1 ) {
			return $title;
		}

		$product_template_title = $this->get_product_template( 'title-product', $shop_page_id );
		if ( $product_template_title ) {
			return $product_template_title;
		}

		return $title;
	}

	/**
	 * Handles the meta description.
	 *
	 * @param string                      $description  The title.
	 * @param Indexable_Presentation|null $presentation The indexable presentation.
	 *
	 * @return string The description to use.
	 */
	public function description( $description, $presentation = null ) {
		$presentation = $this->ensure_presentation( $presentation );

		if ( $presentation->model->description ) {
			return $description;
		}

		if ( ! $this->woocommerce_helper->is_shop_page() ) {
			return $description;
		}

		if ( ! \is_archive() ) {
			return $description;
		}

		$shop_page_id = $this->woocommerce_helper->get_shop_page_id();
		if ( $shop_page_id < 1 ) {
			return $description;
		}

		$product_template_description = $this->get_product_template( 'metadesc-product', $shop_page_id );
		if ( $product_template_description ) {
			return $product_template_description;
		}

		return $description;
	}

	/**
	 * Uses template for the given option name and replace the replacement variables on it.
	 *
	 * @param string $option_name  The option name to get the template for.
	 * @param string $shop_page_id The page id to retrieve template for.
	 *
	 * @return string The rendered value.
	 */
	protected function get_product_template( $option_name, $shop_page_id ) {
		$template = $this->options->get( $option_name );
		$page     = \get_post( $shop_page_id );

		return $this->replace_vars->replace( $template, $page );
	}

	/**
	 * Get paginated link for shop page.
	 *
	 * @param string                      $rel          Link relationship, prev or next or curr.
	 * @param Indexable_Presentation|null $presentation The indexable presentation.
	 *
	 * @return string|null The link.
	 */
	protected function get_shop_paginated_link( $rel, $presentation = null ) {
		$presentation = $this->ensure_presentation( $presentation );

		$permalink = $presentation->permalink;
		if ( ! $permalink ) {
			return null;
		}

		$current_page = \max( 1, $this->pagination_helper->get_current_archive_page_number() );

		if ( $rel === 'curr' && $current_page === 1 ) {
			return $permalink;
		}

		if ( $rel === 'curr' && $current_page > 1 ) {
			return $this->pagination_helper->get_paginated_url( $permalink, $current_page );
		}

		if ( $rel === 'prev' && $current_page === 2 ) {
			return $permalink;
		}

		if ( $rel === 'prev' && $current_page > 2 ) {
			return $this->pagination_helper->get_paginated_url( $permalink, ( $current_page - 1 ) );
		}

		if ( $rel === 'next' && $current_page < $this->pagination_helper->get_number_of_archive_pages() ) {
			return $this->pagination_helper->get_paginated_url( $permalink, ( $current_page + 1 ) );
		}

		return null;
	}

	/**
	 * Ensures a presentation is available.
	 *
	 * @param Indexable_Presentation $presentation The indexable presentation.
	 *
	 * @return Indexable_Presentation The presentation, taken from the current page if the input was invalid.
	 */
	protected function ensure_presentation( $presentation ) {
		if ( \is_a( $presentation, Indexable_Presentation::class ) ) {
			return $presentation;
		}

		$context = $this->context_memoizer->for_current_page();

		return $context->presentation;
	}
}
w3-total-cache.php000066600000001474151131463340010003 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Third_Party\W3_Total_Cache_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * W3 Total Cache integration.
 */
class W3_Total_Cache implements Integration_Interface {

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ W3_Total_Cache_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * On successful update/add of the taxonomy meta option, flush the W3TC cache.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_action( 'add_option_wpseo_taxonomy_meta', 'w3tc_objectcache_flush' );
		\add_action( 'update_option_wpseo_taxonomy_meta', 'w3tc_objectcache_flush' );
	}
}
web-stories.php000066600000006036151131463340007532 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Web_Stories_Conditional;
use Yoast\WP\SEO\Integrations\Front_End_Integration;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Models\Indexable;
use Yoast\WP\SEO\Presentations\Indexable_Presentation;

/**
 * Web Stories integration.
 */
class Web_Stories implements Integration_Interface {

	/**
	 * The front end integration.
	 *
	 * @var Front_End_Integration
	 */
	protected $front_end;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Web_Stories_Conditional::class ];
	}

	/**
	 * Constructs the Web Stories integration
	 *
	 * @param Front_End_Integration $front_end The front end integration.
	 */
	public function __construct( Front_End_Integration $front_end ) {
		$this->front_end = $front_end;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		// If the title presenter was not removed, we will disable the web stories metadata because Yoast SEO will output the title already.
		if ( ! $this->front_end->should_title_presenter_be_removed() ) {
			\add_action( 'web_stories_enable_metadata', '__return_false' );
		}
		\add_action( 'web_stories_enable_schemaorg_metadata', '__return_false' );
		\add_action( 'web_stories_enable_open_graph_metadata', '__return_false' );
		\add_action( 'web_stories_enable_twitter_metadata', '__return_false' );

		\add_action( 'web_stories_story_head', [ $this, 'web_stories_story_head' ], 1 );
		\add_filter( 'wpseo_schema_article_type', [ $this, 'filter_schema_article_type' ], 10, 2 );
		\add_filter( 'wpseo_metadesc', [ $this, 'filter_meta_description' ], 10, 2 );
	}

	/**
	 * Hooks into web story <head> generation to modify output.
	 *
	 * @return void
	 */
	public function web_stories_story_head() {
		\remove_action( 'web_stories_story_head', 'rel_canonical' );
		\add_action( 'web_stories_story_head', [ $this->front_end, 'call_wpseo_head' ], 9 );
	}

	/**
	 * Filters the meta description for stories.
	 *
	 * @param string                 $description  The description sentence.
	 * @param Indexable_Presentation $presentation The presentation of an indexable.
	 * @return string The description sentence.
	 */
	public function filter_meta_description( $description, $presentation ) {
		if ( $description || $presentation->model->object_sub_type !== 'web-story' ) {
			return $description;
		}

		return \get_the_excerpt( $presentation->model->object_id );
	}

	/**
	 * Filters Article type for Web Stories.
	 *
	 * @param string|string[] $type      The Article type.
	 * @param Indexable       $indexable The indexable.
	 * @return string|string[] Article type.
	 */
	public function filter_schema_article_type( $type, $indexable ) {
		if ( $indexable->object_sub_type !== 'web-story' ) {
			return $type;
		}

		if ( \is_string( $type ) && $type === 'None' ) {
			return 'Article';
		}

		return $type;
	}
}
wordproof.php000066600000016046151131463340007312 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use WPSEO_Admin_Asset;
use WPSEO_Admin_Asset_Manager;
use Yoast\WP\SEO\Conditionals\Non_Multisite_Conditional;
use Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Integration_Active_Conditional;
use Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Plugin_Inactive_Conditional;
use Yoast\WP\SEO\Config\Wordproof_App_Config;
use Yoast\WP\SEO\Config\Wordproof_Translations;
use Yoast\WP\SEO\Helpers\Wordproof_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use YoastSEO_Vendor\WordProof\SDK\Helpers\CertificateHelper;
use YoastSEO_Vendor\WordProof\SDK\Helpers\PostMetaHelper;
use YoastSEO_Vendor\WordProof\SDK\WordPressSDK;

/**
 * Class WordProof
 *
 * @package Yoast\WP\SEO\Integrations\Third_Party
 */
class Wordproof implements Integration_Interface {

	/**
	 * The Yoast meta key used to save if a post shiould be timestamped.
	 *
	 * @var string
	 */
	protected $post_meta_key = '_yoast_wpseo_wordproof_timestamp';

	/**
	 * The WordProof helper instance.
	 *
	 * @var Wordproof_Helper
	 */
	protected $wordproof;

	/**
	 * Asset manager instance.
	 *
	 * @var WPSEO_Admin_Asset_Manager
	 */
	protected $asset_manager;

	/**
	 * The WordProof integration constructor.
	 *
	 * @param Wordproof_Helper          $wordproof     The WordProof helper instance.
	 * @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO admin asset manager instance.
	 */
	public function __construct( Wordproof_Helper $wordproof, WPSEO_Admin_Asset_Manager $asset_manager = null ) {
		if ( ! $asset_manager ) {
			$asset_manager = new WPSEO_Admin_Asset_Manager();
		}

		$this->asset_manager = $asset_manager;
		$this->wordproof     = $wordproof;
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [
			Wordproof_Plugin_Inactive_Conditional::class,
			Non_Multisite_Conditional::class,
			Wordproof_Integration_Active_Conditional::class,
		];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		/**
		 * Used to initialize the WordProof WordPress SDK.
		 */
		\add_action( 'init', [ $this, 'sdk_setup' ], 11 );

		/**
		 * Enqueue the wordproof assets.
		 */
		\add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ], 10, 0 );

		/**
		 * Add async to the wordproof scripts.
		 */
		\add_filter( 'script_loader_tag', [ $this, 'add_async_to_script' ], 10, 3 );

		/**
		 * Removes the post meta timestamp key for the old privacy page.
		 */
		\add_action( 'update_option_wp_page_for_privacy_policy', [ $this, 'disable_timestamp_for_previous_legal_page' ], 10, 2 );

		/**
		 * Removes the post meta timestamp key for the old terms and conditions page.
		 */
		\add_action( 'update_option_woocommerce_terms_page_id', [ $this, 'disable_timestamp_for_previous_legal_page' ], 10, 2 );

		/**
		 * Called by the WordProof WordPress SDK to determine if the post should be timestamped.
		 */
		\add_filter( 'wordproof_timestamp_post_meta_key_overrides', [ $this, 'add_post_meta_key' ] );

		/**
		 * Called by the WordProof WordPress SDK to determine if the post should be automatically timestamped.
		 */
		\add_filter( 'wordproof_timestamp_post_types', [ $this, 'wordproof_timestamp_post_types' ] );

		/**
		 * Called by the WordProof WordPress SDK to determine if the certificate should be shown.
		 */
		\add_filter( 'wordproof_timestamp_show_certificate', [ $this, 'show_certificate' ], 10, 2 );

		/**
		 * Called by WPSEO_Meta to add extra meta fields to the ones defined there.
		 */
		\add_filter( 'add_extra_wpseo_meta_fields', [ $this, 'add_meta_field' ] );
	}

	/**
	 * Initializes the WordProof WordPress SDK.
	 */
	public function sdk_setup() {

		$config       = new Wordproof_App_Config();
		$translations = new Wordproof_Translations();

		WordPressSDK::getInstance( $config, $translations )
			->certificate()
			->initialize();
	}

	/**
	 * Removes the WordProof timestamp post meta if a legal page is changed.
	 *
	 * @param int $old_post_id The old post id.
	 * @param int $new_post_id The new post id.
	 */
	public function disable_timestamp_for_previous_legal_page( $old_post_id, $new_post_id ) {

		if ( $old_post_id !== $new_post_id ) {
			\delete_post_meta( $old_post_id, '_yoast_wpseo_wordproof_timestamp' );
		}
	}

	/**
	 * Return the Yoast post meta key for the SDK to determine if the post should be timestamped.
	 *
	 * @param array $meta_keys The array containing meta keys that should be used.
	 * @return array
	 */
	public function add_post_meta_key( $meta_keys ) {
		return [ $this->post_meta_key ];
	}

	/**
	 * Return an empty array to disable automatically timestamping selected post types.
	 *
	 * @param array $post_types The array containing post types that should be automatically timestamped.
	 * @return array
	 */
	public function wordproof_timestamp_post_types( $post_types ) {
		return [];
	}

	/**
	 * This filters hides the certificate if the Yoast post meta key is not set to true.
	 *
	 * @param bool    $value If the certificate should be shown.
	 * @param WP_Post $post  The post object of the post for which to determine the certificate should be shown.
	 * @return bool|null
	 */
	public function show_certificate( $value, $post ) {
		if ( ! $value ) {
			return $value;
		}

		if ( ! $this->wordproof->integration_is_active() ) {
			return false;
		}

		return \boolval( PostMetaHelper::get( $post->ID, $this->post_meta_key ) );
	}

	/**
	 * Adds the WordProof integration toggle to the array.
	 *
	 * @param array $fields The currently registered meta fields.
	 *
	 * @return array A new array with meta fields.
	 */
	public function add_meta_field( $fields ) {
		$fields['advanced']['wordproof_timestamp'] = [
			'type'          => 'hidden',
			'title'         => '',
			'default_value' => '',
			'description'   => '0',
		];

		return $fields;
	}

	/**
	 * Enqueue the uikit script.
	 *
	 * @return void
	 */
	public function enqueue_assets() {
		if ( CertificateHelper::show() ) {
			$flat_version = $this->asset_manager->flatten_version( \WPSEO_VERSION );

			/**
			 * We are using the Admin asset manager to register and enqueue a file served for all visitors,
			 * authenticated and unauthenticated users.
			 */
			$script = new WPSEO_Admin_Asset(
				[
					'name'    => 'wordproof-uikit',
					'src'     => 'wordproof-uikit.js',
					'version' => $flat_version,
				]
			);

			$this->asset_manager->register_script( $script );
			$this->asset_manager->enqueue_script( 'wordproof-uikit' );
		}
	}

	/**
	 * Adds async to the wordproof-uikit script.
	 *
	 * @param string $tag    The script tag for the enqueued script.
	 * @param string $handle The script's registered handle.
	 * @param string $src    The script's source URL.
	 *
	 * @return string The script's tag.
	 *
	 * @phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript
	 */
	public function add_async_to_script( $tag, $handle, $src ) {
		if ( $handle !== WPSEO_Admin_Asset_Manager::PREFIX . 'wordproof-uikit' ) {
			return $tag;
		}

		return "<script src={$src} async></script>";
	}
}
wpml.php000066600000003526151131463340006247 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * WPML integration.
 *
 * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded -- Known false positive with acronyms. Fix expected in YoastCS 3.x.
 */
class WPML implements Integration_Interface {

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_action( 'wpseo_home_url', [ $this, 'filter_home_url_before' ] );
		\add_filter( 'home_url', [ $this, 'filter_home_url_after' ], 100 );
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ WPML_Conditional::class ];
	}

	/**
	 * Adds a filter to WPML's wpml_get_home_url filter to ensure we get the unmanipulated home URL.
	 */
	public function filter_home_url_before() {
		\add_filter( 'wpml_get_home_url', [ $this, 'wpml_get_home_url' ], 10, 2 );
	}

	/**
	 * Removes the wpml_get_home_url filter to return the WPML, language-enriched home URL.
	 *
	 * @param string $home_url The filtered home URL.
	 *
	 * @return string The unfiltered home URL.
	 */
	public function filter_home_url_after( $home_url ) {
		\remove_filter( 'wpml_get_home_url', [ $this, 'wpml_get_home_url' ], 10 );

		return $home_url;
	}

	/**
	 * Returns the original URL instead of the language-enriched URL.
	 * This method gets automatically triggered by the wpml_get_home_url filter.
	 *
	 * @param string $home_url The url altered by WPML. Unused.
	 * @param string $url      The url that isn't altered by WPML.
	 *
	 * @return string The original url.
	 */
	public function wpml_get_home_url( $home_url, $url ) {
		return $url;
	}
}
exclude-woocommerce-post-types.php000066600000001613151131463340013356 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
use Yoast\WP\SEO\Integrations\Abstract_Exclude_Post_Type;

/**
 * Excludes certain WooCommerce-specific post types from the indexable table.
 *
 * Posts with these post types will not be saved to the indexable table.
 */
class Exclude_WooCommerce_Post_Types extends Abstract_Exclude_Post_Type {

	/**
	 * This integration is only active when the WooCommerce plugin
	 * is installed and activated.
	 *
	 * @return array|string[] The conditionals.
	 */
	public static function get_conditionals() {
		return [ WooCommerce_Conditional::class ];
	}

	/**
	 * Returns the names of the post types to be excluded.
	 * To be used in the wpseo_indexable_excluded_post_types filter.
	 *
	 * @return array The names of the post types.
	 */
	public function get_post_type() {
		return [ 'shop_order' ];
	}
}
jetpack.php000066600000001504151131463340006703 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Conditionals\Jetpack_Conditional;
use Yoast\WP\SEO\Conditionals\Open_Graph_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Jetpack integration.
 */
class Jetpack implements Integration_Interface {

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class, Jetpack_Conditional::class, Open_Graph_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'jetpack_enable_open_graph', '__return_false' );
	}
}
woocommerce-permalinks.php000066600000005642151131463340011753 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
use Yoast\WP\SEO\Helpers\Indexable_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * The permalink watcher.
 */
class Woocommerce_Permalinks implements Integration_Interface {

	/**
	 * Represents the indexable helper.
	 *
	 * @var Indexable_Helper
	 */
	protected $indexable_helper;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ WooCommerce_Conditional::class, Migrations_Conditional::class ];
	}

	/**
	 * Constructor.
	 *
	 * @param Indexable_Helper $indexable_helper Indexable Helper.
	 */
	public function __construct( Indexable_Helper $indexable_helper ) {
		$this->indexable_helper = $indexable_helper;
	}

	/**
	 * Registers the hooks.
	 *
	 * @codeCoverageIgnore
	 */
	public function register_hooks() {
		\add_filter( 'wpseo_post_types_reset_permalinks', [ $this, 'filter_product_from_post_types' ] );
		\add_action( 'update_option_woocommerce_permalinks', [ $this, 'reset_woocommerce_permalinks' ], 10, 2 );
	}

	/**
	 * Filters the product post type from the post type.
	 *
	 * @param array $post_types The post types to filter.
	 *
	 * @return array The filtered post types.
	 */
	public function filter_product_from_post_types( $post_types ) {
		unset( $post_types['product'] );

		return $post_types;
	}

	/**
	 * Resets the indexables for WooCommerce based on the changed permalink fields.
	 *
	 * @param array $old_value The old value.
	 * @param array $new_value The new value.
	 */
	public function reset_woocommerce_permalinks( $old_value, $new_value ) {
		$changed_options = \array_diff( $old_value, $new_value );

		if ( \array_key_exists( 'product_base', $changed_options ) ) {
			$this->indexable_helper->reset_permalink_indexables( 'post', 'product' );
		}

		if ( \array_key_exists( 'attribute_base', $changed_options ) ) {
			$attribute_taxonomies = $this->get_attribute_taxonomies();

			foreach ( $attribute_taxonomies as $attribute_name ) {
				$this->indexable_helper->reset_permalink_indexables( 'term', $attribute_name );
			}
		}

		if ( \array_key_exists( 'category_base', $changed_options ) ) {
			$this->indexable_helper->reset_permalink_indexables( 'term', 'product_cat' );
		}

		if ( \array_key_exists( 'tag_base', $changed_options ) ) {
			$this->indexable_helper->reset_permalink_indexables( 'term', 'product_tag' );
		}
	}

	/**
	 * Retrieves the taxonomies based on the attributes.
	 *
	 * @return array The taxonomies.
	 */
	protected function get_attribute_taxonomies() {
		$taxonomies = [];
		foreach ( \wc_get_attribute_taxonomies() as $attribute_taxonomy ) {
			$taxonomies[] = \wc_attribute_taxonomy_name( $attribute_taxonomy->attribute_name );
		}

		$taxonomies = \array_filter( $taxonomies );

		return $taxonomies;
	}
}
wpml-wpseo-notification.php000066600000006576151131463340012076 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional;
use Yoast\WP\SEO\Conditionals\Third_Party\WPML_WPSEO_Conditional;
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast_Notification;
use Yoast_Notification_Center;

/**
 * Adds a notification to the dashboard if the WPML plugin is installed,
 * but the Yoast SEO Multilingual plugin (a glue plugin to make Yoast SEO and WPML work nicely together)
 * is not.
 */
class WPML_WPSEO_Notification implements Integration_Interface {

	/**
	 * The notification ID.
	 *
	 * @internal
	 */
	const NOTIFICATION_ID = 'wpml-wpseo-not-installed';

	/**
	 * The short link helper.
	 *
	 * @var Short_Link_Helper
	 */
	protected $short_link_helper;

	/**
	 * The notification center.
	 *
	 * @var Yoast_Notification_Center
	 */
	protected $notification_center;

	/**
	 * The WPML WPSEO conditional.
	 *
	 * @var WPML_WPSEO_Conditional
	 */
	protected $wpml_wpseo_conditional;

	/**
	 * WPML WPSEO notification constructor.
	 *
	 * @param Short_Link_Helper         $short_link_helper      The short link helper.
	 * @param Yoast_Notification_Center $notification_center    The notification center.
	 * @param WPML_WPSEO_Conditional    $wpml_wpseo_conditional The WPML WPSEO conditional.
	 */
	public function __construct(
		Short_Link_Helper $short_link_helper,
		Yoast_Notification_Center $notification_center,
		WPML_WPSEO_Conditional $wpml_wpseo_conditional
	) {
		$this->short_link_helper      = $short_link_helper;
		$this->notification_center    = $notification_center;
		$this->wpml_wpseo_conditional = $wpml_wpseo_conditional;
	}

	/**
	 * Initializes the integration.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_action( 'admin_notices', [ $this, 'notify_not_installed' ] );
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * This integration should only be active when WPML is installed and activated.
	 *
	 * @return array The conditionals.
	 */
	public static function get_conditionals() {
		return [ WPML_Conditional::class ];
	}

	/**
	 * Notify the user that the Yoast SEO Multilingual plugin is not installed
	 * (when the WPML plugin is installed).
	 *
	 * Remove the notification again when it is installed.
	 *
	 * @return void
	 */
	public function notify_not_installed() {
		if ( ! $this->wpml_wpseo_conditional->is_met() ) {
			$this->notification_center->add_notification( $this->get_notification() );
			return;
		}
		$this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID );
	}

	/**
	 * Generates the notification to show to the user when WPML is installed,
	 * but the Yoast SEO Multilingual plugin is not.
	 *
	 * @return Yoast_Notification The notification.
	 */
	protected function get_notification() {
		return new Yoast_Notification(
			\sprintf(
				/* translators: %1$s expands to an opening anchor tag, %2$s expands to an closing anchor tag. */
				\__( 'We notice that you have installed WPML. To make sure your canonical URLs are set correctly, %1$sinstall and activate the Yoast SEO Multilingual add-on%2$s as well!', 'wordpress-seo' ),
				'<a href="' . \esc_url( $this->short_link_helper->get( 'https://yoa.st/wpml-yoast-seo' ) ) . '" target="_blank">',
				'</a>'
			),
			[
				'id'   => self::NOTIFICATION_ID,
				'type' => Yoast_Notification::WARNING,
			]
		);
	}
}
bbpress.php000066600000002651151131463340006726 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * BbPress integration.
 */
class BbPress implements Integration_Interface {

	/**
	 * The options helper.
	 *
	 * @var Options_Helper
	 */
	private $options;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * BbPress constructor.
	 *
	 * @codeCoverageIgnore It only sets dependencies.
	 *
	 * @param Options_Helper $options The options helper.
	 */
	public function __construct( Options_Helper $options ) {
		$this->options = $options;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		if ( $this->options->get( 'breadcrumbs-enable' ) !== true ) {
			return;
		}

		/**
		 * If breadcrumbs are active (which they supposedly are if the users has enabled this settings,
		 * there's no reason to have bbPress breadcrumbs as well.
		 *
		 * {@internal The class itself is only loaded when the template tag is encountered
		 *            via the template tag function in the wpseo-functions.php file.}}
		 */
		\add_filter( 'bbp_get_breadcrumb', '__return_false' );
	}
}
wordproof-integration-toggle.php000066600000011355151131463340013110 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Admin_Conditional;
use Yoast\WP\SEO\Helpers\Wordproof_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast_Feature_Toggle;

/**
 * Class WordProofIntegrationToggle.
 *
 * @package Yoast\WP\SEO\Integrations\Third_Party
 */
class Wordproof_Integration_Toggle implements Integration_Interface {

	/**
	 * The WordProof helper instance.
	 *
	 * @var Wordproof_Helper
	 */
	protected $wordproof;

	/**
	 * The WordProof integration toggle constructor.
	 *
	 * @param Wordproof_Helper $wordproof The WordProof helper instance.
	 */
	public function __construct( Wordproof_Helper $wordproof ) {
		$this->wordproof = $wordproof;
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Admin_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		/**
		 * Called by Yoast_Integration_Toggles to add extra toggles to the ones defined there.
		 */
		\add_filter( 'wpseo_integration_toggles', [ $this, 'add_integration_toggle' ] );

		/**
		 * Update the default wordproof_integration_active depending if the integration is disabled or not.
		 */
		\add_filter( 'wpseo_option_wpseo_defaults', [ $this, 'default_values' ] );

		/**
		 * Add extra text after the integration toggle if the toggle is disabled.
		 */
		\add_action( 'Yoast\WP\SEO\admin_integration_after', [ $this, 'after_integration_toggle' ] );

		/**
		 * Add extra text after the network integration toggle if the toggle is disabled.
		 */
		\add_action( 'Yoast\WP\SEO\admin_network_integration_after', [ $this, 'after_network_integration_toggle' ] );
	}

	/**
	 * Adds the WordProof integration toggle to the array.
	 *
	 * @param array $integration_toggles The integration toggles array.
	 *
	 * @return array The updated integration toggles array.
	 */
	public function add_integration_toggle( $integration_toggles ) {
		if ( \is_array( $integration_toggles ) ) {
			$integration_toggles[] = (object) [
				/* translators: %s expands to WordProof */
				'name'            => \sprintf( \__( '%s integration', 'wordpress-seo' ), 'WordProof' ),
				'setting'         => 'wordproof_integration_active',
				'label'           => \sprintf(
				/* translators: %s expands to WordProof */
					\__( '%1$s can be used to timestamp your privacy page.', 'wordpress-seo' ),
					'WordProof'
				),
				/* translators: %s expands to WordProof */
				'read_more_label' => \sprintf( \__( 'Read more about how %s works.', 'wordpress-seo' ), 'WordProof ' ),
				'read_more_url'   => 'https://yoa.st/wordproof-integration',
				'order'           => 16,
				'disabled'        => $this->wordproof->integration_is_disabled(),
				'new'             => true,
			];
		}

		return $integration_toggles;
	}

	/**
	 * Set the default WordProof integration option value depending if the integration is disabled or not.
	 *
	 * @param array $defaults Array containing default wpseo options.
	 *
	 * @return array
	 */
	public function default_values( $defaults ) {
		if ( $this->wordproof->integration_is_disabled() ) {
			$defaults['wordproof_integration_active'] = false;
		}

		return $defaults;
	}

	/**
	 * Add an explainer when the integration toggle is disabled.
	 *
	 * @param Yoast_Feature_Toggle $integration The integration toggle class.
	 */
	public function after_integration_toggle( $integration ) {
		if ( $integration->setting === 'wordproof_integration_active' ) {
			if ( $integration->disabled ) {

				$conditional = $this->wordproof->integration_is_disabled( true );

				if ( $conditional === 'Non_Multisite_Conditional' ) {
					echo '<p>' . \sprintf(
						/* translators: %s expands to WordProof */
						\esc_html__( 'Currently, the %s integration is not available for multisites.', 'wordpress-seo' ),
						'WordProof'
					) . '</p>';
				}

				if ( $conditional === 'Wordproof_Plugin_Inactive_Conditional' ) {
					echo '<p>' . \esc_html__( 'The WordProof Timestamp plugin needs to be disabled before you can activate this integration.', 'wordpress-seo' ) . '</p>';
				}
			}
		}
	}

	/**
	 * Add an explainer when the network integration toggle is disabled.
	 *
	 * @param Yoast_Feature_Toggle $integration The integration toggle class.
	 */
	public function after_network_integration_toggle( $integration ) {
		if ( $integration->setting === 'wordproof_integration_active' ) {
			if ( $integration->disabled ) {
				echo '<p>' . \sprintf(
					/* translators: %s expands to WordProof */
					\esc_html__( 'Currently, the %s integration is not available for multisites.', 'wordpress-seo' ),
					'WordProof'
				) . '</p>';
			}
		}
	}
}
exclude-elementor-post-types.php000066600000001650151131463340013032 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional;
use Yoast\WP\SEO\Integrations\Abstract_Exclude_Post_Type;

/**
 * Excludes certain Elementor-specific post types from the indexable table.
 *
 * Posts with these post types will not be saved to the indexable table.
 */
class Exclude_Elementor_Post_Types extends Abstract_Exclude_Post_Type {

	/**
	 * This integration is only active when the Elementor plugin
	 * is installed and activated.
	 *
	 * @return array|string[] The conditionals.
	 */
	public static function get_conditionals() {
		return [ Elementor_Activated_Conditional::class ];
	}

	/**
	 * Returns the names of the post types to be excluded.
	 * To be used in the wpseo_indexable_excluded_post_types filter.
	 *
	 * @return array The names of the post types.
	 */
	public function get_post_type() {
		return [ 'elementor_library' ];
	}
}
woocommerce-post-edit.php000066600000002740151131463340011512 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use WP_Post;
use Yoast\WP\SEO\Conditionals\Admin\Post_Conditional;
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * A WooCommerce integration that runs in the post editor.
 */
class WooCommerce_Post_Edit implements Integration_Interface {

	/**
	 * Register the hooks for this integration to work.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'wpseo_post_edit_values', [ $this, 'remove_meta_description_date' ], 10, 2 );
	}

	/**
	 * Only run this integration when WooCommerce is active and the user is in the post editor.
	 *
	 * @return string[] The conditionals that should be met before this integration is loaded.
	 */
	public static function get_conditionals() {
		return [ WooCommerce_Conditional::class, Post_Conditional::class ];
	}

	/**
	 * Don't show the date in the Google preview for WooCommerce products,
	 * since Google does not show dates for product pages in the search results.
	 *
	 * @param array   $values Key-value map of variables we enqueue in the JavaScript of the post editor.
	 * @param WP_Post $post   The post currently opened in the editor.
	 *
	 * @return array The values, where the `metaDescriptionDate` is set to the empty string.
	 */
	public function remove_meta_description_date( $values, $post ) {
		if ( $post->post_type === 'product' ) {
			$values['metaDescriptionDate'] = '';
		}

		return $values;
	}
}
wincher.php000066600000006654151131463340006734 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Third_Party;

use Yoast\WP\SEO\Conditionals\Admin_Conditional;
use Yoast\WP\SEO\Helpers\Wincher_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast_Feature_Toggle;

/**
 * Adds the Wincher integration.
 */
class Wincher implements Integration_Interface {

	/**
	 * The Wincher helper instance.
	 *
	 * @var Wincher_Helper
	 */
	protected $wincher;

	/**
	 * The Wincher integration toggle constructor.
	 *
	 * @param Wincher_Helper $wincher The Wincher helper instance.
	 */
	public function __construct( Wincher_Helper $wincher ) {
		$this->wincher = $wincher;
	}

	/**
	 * Initializes the integration.
	 *
	 * @return void
	 */
	public function register_hooks() {
		/**
		 * Called by Yoast_Integration_Toggles to add extra toggles to the ones defined there.
		 */
		\add_filter( 'wpseo_integration_toggles', [ $this, 'add_integration_toggle' ] );

		/**
		 * Called in dashboard/integrations.php to put additional content after the toggle.
		 */
		\add_action( 'Yoast\WP\SEO\admin_integration_after', [ $this, 'after_integration_toggle' ] );

		/**
		 * Add extra text after the network integration toggle if the toggle is disabled.
		 */
		\add_action( 'Yoast\WP\SEO\admin_network_integration_after', [ $this, 'after_network_integration_toggle' ] );
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array The conditionals.
	 */
	public static function get_conditionals() {
		return [ Admin_Conditional::class ];
	}

	/**
	 * Adds the Wincher integration toggle to the $integration_toggles array.
	 *
	 * @param array $integration_toggles The integration toggles array.
	 *
	 * @return array The updated integration toggles array.
	 */
	public function add_integration_toggle( $integration_toggles ) {
		if ( \is_array( $integration_toggles ) ) {
			$integration_toggles[] = (object) [
				/* translators: %s: 'Wincher' */
				'name'     => \sprintf( \__( '%s integration', 'wordpress-seo' ), 'Wincher' ),
				'setting'  => 'wincher_integration_active',
				'label'    => \sprintf(
				/* translators: %s: 'Wincher' */
					\__( 'The %s integration offers the option to track specific keyphrases and gain insights in their positions.', 'wordpress-seo' ),
					'Wincher'
				),
				'order'    => 11,
				'disabled' => \is_multisite(),
			];
		}

		return $integration_toggles;
	}

	/**
	 * Adds the disabled note when the integration toggle is disabled.
	 *
	 * @param Yoast_Feature_Toggle $integration The integration toggle class.
	 */
	public function after_integration_toggle( $integration ) {
		if ( $integration->setting === 'wincher_integration_active' ) {

			require \WPSEO_PATH . 'admin/views/tabs/metas/paper-content/integrations/wincher.php';

			if ( \is_multisite() ) {
				$this->get_disabled_note();
			}
		}
	}

	/**
	 * Adds the disabled note to the network integration toggle.
	 *
	 * @param Yoast_Feature_Toggle $integration The integration toggle class.
	 */
	public function after_network_integration_toggle( $integration ) {
		if ( $integration->setting === 'wincher_integration_active' ) {
			$this->get_disabled_note();
		}
	}

	/**
	 * Outputs the disabled note.
	 *
	 * @codeCoverageIgnore
	 *
	 * @return void
	 */
	protected function get_disabled_note() {
		echo '<p>', \sprintf(
		/* translators: %s expands to Wincher */
			\esc_html__( 'Currently, the %s integration is not available for multisites.', 'wordpress-seo' ),
			'Wincher'
		), '</p>';
	}
}
translatepress-conditional.php000066600000000710151131641760012637 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;

/**
 * Conditional that is only met when the TranslatePress plugin is active.
 */
class TranslatePress_Conditional implements Conditional {

	/**
	 * Checks whether the TranslatePress plugin is active.
	 *
	 * @return bool Whether the TranslatePress plugin is active.
	 */
	public function is_met() {
		return \class_exists( 'TRP_Translate_Press' );
	}
}
wpml-conditional.php000066600000000627151131641760010553 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;

/**
 * Conditional that is only met when WPML is active.
 */
class WPML_Conditional implements Conditional {

	/**
	 * Returns whether or not this conditional is met.
	 *
	 * @return bool Whether or not the conditional is met.
	 */
	public function is_met() {
		return \defined( 'WPML_PLUGIN_FILE' );
	}
}
coauthors-plus-flag-conditional.php000066600000000721151131641760013466 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Feature_Flag_Conditional;

/**
 * Feature flag conditional for the CoAuthors Plus integration.
 */
class CoAuthors_Plus_Flag_Conditional extends Feature_Flag_Conditional {

	/**
	 * Returns the name of the CoAuthors Plus integration feature flag.
	 *
	 * @return string The name of the feature flag.
	 */
	protected function get_feature_flag() {
		return 'COAUTHORS_PLUS';
	}
}
wordproof-plugin-inactive-conditional.php000066600000000751151131641760014707 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;

/**
 * Conditional that is met when the WordProof Timestamp plugin is inactive.
 */
class Wordproof_Plugin_Inactive_Conditional implements Conditional {

	/**
	 * Returns whether or not the WordProof Timestamp plugin is active.
	 *
	 * @return bool Whether or not the WordProof Timestamp plugin is active.
	 */
	public function is_met() {
		return ! \defined( 'WORDPROOF_VERSION' );
	}
}
wpml-wpseo-conditional.php000066600000001301151131641760011674 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;

/**
 * Conditional that is met when the Yoast SEO Multilingual plugin,
 * a glue plugin developed by and for WPML, is active.
 */
class WPML_WPSEO_Conditional implements Conditional {

	/**
	 * Path to the Yoast SEO Multilingual plugin file.
	 *
	 * @internal
	 */
	const PATH_TO_WPML_WPSEO_PLUGIN_FILE = 'wp-seo-multilingual/plugin.php';

	/**
	 * Returns whether or not the Yoast SEO Multilingual plugin is active.
	 *
	 * @return bool Whether or not the Yoast SEO Multilingual plugin is active.
	 */
	public function is_met() {
		return \is_plugin_active( self::PATH_TO_WPML_WPSEO_PLUGIN_FILE );
	}
}
wordproof-integration-active-conditional.php000066600000001516151131641760015405 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;
use Yoast\WP\SEO\Helpers\Wordproof_Helper;

/**
 * Conditional that is met when the WordProof integration is toggled on.
 */
class Wordproof_Integration_Active_Conditional implements Conditional {

	/**
	 * The WordProof helper.
	 *
	 * @var Wordproof_Helper
	 */
	private $wordproof;

	/**
	 * WordProof integration active constructor.
	 *
	 * @param Wordproof_Helper $wordproof The options helper.
	 */
	public function __construct( Wordproof_Helper $wordproof ) {
		$this->wordproof = $wordproof;
	}

	/**
	 * Returns whether or not the WordProof Timestamp plugin is active.
	 *
	 * @return bool Whether or not the WordProof Timestamp plugin is active.
	 */
	public function is_met() {
		return $this->wordproof->integration_is_active();
	}
}
coauthors-plus-activated-conditional.php000066600000000775151131641760014532 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;

/**
 * Conditional that is met when the CoAuthors Plus plugin is installed and activated.
 */
class CoAuthors_Plus_Activated_Conditional implements Conditional {

	/**
	 * Checks if the CoAuthors Plus plugin is installed and activated.
	 *
	 * @return bool `true` when the CoAuthors Plus plugin is installed and activated.
	 */
	public function is_met() {
		return \defined( 'COAUTHORS_PLUS_VERSION' );
	}
}
polylang-conditional.php000066600000000666151131641760011424 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;

/**
 * Conditional that is only met when the Polylang plugin is active.
 */
class Polylang_Conditional implements Conditional {

	/**
	 * Checks whether the Polylang plugin is installed and active.
	 *
	 * @return bool Whether Polylang is installed and active.
	 */
	public function is_met() {
		return \defined( 'POLYLANG_FILE' );
	}
}
w3-total-cache-conditional.php000066600000000747151131641760012312 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;

/**
 * Conditional that is only met when in the admin.
 */
class W3_Total_Cache_Conditional implements Conditional {

	/**
	 * Returns whether or not this conditional is met.
	 *
	 * @return bool Whether or not the conditional is met.
	 */
	public function is_met() {
		if ( ! \defined( 'W3TC_DIR' ) ) {
			return false;
		}

		return \function_exists( 'w3tc_objectcache_flush' );
	}
}
elementor-activated-conditional.php000066600000000745151131641760013531 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;

/**
 * Conditional that is met when the Elementor plugin is installed and activated.
 */
class Elementor_Activated_Conditional implements Conditional {

	/**
	 * Checks if the Elementor plugins is installed and activated.
	 *
	 * @return bool `true` when the Elementor plugin is installed and activated.
	 */
	public function is_met() {
		return \defined( 'ELEMENTOR__FILE__' );
	}
}
elementor-edit-conditional.php000066600000001612151131641760012504 0ustar00<?php

namespace Yoast\WP\SEO\Conditionals\Third_Party;

use Yoast\WP\SEO\Conditionals\Conditional;

/**
 * Conditional that is only met when on an Elementor edit page or when the current
 * request is an ajax request for saving our post meta data.
 */
class Elementor_Edit_Conditional implements Conditional {

	/**
	 * Returns whether or not this conditional is met.
	 *
	 * @return bool Whether or not the conditional is met.
	 */
	public function is_met() {
		global $pagenow;

		// Check if we are on an Elementor edit page.
		$get_action = \filter_input( \INPUT_GET, 'action', \FILTER_SANITIZE_STRING );
		if ( $pagenow === 'post.php' && $get_action === 'elementor' ) {
			return true;
		}

		// Check if we are in our Elementor ajax request.
		$post_action = \filter_input( \INPUT_POST, 'action', \FILTER_SANITIZE_STRING );
		return \wp_doing_ajax() && $post_action === 'wpseo_elementor_save';
	}
}
events-calendar-schema.php000066600000015174151144330650011604 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema\Third_Party;

use stdClass;
use Tribe__Events__JSON_LD__Event;
use Tribe__Events__Template__Month;
use Yoast\WP\SEO\Config\Schema_IDs;
use Yoast\WP\SEO\Context\Meta_Tags_Context;
use Yoast\WP\SEO\Generators\Schema\Abstract_Schema_Piece;
use Yoast\WP\SEO\Surfaces\Helpers_Surface;

/**
 * A class to handle textdomains and other Yoast Event Schema related logic..
 */
class Events_Calendar_Schema extends Abstract_Schema_Piece {

	/**
	 * The meta tags context.
	 *
	 * @var Meta_Tags_Context
	 */
	public $context;

	/**
	 * The helpers surface
	 *
	 * @var Helpers_Surface
	 */
	public $helpers;

	/**
	 * Determines whether or not a piece should be added to the graph.
	 *
	 * @return bool
	 */
	public function is_needed() {
		if ( \is_single() && \get_post_type() === 'tribe_events' ) {
			// The single event view.
			return true;
		}
		elseif ( \tribe_is_month() ) {
			// The month event view.
			return true;
		}

		return false;
	}

	/**
	 * Adds our Event piece of the graph.
	 * Partially lifted from the 'Tribe__JSON_LD__Abstract' class.
	 *
	 * @see https://docs.theeventscalendar.com/reference/classes/tribe__json_ld__abstract/
	 * @return array Event Schema markup
	 */
	public function generate() {
		$posts = [];

		if ( \is_singular( 'tribe_events' ) ) {
			global $post;
			$posts[] = $post;
		}
		elseif ( \tribe_is_month() ) {
			$posts = $this->get_month_events();
		}

		$tribe_data = $this->get_tribe_schema( $posts );
		$tribe_data = $this->transform_tribe_schema( $tribe_data );

		$data = [];
		foreach ( $tribe_data as $t ) {
			// Cast the schema object as array, the Yoast Class can't handle objects.
			$data[] = (array) $t;
		}

		// If the resulting array only has one entry, print it directly.
		if ( \count( $data ) === 1 ) {
			$data                     = $data[0];
			$data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id ];
			if ( $this->context->has_article ) {
				$data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id . Schema_IDs::ARTICLE_HASH ];
			}
		}
		elseif ( \count( $data ) === 0 ) {
			$data = false;
		}

		return $data;
	}

	/**
	 * Get and return the schema markup for a collection of posts.
	 * If the posts array is empty, only the current post is returned.
	 *
	 * @param  array $posts The collection of posts we want schema markup for.
	 *
	 * @return array        The tribe schema for these posts.
	 */
	private function get_tribe_schema( array $posts = [] ) {
		$args = [
			// We do not want the @context to be shown.
			'context' => false,
		];

		$tribe_data = Tribe__Events__JSON_LD__Event::instance()->get_data( $posts, $args );
		$type       = \strtolower( \esc_attr( Tribe__Events__JSON_LD__Event::instance()->type ) );

		foreach ( $tribe_data as $post_id => $_data ) {
			Tribe__Events__JSON_LD__Event::instance()->set_type( $post_id, $type );
			// Register this post as done already.
			Tribe__Events__JSON_LD__Event::instance()->register( $post_id );
		}

		/**
		 * Allows the event data to be modifed by themes and other plugins.
		 *
		 * @example yoast_tec_json_ld_thing_data
		 * @example yoast_tec_json_ld_event_data
		 *
		 * @param array $data objects representing the Google Markup for each event.
		 * @param array $args the arguments used to get data
		 */
		$tribe_data = \apply_filters( "yoast_tec_json_ld_{$type}_data", $tribe_data, $args );

		return $tribe_data;
	}

	/**
	 * Transform the tribe schema markup and adapt it to the Yoast SEO standard.
	 *
	 * @param  array $data The data retrieved from the TEC plugin.
	 *
	 * @return array       The transformed event data.
	 */
	private function transform_tribe_schema( array $data = [] ) {
		$new_data = [];

		foreach ( $data as $post_id => $d ) {
			$permalink = \get_permalink( $post_id );

			// EVENT.
			// Generate an @id for the event.
			$d->{'@id'} = $permalink . '#' . \strtolower( \esc_attr( $d->{'@type'} ) );

			// Transform the post_thumbnail from the url to the @id of #primaryimage.
			if ( \has_post_thumbnail( $post_id ) ) {
				if ( \is_singular( 'tribe_events' ) ) {
					// On a single view we can assume that Yoast SEO already printed the
					// image schema for the post thumbnail.
					$d->image = (object) [
						'@id' => $permalink . '#primaryimage',
					];
				}
				else {
					$image_id  = \get_post_thumbnail_id( $post_id );
					$schema_id = $permalink . '#primaryimage';
					$d->image  = $this->helpers->schema->image->generate_from_attachment_id( $schema_id, $image_id );
				}
			}

			if ( isset( $d->description ) && ! empty( $d->description ) ) {
				// By the time the description arrives in this plugin it is heavily
				// escaped. That's why we basically pull new text from the database.
				$d->description = \get_the_excerpt( $post_id );
			}

			// ORGANIZER.
			if ( \tribe_has_organizer( $post_id ) ) {
				if ( ! $d->organizer ) {
					$d->organizer = new stdClass();
				}

				$organizer_id              = \tribe_get_organizer_id( $post_id );
				$d->organizer->description = \get_the_excerpt( $organizer_id );

				// Fix empty organizer/url and wrong organizer/sameAs.
				if ( isset( $d->organizer->sameAs ) && $d->organizer->url === false ) {
					$d->organizer->url = $d->organizer->sameAs;
				}
				unset( $d->organizer->sameAs );
			}

			// VENUE / LOCATION.
			if ( \tribe_has_venue( $post_id ) ) {
				if ( ! $d->location ) {
					$d->location = new stdClass();
				}

				$venue_id                 = \tribe_get_venue_id( $post_id );
				$d->location->description = \get_the_excerpt( $venue_id );
			}

			/*
			 * PERFORMER
			 * Unset the performer, as it is currently unused.
			 * @see: https://github.com/moderntribe/the-events-calendar/blob/5e737eb820c59bb9639d9ee9f4b88931a51c8554/src/Tribe/JSON_LD/Event.php#L151
			 */
			unset( $d->performer );

			// OFFERS.
			if ( isset( $d->offers ) && \is_array( $d->offers ) ) {
				foreach ( $d->offers as $key => $offer ) {
					unset( $d->offers[ $key ]->category );
				}
			}

			$new_data[ $post_id ] = $d;
		}

		return $new_data;
	}

	/**
	 * Get an array of events for the requested month.
	 *
	 * @return array An array of posts of the custom post type event.
	 */
	private function get_month_events() {
		$wp_query = \tribe_get_global_query_object();

		$event_date = $wp_query->get( 'eventDate' );

		$month = $event_date;
		if ( empty( $month ) ) {
			$month = \tribe_get_month_view_date();
		}

		$args = [
			'eventDisplay'   => 'custom',
			'start_date'     => Tribe__Events__Template__Month::calculate_first_cell_date( $month ),
			'end_date'       => Tribe__Events__Template__Month::calculate_final_cell_date( $month ),
			'posts_per_page' => -1,
			'hide_upcoming'  => true,
		];

		return \tribe_get_events( $args );
	}
}
coauthor.php000066600000002730151144330650007111 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema\Third_Party;

use Yoast\WP\SEO\Generators\Schema\Author;

/**
 * Returns schema Author data for the CoAuthor Plus assigned user on a post.
 */
class CoAuthor extends Author {

	/**
	 * The user ID of the author we're generating data for.
	 *
	 * @var int
	 */
	private $user_id;

	/**
	 * Determine whether we should return Person schema.
	 *
	 * @return bool
	 */
	public function is_needed() {
		return true;
	}

	/**
	 * Returns Person Schema data.
	 *
	 * @return bool|array Person data on success, false on failure.
	 */
	public function generate() {
		$user_id = $this->determine_user_id();
		if ( ! $user_id ) {
			return false;
		}

		$data = $this->build_person_data( $user_id, true );

		$data['@type'] = 'Person';
		unset( $data['logo'] );

		// If this is a post and the author archives are enabled, set the author archive url as the author url.
		if ( $this->helpers->options->get( 'disable-author' ) !== true ) {
			$data['url'] = $this->helpers->user->get_the_author_posts_url( $user_id );
		}

		return $data;
	}

	/**
	 * Generate the Person data given a user ID.
	 *
	 * @param int $user_id User ID.
	 *
	 * @return array|bool
	 */
	public function generate_from_user_id( $user_id ) {
		$this->user_id = $user_id;

		return $this->generate();
	}

	/**
	 * Determines a User ID for the Person data.
	 *
	 * @return bool|int User ID or false upon return.
	 */
	protected function determine_user_id() {
		return $this->user_id;
	}
}
.htaccess000066600000000424151145460070006351 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>