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/Standalone.tar

Standalone.php000066600000005446151147516220007366 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

use AIOSEO\Plugin\Pro\Standalone as ProStandalone;

/**
 * Registers the standalone components.
 *
 * @since 4.2.0
 */
class Standalone {
	/**
	 * HeadlineAnalyzer class instance.
	 *
	 * @since 4.2.7
	 *
	 * @var HeadlineAnalyzer
	 */
	public $headlineAnalyzer = null;

	/**
	 * FlyoutMenu class instance.
	 *
	 * @since 4.2.7
	 *
	 * @var FlyoutMenu
	 */
	public $flyoutMenu = null;

	/**
	 * SeoPreview class instance.
	 *
	 * @since 4.2.8
	 *
	 * @var SeoPreview
	 */
	public $seoPreview = null;

	/**
	 * SetupWizard class instance.
	 *
	 * @since 4.2.7
	 *
	 * @var SetupWizard
	 */
	public $setupWizard = null;

	/**
	 * PrimaryTerm class instance.
	 *
	 * @since 4.3.6
	 *
	 * @var PrimaryTerm
	 */
	public $primaryTerm = null;

	/**
	 * UserProfileTab class instance.
	 *
	 * @since 4.5.4
	 *
	 * @var UserProfileTab
	 */
	public $userProfileTab = null;

	/**
	 * BuddyPress class instance.
	 *
	 * @since 4.7.6
	 *
	 * @var BuddyPress\BuddyPress
	 */
	public $buddyPress = null;

	/**
	 * BbPress class instance.
	 *
	 * @since 4.8.1
	 *
	 * @var BbPress\BbPress
	 */
	public $bbPress = null;

	/**
	 * List of page builder integration class instances.
	 *
	 * @since 4.2.7
	 *
	 * @var array[Object]
	 */
	public $pageBuilderIntegrations = [];

	/**
	 * List of block class instances.
	 *
	 * @since 4.2.7
	 *
	 * @var array[Object]
	 */
	public $standaloneBlocks = [];

	/**
	 * Class constructor.
	 *
	 * @since 4.2.0
	 */
	public function __construct() {
		$this->headlineAnalyzer = new HeadlineAnalyzer();
		$this->flyoutMenu       = new FlyoutMenu();
		$this->seoPreview       = new SeoPreview();
		$this->setupWizard      = new SetupWizard();
		$this->primaryTerm      = aioseo()->pro ? new ProStandalone\PrimaryTerm() : new PrimaryTerm();
		$this->userProfileTab   = new UserProfileTab();
		$this->buddyPress       = aioseo()->pro ? new ProStandalone\BuddyPress\BuddyPress() : new BuddyPress\BuddyPress();
		$this->bbPress          = aioseo()->pro ? new ProStandalone\BbPress\BbPress() : new BbPress\BbPress();

		aioseo()->pro ? new ProStandalone\DetailsColumn() : new DetailsColumn();

		new AdminBarNoindexWarning();
		new LimitModifiedDate();
		new Notifications();
		new PublishPanel();
		new WpCode();

		$this->pageBuilderIntegrations = [
			'elementor'  => new PageBuilders\Elementor(),
			'divi'       => new PageBuilders\Divi(),
			'seedprod'   => new PageBuilders\SeedProd(),
			'wpbakery'   => new PageBuilders\WPBakery(),
			'avada'      => new PageBuilders\Avada(),
			'siteorigin' => new PageBuilders\SiteOrigin(),
			'thrive'     => new PageBuilders\ThriveArchitect()
		];

		$this->standaloneBlocks = [
			'tocBlock' => new Blocks\TableOfContents(),
			'faqBlock' => new Blocks\FaqPage()
		];
	}
}HeadlineAnalyzer.php000066600000040326151147516220010511 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

/**
 * Handles the headline analysis.
 *
 * @since 4.1.2
 */
class HeadlineAnalyzer {
	/**
	 * Class constructor.
	 *
	 * @since 4.1.2
	 */
	public function __construct() {
		if ( ! is_admin() || wp_doing_cron() ) {
			return;
		}

		add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue' ] );

		if ( ! aioseo()->options->advanced->headlineAnalyzer ) {
			return;
		}

		add_filter( 'monsterinsights_headline_analyzer_enabled', '__return_false' );
		add_filter( 'exactmetrics_headline_analyzer_enabled', '__return_false' );
	}

	/**
	 * Enqueues the headline analyzer.
	 *
	 * @since 4.1.2
	 *
	 * @return void
	 */
	public function enqueue() {
		if (
			! aioseo()->helpers->isScreenBase( 'post' ) ||
			! aioseo()->access->hasCapability( 'aioseo_page_analysis' )
		) {
			return;
		}

		if ( ! aioseo()->options->advanced->headlineAnalyzer ) {
			return;
		}

		$path = '/vendor/jwhennessey/phpinsight/autoload.php';
		if ( ! aioseo()->core->fs->exists( AIOSEO_DIR . $path ) ) {
			return;
		}
		require AIOSEO_DIR . $path;

		aioseo()->core->assets->load( 'src/vue/standalone/headline-analyzer/main.js' );
	}

	/**
	 * Returns the result of the analsyis.
	 *
	 * @since 4.1.2
	 *
	 * @param  string $title The title.
	 * @return array         The result.
	 */
	public function getResult( $title ) {
		$result = $this->getHeadlineScore( html_entity_decode( $title ) );

		return [
			'result'   => $result,
			'analysed' => ! $result->err,
			'sentence' => ucwords( wp_unslash( sanitize_text_field( $title ) ) ),
			'score'    => ! empty( $result->score ) ? $result->score : 0
		];
	}

	/**
	 * Returns the score.
	 *
	 * @since 4.1.2
	 *
	 * @param  string    $title The title.
	 * @return \stdClass        The result.
	 */
	public function getHeadlineScore( $title ) {
		$result                           = new \stdClass();
		$result->originalExplodedHeadline = explode( ' ', wp_unslash( $title ) );

		// Strip useless characters and whitespace.
		$title = preg_replace( '/[^A-Za-z0-9 ]/', '', (string) $title );
		$title = preg_replace( '!\s+!', ' ', (string) $title );
		$title = strtolower( $title );

		$result->input = $title;

		// If the headline is invalid, return an error.
		if ( ! $title || ' ' === $title || trim( $title ) === '' ) {
			$result->err = true;
			$result->msg = 'The headline is invalid.';

			return $result;
		}

		$totalScore               = 0;
		$explodedHeadline         = explode( ' ', $title );
		$result->explodedHeadline = $explodedHeadline;
		$result->err              = false;

		// The optimal length is 55 characters.
		$result->length = strlen( str_replace( ' ', '', $title ) );
		$totalScore     = $totalScore + 3;

		//phpcs:disable Squiz.ControlStructures.ControlSignature
		if ( $result->length <= 19 ) { $totalScore += 5; }
		elseif ( $result->length >= 20 && $result->length <= 34 ) { $totalScore += 8; }
		elseif ( $result->length >= 35 && $result->length <= 66 ) { $totalScore += 11; }
		elseif ( $result->length >= 67 && $result->length <= 79 ) { $totalScore += 8; }
		elseif ( $result->length >= 80 ) { $totalScore += 5; }

		// The average headline is 6-7 words long.
		$result->wordCount = count( $explodedHeadline );
		$totalScore        = $totalScore + 3;

		if ( 0 === $result->wordCount ) { $totalScore = 0; }
		elseif ( $result->wordCount >= 2 && $result->wordCount <= 4 ) { $totalScore += 5; }
		elseif ( $result->wordCount >= 5 && $result->wordCount <= 9 ) { $totalScore += 11; }
		elseif ( $result->wordCount >= 10 && $result->wordCount <= 11 ) { $totalScore += 8; }
		elseif ( $result->wordCount >= 12 ) { $totalScore += 5; }

		// Check for power words, emotional words, etc.
		$result->powerWords               = $this->matchWords( $result->input, $result->explodedHeadline, $this->powerWords() );
		$result->powerWordsPercentage     = count( $result->powerWords ) / $result->wordCount;
		$result->emotionWords             = $this->matchWords( $result->input, $result->explodedHeadline, $this->emotionPowerWords() );
		$result->emotionalWordsPercentage = count( $result->emotionWords ) / $result->wordCount;
		$result->commonWords              = $this->matchWords( $result->input, $result->explodedHeadline, $this->commonWords() );
		$result->commonWordsPercentage    = count( $result->commonWords ) / $result->wordCount;
		$result->uncommonWords            = $this->matchWords( $result->input, $result->explodedHeadline, $this->uncommonWords() );
		$result->uncommonWordsPercentage  = count( $result->uncommonWords ) / $result->wordCount;
		$result->detectedWordTypes        = [];

		if ( $result->emotionalWordsPercentage < 0.1 ) {
			$result->detectedWordTypes[] = 'emotion';
		} else {
			$totalScore = $totalScore + 15;
		}

		if ( $result->commonWordsPercentage < 0.2 ) {
			$result->detectedWordTypes[] = 'common';
		} else {
			$totalScore = $totalScore + 11;
		}

		if ( $result->uncommonWordsPercentage < 0.1 ) {
			$result->detectedWordTypes[] = 'uncommon';
		} else {
			$totalScore = $totalScore + 15;
		}

		if ( count( $result->powerWords ) < 1 ) {
			$result->detectedWordTypes[] = 'power';
		} else {
			$totalScore = $totalScore + 19;
		}

		if (
			$result->emotionalWordsPercentage >= 0.1 &&
			$result->commonWordsPercentage >= 0.2 &&
			$result->uncommonWordsPercentage >= 0.1 &&
			count( $result->powerWords ) >= 1
		) {
			$totalScore = $totalScore + 3;
		}

		$sentiment         = new \PHPInsight\Sentiment();
		$sentimentClass    = $sentiment->categorise( $title );
		$result->sentiment = $sentimentClass;

		$totalScore = $totalScore + ( 'pos' === $result->sentiment ? 10 : ( 'neg' === $result->sentiment ? 10 : 7 ) );

		$headlineTypes = [];
		if ( strpos( $title, 'how to' ) !== false || strpos( $title, 'howto' ) !== false ) {
			$headlineTypes[] = __( 'How-To', 'all-in-one-seo-pack' );
			$totalScore      = $totalScore + 7;
		}

		$listWords = array_intersect( $explodedHeadline, $this->numericalIndicators() );
		if ( preg_match( '~[0-9]+~', (string) $title ) || ! empty( $listWords ) ) {
			$headlineTypes[] = __( 'List', 'all-in-one-seo-pack' );
			$totalScore      = $totalScore + 7;
		}

		if ( in_array( $explodedHeadline[0], $this->primaryQuestionIndicators(), true ) ) {
			if ( in_array( $explodedHeadline[1], $this->secondaryQuestionIndicators(), true ) ) {
				$headlineTypes[] = __( 'Question', 'all-in-one-seo-pack' );
				$totalScore      = $totalScore + 7;
			}
		}

		if ( empty( $headlineTypes ) ) {
			$headlineTypes[] = __( 'General', 'all-in-one-seo-pack' );
			$totalScore      = $totalScore + 5;
		}

		$result->headlineTypes = $headlineTypes;
		$result->score         = $totalScore >= 93 ? 93 : $totalScore;

		return $result;
	}

	/**
	* Tries to find matches for power words, emotional words, etc. in the headline.
	*
	* @since 4.1.2
	*
	* @param  string $headline         The headline.
	* @param  array  $explodedHeadline The exploded headline.
	* @param  array  $words            The words to match.
	* @return array                    The matches that were found.
	*/
	public function matchWords( $headline, $explodedHeadline, $words ) {
		$foundMatches = [];
		foreach ( $words as $word ) {
			$strippedWord = preg_replace( '/[^A-Za-z0-9 ]/', '', (string) $word );

			// Check if word is a phrase.
			if ( strpos( $word, ' ' ) !== false ) {
				if ( strpos( $headline, $strippedWord ) !== false ) {
					$foundMatches[] = $word;
				}
				continue;
			}
			// Check if it is a single word.
			if ( in_array( $strippedWord, $explodedHeadline, true ) ) {
				$foundMatches[] = $word;
			}
		}

		return $foundMatches;
	}

	/**
	 * Returns a list of numerical indicators.
	 *
	 * @since 4.1.2
	 *
	 * @return array The list of numerical indicators.
	 */
	private function numericalIndicators() {
		return [
			'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'eleven', 'twelve', 'thirt', 'fift', 'hundred', 'thousand' // phpcs:ignore Generic.Files.LineLength.MaxExceeded, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
		];
	}

	/**
	 * Returns a list of primary question indicators.
	 *
	 * @since 4.1.2
	 *
	 * @return array The list of primary question indicators.
	 */
	private function primaryQuestionIndicators() {
		return [
			'where', 'when', 'how', 'what', 'have', 'has', 'does', 'do', 'can', 'are', 'will' // phpcs:ignore Generic.Files.LineLength.MaxExceeded, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
		];
	}

	/**
	 * Returns a list of secondary question indicators.
	 *
	 * @since 4.1.2
	 *
	 * @return array The list of secondary question indicators.
	 */
	private function secondaryQuestionIndicators() {
		return [
			'you', 'they', 'he', 'she', 'your', 'it', 'they', 'my', 'have', 'has', 'does', 'do', 'can', 'are', 'will' // phpcs:ignore Generic.Files.LineLength.MaxExceeded, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
		];
	}

	/**
	 * Returns a list of power words.
	 *
	 * @since 4.1.2
	 *
	 * @return array The list of power words.
	 */
	private function powerWords() {
		return [
			'great', 'free', 'focus', 'remarkable', 'confidential', 'sale', 'wanted', 'obsession', 'sizable', 'new', 'absolutely lowest', 'surging', 'wonderful', 'professional', 'interesting', 'revisited', 'delivered', 'guaranteed', 'challenge', 'unique', 'secrets', 'special', 'lifetime', 'bargain', 'scarce', 'tested', 'highest', 'hurry', 'alert famous', 'improved', 'expert', 'daring', 'strong', 'immediately', 'advice', 'pioneering', 'unusual', 'limited', 'the truth about', 'destiny', 'outstanding', 'simplistic', 'compare', 'unsurpassed', 'energy', 'powerful', 'colorful', 'genuine', 'instructive', 'big', 'affordable', 'informative', 'liberal', 'popular', 'ultimate', 'mainstream', 'rare', 'exclusive', 'willpower', 'complete', 'edge', 'valuable', 'attractive', 'last chance', 'superior', 'how to', 'easily', 'exploit', 'unparalleled', 'endorsed', 'approved', 'quality', 'fascinating', 'unlimited', 'competitive', 'gigantic', 'compromise', 'discount', 'full', 'love', 'odd', 'fundamentals', 'mammoth', 'lavishly', 'bottom line', 'under priced', 'innovative', 'reliable', 'zinger', 'suddenly', 'it\'s here', 'terrific', 'simplified', 'perspective', 'just arrived', 'breakthrough', 'tremendous', 'launching', 'sure fire', 'emerging', 'helpful', 'skill', 'soar', 'profitable', 'special offer', 'reduced', 'beautiful', 'sampler', 'technology', 'better', 'crammed', 'noted', 'selected', 'shrewd', 'growth', 'luxury', 'sturdy', 'enormous', 'promising', 'unconditional', 'wealth', 'spotlight', 'astonishing', 'timely', 'successful', 'useful', 'imagination', 'bonanza', 'opportunities', 'survival', 'greatest', 'security', 'last minute', 'largest', 'high tech', 'refundable', 'monumental', 'colossal', 'latest', 'quickly', 'startling', 'now', 'important', 'revolutionary', 'quick', 'unlock', 'urgent', 'miracle', 'easy', 'fortune', 'amazing', 'magic', 'direct', 'authentic', 'exciting', 'proven', 'simple', 'announcing', 'portfolio', 'reward', 'strange', 'huge gift', 'revealing', 'weird', 'value', 'introducing', 'sensational', 'surprise', 'insider', 'practical', 'excellent', 'delighted', 'download' // phpcs:ignore Generic.Files.LineLength.MaxExceeded, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
		];
	}

	/**
	 * Returns a list of common words.
	 *
	 * @since 4.1.2
	 *
	 * @return array The list of common words.
	 */
	private function commonWords() {
		return [
			'a', 'for', 'about', 'from', 'after', 'get', 'all', 'has', 'an', 'have', 'and', 'he', 'are', 'her', 'as', 'his', 'at', 'how', 'be', 'I', 'but', 'if', 'by', 'in', 'can', 'is', 'did', 'it', 'do', 'just', 'ever', 'like', 'll', 'these', 'me', 'they', 'most', 'things', 'my', 'this', 'no', 'to', 'not', 'up', 'of', 'was', 'on', 'what', 're', 'when', 'she', 'who', 'sould', 'why', 'so', 'will', 'that', 'with', 'the', 'you', 'their', 'your', 'there' // phpcs:ignore Generic.Files.LineLength.MaxExceeded, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
		];
	}

	/**
	 * Returns a list of uncommon words.
	 *
	 * @since 4.1.2
	 *
	 * @return array The list of uncommon words.
	 */
	private function uncommonWords() {
		return [
			'actually', 'happened', 'need', 'thing', 'awesome', 'heart', 'never', 'think', 'baby', 'here', 'new', 'time', 'beautiful', 'its', 'now', 'valentines', 'being', 'know', 'old', 'video', 'best', 'life', 'one', 'want', 'better', 'little', 'out', 'watch', 'boy', 'look', 'people', 'way', 'dog', 'love', 'photos', 'ways', 'down', 'made', 'really', 'world', 'facebook', 'make', 'reasons', 'year', 'first', 'makes', 'right', 'years', 'found', 'man', 'see', 'you’ll', 'girl', 'media', 'seen', 'good', 'mind', 'social', 'guy', 'more', 'something' // phpcs:ignore Generic.Files.LineLength.MaxExceeded, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
		];
	}

	/**
	 * Returns a list of emotional power words.
	 *
	 * @since 4.1.2
	 *
	 * @return array The list of emotional power words.
	 */
	private function emotionPowerWords() {
		return [
			'destroy', 'extra', 'in a', 'devastating', 'eye-opening', 'gift', 'in the world', 'devoted', 'fail', 'in the', 'faith', 'grateful', 'inexpensive', 'dirty', 'famous', 'disastrous', 'fantastic', 'greed', 'grit', 'insanely', 'disgusting', 'fearless', 'disinformation', 'feast', 'insidious', 'dollar', 'feeble', 'gullible', 'double', 'fire', 'hack', 'fleece', 'had enough', 'invasion', 'drowning', 'floundering', 'happy', 'ironclad', 'dumb', 'flush', 'hate', 'irresistibly', 'hazardous', 'is the', 'fool', 'is what happens when', 'fooled', 'helpless', 'it looks like a', 'embarrass', 'for the first time', 'help are the', 'jackpot', 'forbidden', 'hidden', 'jail', 'empower', 'force-fed', 'high', 'jaw-dropping', 'forgotten', 'jeopardy', 'energize', 'hoax', 'jubilant', 'foul', 'hope', 'killer', 'frantic', 'horrific', 'know it all', 'epic', 'how to make', 'evil', 'freebie', 'frenzy', 'hurricane', 'excited', 'fresh on the mind', 'frightening', 'hypnotic', 'lawsuit', 'frugal', 'illegal', 'fulfill', 'lick', 'explode', 'lies', 'exposed', 'gambling', 'like a normal', 'nightmare', 'results', 'line', 'no good', 'pound', 'loathsome', 'no questions asked', 'revenge', 'lonely', 'looks like a', 'obnoxious', 'preposterous', 'revolting', 'looming', 'priced', 'lost', 'prison', 'lowest', 'of the', 'privacy', 'rich', 'lunatic', 'off-limits', 'private', 'risky', 'lurking', 'offer', 'prize', 'ruthless', 'lust', 'official', 'luxurious', 'on the', 'profit', 'scary', 'lying', 'outlawed', 'protected', 'scream', 'searing', 'overcome', 'provocative', 'make you', 'painful', 'pummel', 'secure', 'pale', 'punish', 'marked down', 'panic', 'quadruple', 'seductively', 'massive', 'pay zero', 'seize', 'meltdown', 'payback', 'might look like a', 'peril', 'mind-blowing', 'shameless', 'minute', 'rave', 'shatter', 'piranha', 'reckoning', 'shellacking', 'mired', 'pitfall', 'reclaim', 'mistakes', 'plague', 'sick and tired', 'money', 'played', 'refugee', 'silly', 'money-grubbing', 'pluck', 'refund', 'moneyback', 'plummet', 'plunge', 'murder', 'pointless', 'sinful', 'myths', 'poor', 'remarkably', 'six-figure', 'never again', 'research', 'surrender', 'to the', 'varify', 'skyrocket', 'toxic', 'vibrant', 'slaughter', 'swindle', 'trap', 'victim', 'sleazy', 'taboo', 'treasure', 'victory', 'smash', 'tailspin', 'vindication', 'smug', 'tank', 'triple', 'viral', 'smuggled', 'tantalizing', 'triumph', 'volatile', 'sniveling', 'targeted', 'truth', 'vulnerable', 'snob', 'tawdry', 'try before you buy', 'tech', 'turn the tables', 'wanton', 'soaring', 'warning', 'teetering', 'unauthorized', 'spectacular', 'temporary fix', 'unbelievably', 'spine', 'tempting', 'uncommonly', 'what happened', 'spirit', 'what happens when', 'terror', 'under', 'what happens', 'staggering', 'underhanded', 'what this', 'that will make you', 'undo","when you see', 'that will make', 'unexpected', 'when you', 'strangle', 'that will', 'whip', 'the best', 'whopping', 'stuck up', 'the ranking of', 'wicked', 'stunning', 'the most', 'will make you', 'stupid', 'the reason why is', 'unscrupulous', 'thing ive ever seen', 'withheld', 'this is the', 'this is what happens', 'unusually', 'wondrous', 'this is what', 'uplifting', 'worry', 'sure', 'this is', 'wounded', 'surge', 'thrilled', 'you need to know', 'thrilling', 'valor', 'you need to', 'you see what', 'surprising', 'tired', 'you see', 'surprisingly', 'to be', 'vaporize' // phpcs:ignore Generic.Files.LineLength.MaxExceeded, WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
		];
	}
}Notifications.php000066600000001357151147516220010104 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

use AIOSEO\Plugin\Common\Models;

/**
 * Handles the notifications standalone.
 *
 * @since 4.2.0
 */
class Notifications {
	/**
	 * Class constructor.
	 *
	 * @since 4.2.0
	 */
	public function __construct() {
		if ( ! is_admin() ) {
			return;
		}

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueScript' ] );
	}

	/**
	 * Enqueues the script.
	 *
	 * @since 4.2.0
	 *
	 * @return void
	 */
	public function enqueueScript() {
		aioseo()->core->assets->load( 'src/vue/standalone/notifications/main.js', [], [
			'newNotifications' => count( Models\Notification::getNewNotifications() )
		], 'aioseoNotifications' );
	}
}PrimaryTerm.php000066600000002402151147516220007536 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

/**
 * Handles the Primary Term feature.
 *
 * @since 4.3.6
 */
class PrimaryTerm {
	/**
	 * Class constructor.
	 *
	 * @since 4.3.6
	 */
	public function __construct() {
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		if ( wp_doing_ajax() || wp_doing_cron() || ! is_admin() ) {
			return;
		}

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAssets' ] );
	}

	/**
	 * Enqueues the JS/CSS for the on page/posts settings.
	 *
	 * @since 4.3.6
	 *
	 * @return void
	 */
	public function enqueueAssets() {
		if ( ! aioseo()->helpers->isScreenBase( 'post' ) ) {
			return;
		}

		aioseo()->core->assets->load( 'src/vue/standalone/primary-term/main.js', [], aioseo()->helpers->getVueData( 'post' ) );
	}

	/**
	 * Returns the primary term for the given taxonomy name.
	 *
	 * @since 4.3.6
	 *
	 * @param  int            $postId       The post ID.
	 * @param  string         $taxonomyName The taxonomy name.
	 * @return \WP_Term|false               The term or false.
	 */
	public function getPrimaryTerm( $postId, $taxonomyName ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		return false;
	}
}AdminBarNoindexWarning.php000066600000003166151147516220011623 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

use AIOSEO\Plugin\Common\Models;

/**
 * Handles the admin bar noindex warning.
 *
 * @since 4.6.7
 */
class AdminBarNoindexWarning {
	/**
	 * Class constructor.
	 *
	 * @since 4.6.7
	 */
	public function __construct() {
		add_action( 'init', [ $this, 'init' ] );
	}

	/**
	 * Initializes the standalone.
	 *
	 * @since 4.6.7
	 *
	 * @return void
	 */
	public function init() {
		if ( wp_doing_ajax() || wp_doing_cron() ) {
			return;
		}

		$isSitePublic = get_option( 'blog_public' );
		if ( $isSitePublic ) {
			return;
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueScript' ] );
		add_action( 'wp_enqueue_scripts', [ $this, 'enqueueScript' ] );

		add_action( 'admin_bar_menu', [ $this, 'addAdminBarElement' ], 99999 );
	}

	/**
	 * Enqueues the script.
	 *
	 * @since 4.6.7
	 *
	 * @return void
	 */
	public function enqueueScript() {
		aioseo()->core->assets->load( 'src/vue/standalone/admin-bar-noindex-warning/main.js', [], [
			'optionsReadingUrl' => admin_url( 'options-reading.php' ),
		], 'aioseoAdminBarNoindexWarning' );
	}

	/**
	 * Adds the admin bar element.
	 *
	 * @since 4.6.7
	 *
	 * @param  \WP_Admin_Bar $wpAdminBar The admin bar object.
	 * @return void
	 */
	public function addAdminBarElement( $wpAdminBar ) {
		$wpAdminBar->add_node(
			[
				'id'    => 'aioseo-admin-bar-noindex-warning',
				'title' => __( 'Search Engines Blocked!', 'all-in-one-seo-pack' ),
				'href'  => admin_url( 'options-reading.php' )
			]
		);
	}
}SeoPreview.php000066600000021725151147516220007364 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

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

/**
 * Handles the SEO Preview feature on the front-end.
 *
 * @since 4.2.8
 */
class SeoPreview {
	/**
	 * Whether this feature is allowed on the current page or not.
	 *
	 * @since 4.2.8
	 *
	 * @var bool
	 */
	private $enable = false;

	/**
	 * The relative JS filename for this standalone.
	 *
	 * @since 4.3.1
	 *
	 * @var string
	 */
	private $mainAssetRelativeFilename = 'src/vue/standalone/seo-preview/main.js';

	/**
	 * Class constructor.
	 *
	 * @since 4.2.8
	 */
	public function __construct() {
		// Allow users to disable SEO Preview.
		if ( apply_filters( 'aioseo_seo_preview_disable', false ) ) {
			return;
		}

		// Hook into `wp` in order to have access to the WP queried object.
		add_action( 'wp', [ $this, 'init' ], 20 );
	}

	/**
	 * Initialize the feature.
	 * Hooked into `wp` action hook.
	 *
	 * @since 4.2.8
	 *
	 * @return void
	 */
	public function init() {
		if (
			is_admin() ||
			! aioseo()->helpers->isAdminBarEnabled() ||
			// If we're seeing the Divi theme Visual Builder.
			( function_exists( 'et_core_is_fb_enabled' ) && et_core_is_fb_enabled() ) ||
			aioseo()->helpers->isAmpPage()
		) {
			return;
		}

		$allow = [
			'archive',
			'attachment',
			'author',
			'date',
			'dynamic_home',
			'page',
			'search',
			'single',
			'taxonomy',
		];

		if ( ! in_array( aioseo()->helpers->getTemplateType(), $allow, true ) ) {
			return;
		}

		$this->enable = true;

		// Prevent Autoptimize from optimizing the translations for the SEO Preview. If we don't do this, Autoptimize can break the frontend for certain languages - #5235.
		if ( is_user_logged_in() && 'en_US' !== get_user_locale() ) {
			add_filter( 'autoptimize_filter_noptimize', '__return_true' );
		}

		// As WordPress uses priority 10 to print footer scripts we use 9 to make sure our script still gets output.
		add_action( 'wp_print_footer_scripts', [ $this, 'enqueueScript' ], 9 );
	}

	/**
	 * Hooked into `wp_print_footer_scripts` action hook.
	 * Enqueue the standalone JS the latest possible and prevent 3rd-party performance plugins from merging it.
	 *
	 * @since 4.3.1
	 *
	 * @return void
	 */
	public function enqueueScript() {
		aioseo()->core->assets->load( $this->mainAssetRelativeFilename, [], $this->getVueData(), 'aioseoSeoPreview' );
		aioseo()->main->enqueueTranslations();
	}

	/**
	 * Returns the data for Vue.
	 *
	 * @since 4.2.8
	 *
	 * @return array The data.
	 */
	private function getVueData() {
		$data = [
			'editGoogleSnippetUrl'   => '',
			'editFacebookSnippetUrl' => '',
			'editTwitterSnippetUrl'  => '',
			'editObjectBtnText'      => '',
			'editObjectUrl'          => '',
			'keyphrases'             => '',
			'page_analysis'          => '',
			'urls'                   => [
				'home'        => home_url(),
				'domain'      => aioseo()->helpers->getSiteDomain(),
				'mainSiteUrl' => aioseo()->helpers->getSiteUrl(),
			],
			'mainAssetCssQueue'      => aioseo()->core->assets->getJsAssetCssQueue( $this->mainAssetRelativeFilename ),
			'data'                   => [
				'isDev'           => aioseo()->helpers->isDev(),
				'siteName'        => aioseo()->helpers->getWebsiteName(),
				'usingPermalinks' => aioseo()->helpers->usingPermalinks()
			]
		];

		if ( BuddyPressIntegration::isComponentPage() ) {
			return array_merge( $data, aioseo()->standalone->buddyPress->getVueDataSeoPreview() );
		}

		$queriedObject = get_queried_object(); // Don't use the getTerm helper here.
		$templateType  = aioseo()->helpers->getTemplateType();
		if (
			'taxonomy' === $templateType ||
			'single' === $templateType ||
			'page' === $templateType ||
			'attachment' === $templateType
		) {
			$labels = null;

			if ( is_a( $queriedObject, 'WP_Term' ) ) {
				$wpObject              = $queriedObject;
				$labels                = get_taxonomy_labels( get_taxonomy( $queriedObject->taxonomy ) );
				$data['editObjectUrl'] = get_edit_term_link( $queriedObject, $queriedObject->taxonomy );
			} else {
				$wpObject = aioseo()->helpers->getPost();

				if ( is_a( $wpObject, 'WP_Post' ) ) {
					$labels                = get_post_type_labels( get_post_type_object( $wpObject->post_type ) );
					$data['editObjectUrl'] = get_edit_post_link( $wpObject, 'url' );

					if (
						! aioseo()->helpers->isSpecialPage( $wpObject->ID ) &&
						'attachment' !== $templateType
					) {
						$aioseoPost            = Models\Post::getPost( $wpObject->ID );
						$data['page_analysis'] = Models\Post::getPageAnalysisDefaults( $aioseoPost->page_analysis );
						$data['keyphrases']    = Models\Post::getKeyphrasesDefaults( $aioseoPost->keyphrases );
					}
				}
			}

			// At this point if `$wpObject` is not an instance of WP_Term nor WP_Post, then we can't have the URLs.
			if (
				is_object( $wpObject ) &&
				is_object( $labels )
			) {
				$data['editObjectBtnText'] = sprintf(
					// Translators: 1 - A noun for something that's being edited ("Post", "Page", "Article", "Product", etc.).
					esc_html__( 'Edit %1$s', 'all-in-one-seo-pack' ),
					$labels->singular_name
				);
				$data['editGoogleSnippetUrl']   = $this->getEditSnippetUrl( $templateType, 'google', $wpObject );
				$data['editFacebookSnippetUrl'] = $this->getEditSnippetUrl( $templateType, 'facebook', $wpObject );
				$data['editTwitterSnippetUrl']  = $this->getEditSnippetUrl( $templateType, 'twitter', $wpObject );
			}
		}

		if (
			'archive' === $templateType ||
			'author' === $templateType ||
			'date' === $templateType ||
			'search' === $templateType
		) {
			if ( is_a( $queriedObject, 'WP_User' ) ) {
				$data['editObjectUrl']     = get_edit_user_link( $queriedObject->ID );
				$data['editObjectBtnText'] = esc_html__( 'Edit User', 'all-in-one-seo-pack' );
			}

			$data['editGoogleSnippetUrl'] = $this->getEditSnippetUrl( $templateType, 'google' );
		}

		if ( 'dynamic_home' === $templateType ) {
			$data['editGoogleSnippetUrl']   = $this->getEditSnippetUrl( $templateType, 'google' );
			$data['editFacebookSnippetUrl'] = $this->getEditSnippetUrl( $templateType, 'facebook' );
			$data['editTwitterSnippetUrl']  = $this->getEditSnippetUrl( $templateType, 'twitter' );
		}

		return $data;
	}

	/**
	 * Get the URL to the place where the snippet details can be edited.
	 *
	 * @since 4.2.8
	 *
	 * @param  string                 $templateType The WP template type {@see WpContext::getTemplateType}.
	 * @param  string                 $snippet      'google', 'facebook' or 'twitter'.
	 * @param  \WP_Post|\WP_Term|null $object       Post or term object.
	 * @return string                               The URL. Returns an empty string if nothing matches.
	 */
	private function getEditSnippetUrl( $templateType, $snippet, $object = null ) {
		$url = '';

		// Bail if `$snippet` doesn't fit requirements.
		if ( ! in_array( $snippet, [ 'google', 'facebook', 'twitter' ], true ) ) {
			return $url;
		}

		// If we're in a post/page/term (not an attachment) we'll have a URL directly to the meta box.
		if ( in_array( $templateType, [ 'single', 'page', 'attachment', 'taxonomy' ], true ) ) {
			$url = 'taxonomy' === $templateType
				? get_edit_term_link( $object, $object->taxonomy ) . '#aioseo-term-settings-field'
				: get_edit_post_link( $object, 'url' ) . '#aioseo-settings';

			$queryArgs = [ 'aioseo-tab' => 'general' ];
			if ( in_array( $snippet, [ 'facebook', 'twitter' ], true ) ) {
				$queryArgs = [
					'aioseo-tab' => 'social',
					'social-tab' => $snippet
				];
			}

			return add_query_arg( $queryArgs, $url );
		}

		// If we're in any sort of archive let's point to the global archive editing.
		if ( in_array( $templateType, [ 'archive', 'author', 'date', 'search' ], true ) ) {
			return admin_url( 'admin.php?page=aioseo-search-appearance' ) . '#/archives';
		}

		// If homepage is set to show the latest posts let's point to the global home page editing.
		if ( 'dynamic_home' === $templateType ) {
			// Default `$url` for 'google' snippet.
			$url = add_query_arg(
				[ 'aioseo-scroll' => 'home-page-settings' ],
				admin_url( 'admin.php?page=aioseo-search-appearance' ) . '#/global-settings'
			);

			if ( in_array( $snippet, [ 'facebook', 'twitter' ], true ) ) {
				$url = admin_url( 'admin.php?page=aioseo-social-networks' ) . '#/' . $snippet;
			}

			return $url;
		}

		return $url;
	}

	/**
	 * Returns the "SEO Preview" submenu item data ("node" as WP calls it).
	 *
	 * @since 4.2.8
	 *
	 * @return array The admin bar menu item data or an empty array if this feature is disabled.
	 */
	public function getAdminBarMenuItemNode() {
		if ( ! $this->enable ) {
			return [];
		}

		$title = esc_html__( 'SEO Preview', 'all-in-one-seo-pack' );

		// @TODO Remove 'NEW' after a couple months.
		$title .= '<span class="aioseo-menu-new-indicator">';
		$title .= esc_html__( 'NEW', 'all-in-one-seo-pack' ) . '!';
		$title .= '</span>';

		return [
			'id'     => 'aioseo-seo-preview',
			'parent' => 'aioseo-main',
			'title'  => $title,
			'href'   => '#',
		];
	}
}BuddyPress/Sitemap.php000066600000015654151147516220010766 0ustar00<?php

namespace AIOSEO\Plugin\Common\Standalone\BuddyPress;

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

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

/**
 * BuddyPress Sitemap class.
 *
 * @since 4.7.6
 */
class Sitemap {
	/**
	 * Returns the indexes for the sitemap root index.
	 *
	 * @since 4.7.6
	 *
	 * @return array The indexes.
	 */
	public function indexes() {
		$indexes           = [];
		$includedPostTypes = array_flip( aioseo()->sitemap->helpers->includedPostTypes() );
		$filterPostTypes   = array_filter( [
			BuddyPressIntegration::isComponentActive( 'activity' ) && isset( $includedPostTypes['bp-activity'] ) ? 'bp-activity' : '',
			BuddyPressIntegration::isComponentActive( 'group' ) && isset( $includedPostTypes['bp-group'] ) ? 'bp-group' : '',
			BuddyPressIntegration::isComponentActive( 'member' ) && isset( $includedPostTypes['bp-member'] ) ? 'bp-member' : '',
		] );

		foreach ( $filterPostTypes as $postType ) {
			$indexes = array_merge( $indexes, $this->buildIndexesPostType( $postType ) );
		}

		return $indexes;
	}

	/**
	 * Builds BuddyPress related root indexes.
	 *
	 * @since 4.7.6
	 *
	 * @param  string $postType The BuddyPress fake post type.
	 * @return array            The BuddyPress related root indexes.
	 */
	private function buildIndexesPostType( $postType ) {
		switch ( $postType ) {
			case 'bp-activity':
				return $this->buildIndexesActivity();
			case 'bp-group':
				return $this->buildIndexesGroup();
			case 'bp-member':
				return $this->buildIndexesMember();
			default:
				return [];
		}
	}

	/**
	 * Builds activity root indexes.
	 *
	 * @since 4.7.6
	 *
	 * @return array The activity root indexes.
	 */
	private function buildIndexesActivity() {
		$activityTable = aioseo()->core->db->prefix . 'bp_activity';
		$linksPerIndex = aioseo()->sitemap->linksPerIndex;
		$items         = aioseo()->core->db->execute(
			aioseo()->core->db->db->prepare(
				"SELECT id, date_recorded
				FROM (
					SELECT @row := @row + 1 AS rownum, id, date_recorded
					FROM (
						SELECT a.id, a.date_recorded FROM $activityTable as a
						WHERE a.is_spam = 0
							AND a.hide_sitewide = 0
							AND a.type NOT IN ('activity_comment', 'last_activity')
						ORDER BY a.date_recorded DESC
					) AS x
					CROSS JOIN (SELECT @row := 0) AS vars
					ORDER BY date_recorded DESC
				) AS y
				WHERE rownum = 1 OR rownum % %d = 1;",
				[
					$linksPerIndex
				]
			),
			true
		)->result();

		$totalItems = aioseo()->core->db->execute(
			"SELECT COUNT(*) as count
			FROM $activityTable as a
			WHERE a.is_spam = 0
				AND a.hide_sitewide = 0
				AND a.type NOT IN ('activity_comment', 'last_activity')
			",
			true
		)->result();

		$indexes = [];
		if ( $items ) {
			$filename = aioseo()->sitemap->filename;
			$count    = count( $items );
			for ( $i = 0; $i < $count; $i++ ) {
				$indexNumber = 0 !== $i && 1 < $count ? $i + 1 : '';

				$indexes[] = [
					'loc'     => aioseo()->helpers->localizedUrl( "/bp-activity-$filename$indexNumber.xml" ),
					'lastmod' => aioseo()->helpers->dateTimeToIso8601( $items[ $i ]->date_recorded ),
					'count'   => $linksPerIndex
				];
			}

			// We need to update the count of the last index since it won't necessarily be the same as the links per index.
			$indexes[ count( $indexes ) - 1 ]['count'] = $totalItems[0]->count - ( $linksPerIndex * ( $count - 1 ) );
		}

		return $indexes;
	}

	/**
	 * Builds group root indexes.
	 *
	 * @since 4.7.6
	 *
	 * @return array The group root indexes.
	 */
	private function buildIndexesGroup() {
		$groupsTable     = aioseo()->core->db->prefix . 'bp_groups';
		$groupsMetaTable = aioseo()->core->db->prefix . 'bp_groups_groupmeta';
		$linksPerIndex   = aioseo()->sitemap->linksPerIndex;
		$items           = aioseo()->core->db->execute(
			aioseo()->core->db->db->prepare(
				"SELECT id, date_modified
				FROM (
					SELECT @row := @row + 1 AS rownum, id, date_modified
					FROM (
						SELECT g.id, gm.group_id, MAX(gm.meta_value) as date_modified FROM $groupsTable as g
						INNER JOIN $groupsMetaTable AS gm ON g.id = gm.group_id
						WHERE g.status = 'public'
							AND gm.meta_key = 'last_activity'
						GROUP BY g.id
						ORDER BY date_modified DESC
					) AS x
					CROSS JOIN (SELECT @row := 0) AS vars
					ORDER BY date_modified DESC
				) AS y
				WHERE rownum = 1 OR rownum % %d = 1;",
				[
					$linksPerIndex
				]
			),
			true
		)->result();

		$totalItems = aioseo()->core->db->execute(
			"SELECT COUNT(*) as count
			FROM $groupsTable as g
			WHERE g.status = 'public'
			",
			true
		)->result();

		$indexes = [];
		if ( $items ) {
			$filename = aioseo()->sitemap->filename;
			$count    = count( $items );
			for ( $i = 0; $i < $count; $i++ ) {
				$indexNumber = 0 !== $i && 1 < $count ? $i + 1 : '';

				$indexes[] = [
					'loc'     => aioseo()->helpers->localizedUrl( "/bp-group-$filename$indexNumber.xml" ),
					'lastmod' => aioseo()->helpers->dateTimeToIso8601( $items[ $i ]->date_modified ),
					'count'   => $linksPerIndex
				];
			}

			// We need to update the count of the last index since it won't necessarily be the same as the links per index.
			$indexes[ count( $indexes ) - 1 ]['count'] = $totalItems[0]->count - ( $linksPerIndex * ( $count - 1 ) );
		}

		return $indexes;
	}

	/**
	 * Builds member root indexes.
	 *
	 * @since 4.7.6
	 *
	 * @return array The member root indexes.
	 */
	private function buildIndexesMember() {
		$activityTable = aioseo()->core->db->prefix . 'bp_activity';
		$linksPerIndex = aioseo()->sitemap->linksPerIndex;
		$items         = aioseo()->core->db->execute(
			aioseo()->core->db->db->prepare(
				"SELECT user_id, date_recorded
				FROM (
					SELECT @row := @row + 1 AS rownum, user_id, date_recorded
					FROM (
						SELECT a.user_id, a.date_recorded FROM $activityTable as a
						WHERE a.component = 'members'
							AND a.type = 'last_activity'
						ORDER BY a.date_recorded DESC
					) AS x
					CROSS JOIN (SELECT @row := 0) AS vars
					ORDER BY date_recorded DESC
				) AS y
				WHERE rownum = 1 OR rownum % %d = 1;",
				[
					$linksPerIndex
				]
			),
			true
		)->result();

		$totalItems = aioseo()->core->db->execute(
			"SELECT COUNT(*) as count
			FROM $activityTable as a
			WHERE a.component = 'members'
				AND a.type = 'last_activity'
			",
			true
		)->result();

		$indexes = [];
		if ( $items ) {
			$filename = aioseo()->sitemap->filename;
			$count    = count( $items );
			for ( $i = 0; $i < $count; $i++ ) {
				$indexNumber = 0 !== $i && 1 < $count ? $i + 1 : '';

				$indexes[] = [
					'loc'     => aioseo()->helpers->localizedUrl( "/bp-member-$filename$indexNumber.xml" ),
					'lastmod' => aioseo()->helpers->dateTimeToIso8601( $items[ $i ]->date_recorded ),
					'count'   => $linksPerIndex
				];
			}

			// We need to update the count of the last index since it won't necessarily be the same as the links per index.
			$indexes[ count( $indexes ) - 1 ]['count'] = $totalItems[0]->count - ( $linksPerIndex * ( $count - 1 ) );
		}

		return $indexes;
	}
}BuddyPress/Component.php000066600000030273151147516220011320 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\BuddyPress;

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

use AIOSEO\Plugin\Common\Integrations\BuddyPress as BuddyPressIntegration;
use AIOSEO\Plugin\Common\Schema\Graphs as CommonGraphs;

/**
 * BuddyPress Component class.
 *
 * @since 4.7.6
 */
class Component {
	/**
	 * The current component template type.
	 *
	 * @since 4.7.6
	 *
	 * @var string|null
	 */
	public $templateType = null;

	/**
	 * The component ID.
	 *
	 * @since 4.7.6
	 *
	 * @var int
	 */
	public $id = 0;

	/**
	 * The component author.
	 *
	 * @since 4.7.6
	 *
	 * @var \WP_User|false
	 */
	public $author = false;

	/**
	 * The component date.
	 *
	 * @since 4.7.6
	 *
	 * @var int|false
	 */
	public $date = false;

	/**
	 * The activity single page data.
	 *
	 * @since 4.7.6
	 *
	 * @var array
	 */
	public $activity = [];

	/**
	 * The group single page data.
	 *
	 * @since 4.7.6
	 *
	 * @var array
	 */
	public $group = [];

	/**
	 * The type of the group archive page.
	 *
	 * @since 4.7.6
	 *
	 * @var array
	 */
	public $groupType = [];

	/**
	 * Class constructor.
	 *
	 * @since 4.7.6
	 */
	public function __construct() {
		if ( is_admin() ) {
			return;
		}

		$this->setTemplateType();
		$this->setId();
		$this->setAuthor();
		$this->setDate();
		$this->setActivity();
		$this->setGroup();
		$this->setGroupType();
	}

	/**
	 * Sets the template type.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	private function setTemplateType() {
		if ( BuddyPressIntegration::callFunc( 'bp_is_single_activity' ) ) {
			$this->templateType = 'bp-activity_single';
		} elseif ( BuddyPressIntegration::callFunc( 'bp_is_group' ) ) {
			$this->templateType = 'bp-group_single';
		} elseif (
			BuddyPressIntegration::callFunc( 'bp_is_user' ) &&
			false === BuddyPressIntegration::callFunc( 'bp_is_single_activity' )
		) {
			$this->templateType = 'bp-member_single';
		} elseif ( BuddyPressIntegration::callFunc( 'bp_is_activity_directory' ) ) {
			$this->templateType = 'bp-activity_archive';
		} elseif ( BuddyPressIntegration::callFunc( 'bp_is_members_directory' ) ) {
			$this->templateType = 'bp-member_archive';
		} elseif ( BuddyPressIntegration::callFunc( 'bp_is_groups_directory' ) ) {
			$this->templateType = 'bp-group_archive';
		} elseif (
			BuddyPressIntegration::callFunc( 'bp_is_current_action', 'feed' ) &&
			BuddyPressIntegration::callFunc( 'bp_is_activity_component' )
		) {
			$this->templateType = 'bp-activity_feed';
		}
	}

	/**
	 * Sets the component ID.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	private function setId() {
		switch ( $this->templateType ) {
			case 'bp-activity_single':
				$id = get_query_var( 'bp_member_action' );
				break;
			case 'bp-group_single':
				$id = get_query_var( 'bp_group' );
				break;
			case 'bp-member_single':
				$id = get_query_var( 'bp_member' );
				break;
			default:
				$id = $this->id;
		}

		$this->id = $id;
	}

	/**
	 * Sets the component author.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	private function setAuthor() {
		switch ( $this->templateType ) {
			case 'bp-activity_single':
				if ( ! $this->activity ) {
					$this->setActivity();
				}

				if ( $this->activity ) {
					$this->author = get_user_by( 'id', $this->activity['user_id'] );

					return;
				}

				break;
			case 'bp-group_single':
				if ( ! $this->group ) {
					$this->setGroup();
				}

				if ( $this->group ) {
					$this->author = get_user_by( 'id', $this->group['creator_id'] );

					return;
				}

				break;
			case 'bp-member_single':
				$this->author = get_user_by( 'slug', $this->id );

				return;
		}
	}

	/**
	 * Sets the component date.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	private function setDate() {
		switch ( $this->templateType ) {
			case 'bp-activity_single':
				if ( ! $this->activity ) {
					$this->setActivity();
				}
				$date = strtotime( $this->activity['date_recorded'] );
				break;
			case 'bp-group_single':
				if ( ! $this->group ) {
					$this->setGroup();
				}
				$date = strtotime( $this->group['date_created'] );
				break;
			default:
				$date = $this->date;
		}

		$this->date = $date;
	}

	/**
	 * Sets the activity data.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	private function setActivity() {
		if ( 'bp-activity_single' !== $this->templateType ) {
			return;
		}

		$activities = BuddyPressIntegration::callFunc( 'bp_activity_get_specific', [
			'activity_ids'     => [ $this->id ],
			'display_comments' => true
		] );
		if ( ! empty( $activities['activities'] ) ) {
			list( $activity ) = current( $activities );

			$this->activity = (array) $activity;

			// The `content_rendered` is AIOSEO specific.
			$this->activity['content_rendered'] = $this->activity['content'] ?? '';
			if ( ! empty( $this->activity['content'] ) ) {
				$this->activity['content_rendered'] = apply_filters( 'bp_get_activity_content', $this->activity['content'] );
			}

			return;
		}

		$this->resetComponent();
	}

	/**
	 * Sets the group data.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	private function setGroup() {
		if ( 'bp-group_single' !== $this->templateType ) {
			return;
		}

		$group = BuddyPressIntegration::callFunc( 'bp_get_group_by', 'slug', $this->id );
		if ( ! empty( $group ) ) {
			$this->group = (array) $group;

			return;
		}

		$this->resetComponent();
	}

	/**
	 * Sets the group type.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	private function setGroupType() {
		if ( 'bp-group_archive' !== $this->templateType ) {
			return;
		}

		$type = BuddyPressIntegration::callFunc( 'bp_get_current_group_directory_type' );
		if ( ! $type ) {
			return;
		}

		$term = get_term_by( 'slug', $type, 'bp_group_type' );
		if ( ! $term ) {
			return;
		}

		$meta = get_metadata( 'term', $term->term_id );
		if ( ! $meta ) {
			return;
		}

		$this->groupType = [
			'singular' => $meta['bp_type_singular_name'][0] ?? '',
			'plural'   => $meta['bp_type_name'][0] ?? '',
		];
	}

	/**
	 * Resets some of the component properties.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	private function resetComponent() {
		$this->templateType = null;
		$this->id           = 0;
	}

	/**
	 * Retrieves the SEO metadata value.
	 *
	 * @since 4.7.6
	 *
	 * @param  string $which The SEO metadata to get.
	 * @return string        The SEO metadata value.
	 */
	public function getMeta( $which ) {
		list( $postType, $suffix ) = explode( '_', $this->templateType );

		switch ( $which ) {
			case 'title':
				$meta = 'single' === $suffix
					? aioseo()->meta->title->getPostTypeTitle( $postType )
					: aioseo()->meta->title->getArchiveTitle( $postType );
				$meta = aioseo()->meta->description->helpers->bpSanitize( $meta, $this->id );
				break;
			case 'description':
				$meta = 'single' === $suffix
					? aioseo()->meta->description->getPostTypeDescription( $postType )
					: aioseo()->meta->description->getArchiveDescription( $postType );
				$meta = aioseo()->meta->description->helpers->bpSanitize( $meta, $this->id );
				break;
			case 'keywords':
				$meta = 'single' === $suffix
					? ''
					: aioseo()->meta->keywords->getArchiveKeywords( $postType );
				$meta = aioseo()->meta->keywords->prepareKeywords( $meta );
				break;
			case 'robots':
				$dynamicOptions = aioseo()->dynamicOptions->noConflict();
				if ( 'single' === $suffix && $dynamicOptions->searchAppearance->postTypes->has( $postType ) ) {
					aioseo()->meta->robots->globalValues( [ 'postTypes', $postType ], true );
				} elseif ( $dynamicOptions->searchAppearance->archives->has( $postType ) ) {
					aioseo()->meta->robots->globalValues( [ 'archives', $postType ], true );
				}

				$meta = aioseo()->meta->robots->metaHelper();
				break;
			case 'canonical':
				$meta = '';
				if ( 'single' === $suffix ) {
					if ( 'bp-member' === $postType ) {
						$meta = BuddyPressIntegration::getComponentSingleUrl( 'member', $this->author->ID );
					} elseif ( 'bp-group' === $postType ) {
						$meta = BuddyPressIntegration::getComponentSingleUrl( 'group', $this->group['id'] );
					}
				}
				break;
			default:
				$meta = '';
		}

		return $meta;
	}

	/**
	 * Determines the schema type for the current component.
	 *
	 * @since 4.7.6
	 *
	 * @param  \AIOSEO\Plugin\Common\Schema\Context $contextInstance The Context class instance.
	 * @return void
	 */
	public function determineSchemaGraphsAndContext( $contextInstance ) {
		list( $postType ) = explode( '_', $this->templateType );

		$dynamicOptions = aioseo()->dynamicOptions->noConflict();
		if ( $dynamicOptions->searchAppearance->postTypes->has( $postType ) ) {
			$defaultType = $dynamicOptions->searchAppearance->postTypes->{$postType}->schemaType;
			switch ( $defaultType ) {
				case 'Article':
					aioseo()->schema->graphs[] = $dynamicOptions->searchAppearance->postTypes->{$postType}->articleType;
					break;
				case 'WebPage':
					aioseo()->schema->graphs[] = $dynamicOptions->searchAppearance->postTypes->{$postType}->webPageType;
					break;
				default:
					aioseo()->schema->graphs[] = $defaultType;
			}
		}

		switch ( $this->templateType ) {
			case 'bp-activity_single':
				$datePublished = $this->activity['date_recorded'];
				$contextUrl    = BuddyPressIntegration::getComponentSingleUrl( 'activity', $this->activity['id'] );

				break;
			case 'bp-group_single':
				$datePublished = $this->group['date_created'];
				$contextUrl    = BuddyPressIntegration::getComponentSingleUrl( 'group', $this->group['id'] );

				break;
			case 'bp-member_single':
				aioseo()->schema->graphs[] = 'ProfilePage';

				$contextUrl = BuddyPressIntegration::getComponentSingleUrl( 'member', $this->author->ID );

				break;
			case 'bp-activity_archive':
			case 'bp-group_archive':
			case 'bp-member_archive':
				list( , $component ) = explode( '-', $postType );

				$contextUrl     = BuddyPressIntegration::getComponentArchiveUrl( $component );
				$breadcrumbType = 'CollectionPage';

				break;
			default:
				break;
		}

		if ( ! empty( $datePublished ) ) {
			CommonGraphs\Article\NewsArticle::setOverwriteGraphData( [
				'properties' => compact( 'datePublished' )
			] );
		}

		if ( ! empty( $contextUrl ) ) {
			$name                = aioseo()->meta->title->getTitle();
			$description         = aioseo()->meta->description->getDescription();
			$breadcrumbPositions = [
				'name'        => $name,
				'description' => $description,
				'url'         => $contextUrl,
			];

			if ( ! empty( $breadcrumbType ) ) {
				$breadcrumbPositions['type'] = $breadcrumbType;
			}

			aioseo()->schema->context = [
				'name'        => $name,
				'description' => $description,
				'url'         => $contextUrl,
				'breadcrumb'  => $contextInstance->breadcrumb->setPositions( $breadcrumbPositions ),
			];
		}
	}

	/**
	 * Gets the breadcrumbs for the current component.
	 *
	 * @since 4.7.6
	 *
	 * @return array
	 */
	public function getCrumbs() {
		$crumbs = [];
		switch ( $this->templateType ) {
			case 'bp-activity_single':
				$crumbs[] = aioseo()->breadcrumbs->makeCrumb(
					BuddyPressIntegration::callFunc( 'bp_get_directory_title', 'activity' ),
					BuddyPressIntegration::getComponentArchiveUrl( 'activity' )
				);
				$crumbs[] = aioseo()->breadcrumbs->makeCrumb( sanitize_text_field( $this->activity['action'] ) );
				break;
			case 'bp-group_single':
				$crumbs[] = aioseo()->breadcrumbs->makeCrumb(
					BuddyPressIntegration::callFunc( 'bp_get_directory_title', 'groups' ),
					BuddyPressIntegration::getComponentArchiveUrl( 'group' )
				);
				$crumbs[] = aioseo()->breadcrumbs->makeCrumb( $this->group['name'] );
				break;
			case 'bp-member_single':
				$crumbs[] = aioseo()->breadcrumbs->makeCrumb(
					BuddyPressIntegration::callFunc( 'bp_get_directory_title', 'members' ),
					BuddyPressIntegration::getComponentArchiveUrl( 'member' )
				);
				$crumbs[] = aioseo()->breadcrumbs->makeCrumb( $this->author->display_name );
				break;
			case 'bp-activity_archive':
				$crumbs[] = aioseo()->breadcrumbs->makeCrumb( BuddyPressIntegration::callFunc( 'bp_get_directory_title', 'activity' ) );
				break;
			case 'bp-group_archive':
				$crumbs[] = aioseo()->breadcrumbs->makeCrumb( BuddyPressIntegration::callFunc( 'bp_get_directory_title', 'groups' ) );
				break;
			case 'bp-member_archive':
				$crumbs[] = aioseo()->breadcrumbs->makeCrumb( BuddyPressIntegration::callFunc( 'bp_get_directory_title', 'members' ) );
				break;
			default:
				break;
		}

		return $crumbs;
	}
}BuddyPress/BuddyPress.php000066600000023313151147516220011437 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\BuddyPress;

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

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

/**
 * Handles the BuddyPress integration with AIOSEO.
 *
 * @since 4.7.6
 */
class BuddyPress {
	/**
	 * Instance of the Tags class.
	 *
	 * @since 4.7.6
	 *
	 * @var Tags
	 */
	public $tags;

	/**
	 * Instance of the Component class.
	 *
	 * @since 4.7.6
	 *
	 * @var Component
	 */
	public $component;

	/**
	 * Instance of the Sitemap class.
	 *
	 * @since 4.7.6
	 *
	 * @var Sitemap
	 */
	public $sitemap = null;

	/**
	 * Class constructor.
	 *
	 * @since 4.7.6
	 */
	public function __construct() {
		if (
			aioseo()->helpers->isAjaxCronRestRequest() ||
			! aioseo()->helpers->isPluginActive( 'buddypress' )
		) {
			return;
		}

		// Hook into `plugins_loaded` to ensure BuddyPress has loaded some necessary functions.
		add_action( 'plugins_loaded', [ $this, 'maybeLoad' ], 20 );
	}

	/**
	 * Hooked into `plugins_loaded` action hook.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	public function maybeLoad() {
		// If the BuddyPress version is below 12 we bail.
		if ( ! function_exists( 'bp_get_version' ) || version_compare( bp_get_version(), '12', '<' ) ) {
			return;
		}

		// If none of the necessary BuddyPress components are active we bail.
		if (
			! BuddyPressIntegration::isComponentActive( 'activity' ) &&
			! BuddyPressIntegration::isComponentActive( 'group' ) &&
			! BuddyPressIntegration::isComponentActive( 'member' )
		) {
			return;
		}

		$this->sitemap = new Sitemap();

		add_action( 'init', [ $this, 'setTags' ], 20 );
		add_action( 'bp_parse_query', [ $this, 'setComponent' ], 20 );
	}

	/**
	 * Hooked into `init` action hook.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	public function setTags() {
		$this->tags = new Tags();
	}

	/**
	 * Hooked into `bp_parse_query` action hook.
	 *
	 * @since 4.7.6
	 *
	 * @return void
	 */
	public function setComponent() {
		$this->component = new Component();
	}

	/**
	 * Adds the BuddyPress fake post types to the list of post types, so they appear under e.g. Search Appearance.
	 *
	 * @since 4.7.6
	 *
	 * @param  array $postTypes       Public post types from {@see \AIOSEO\Plugin\Common\Traits\Helpers\Wp::getPublicPostTypes}.
	 * @param  bool  $namesOnly       Whether only the names should be included.
	 * @param  bool  $hasArchivesOnly Whether to only include post types which have archives.
	 * @param  array $args            Additional arguments.
	 * @return void
	 */
	public function maybeAddPostTypes( &$postTypes, $namesOnly, $hasArchivesOnly, $args ) {
		// If one of these CPTs is already registered we bail, so we don't overwrite them and possibly break something.
		if (
			post_type_exists( 'bp-activity' ) ||
			post_type_exists( 'bp-group' ) ||
			post_type_exists( 'bp-member' )
		) {
			return;
		}

		/**
		 * The BP components are registered with the `buddypress` CPT which is not viewable, so we add it here to include our metadata inside <head>.
		 * {@see \AIOSEO\Plugin\Common\Main\Head::wpHead}.
		 */
		if (
			$namesOnly &&
			doing_action( 'wp_head' )
		) {
			$postTypes = array_merge( $postTypes, [ 'buddypress' ] );

			return;
		}

		$fakePostTypes = $this->getFakePostTypes();

		if ( ! BuddyPressIntegration::isComponentActive( 'activity' ) ) {
			unset( $fakePostTypes['bp-activity'] );
		}

		if ( ! BuddyPressIntegration::isComponentActive( 'group' ) ) {
			unset( $fakePostTypes['bp-group'] );
		}

		if ( ! BuddyPressIntegration::isComponentActive( 'member' ) ) {
			unset( $fakePostTypes['bp-member'] );
		}

		if ( $hasArchivesOnly ) {
			$fakePostTypes = array_filter( $fakePostTypes, function ( $postType ) {
				return $postType['hasArchive'];
			} );
		}

		if ( $namesOnly ) {
			$fakePostTypes = array_keys( $fakePostTypes );
		}

		// 0. Below we'll add/merge the BuddyPress post types only under certain conditions.
		$fakePostTypes = array_values( $fakePostTypes );
		$currentScreen = aioseo()->helpers->getCurrentScreen();

		if (
			// 1. If the `buddypress` CPT is set in the list of post types to be included.
			( ! empty( $args['include'] ) && in_array( 'buddypress', $args['include'], true ) ) ||
			// 2. If the current request is for the sitemap.
			( ! empty( aioseo()->sitemap->filename ) && 'general' === ( aioseo()->sitemap->type ?? '' ) ) ||
			// 3. If we're on the Search Appearance screen.
			( $currentScreen && strpos( $currentScreen->id, 'aioseo-search-appearance' ) !== false ) ||
			// 4. If we're on the BuddyPress component front-end screen.
			BuddyPressIntegration::isComponentPage()
		) {
			$postTypes = array_merge( $postTypes, $fakePostTypes );
		}
	}

	/**
	 * Get edit links for the SEO Preview data.
	 *
	 * @since 4.7.6
	 *
	 * @return array
	 */
	public function getVueDataSeoPreview() {
		$data = [
			'editGoogleSnippetUrl' => '',
			'editObjectBtnText'    => '',
			'editObjectUrl'        => '',
		];

		list( $postType, $suffix ) = explode( '_', aioseo()->standalone->buddyPress->component->templateType );

		$bpFakePostTypes  = $this->getFakePostTypes();
		$fakePostTypeData = array_values( wp_list_filter( $bpFakePostTypes, [ 'name' => $postType ] ) );
		$fakePostTypeData = $fakePostTypeData[0] ?? [];
		if ( ! $fakePostTypeData ) {
			return $data;
		}

		if ( 'single' === $suffix ) {
			switch ( $postType ) {
				case 'bp-activity':
					$componentId = aioseo()->standalone->buddyPress->component->activity['id'];
					break;
				case 'bp-group':
					$componentId = aioseo()->standalone->buddyPress->component->group['id'];
					break;
				case 'bp-member':
					$componentId = aioseo()->standalone->buddyPress->component->author->ID;
					break;
				default:
					$componentId = 0;
			}
		}

		$scrollToId                   = 'aioseo-card-' . $postType . ( 'single' === $suffix ? 'SA' : 'ArchiveArchives' );
		$data['editGoogleSnippetUrl'] = 'single' === $suffix
			? admin_url( 'admin.php?page=aioseo-search-appearance' ) . '#/content-types'
			: admin_url( 'admin.php?page=aioseo-search-appearance' ) . '#/archives';
		$data['editGoogleSnippetUrl'] = add_query_arg( [
			'aioseo-scroll'    => $scrollToId,
			'aioseo-highlight' => $scrollToId
		], $data['editGoogleSnippetUrl'] );

		$data['editObjectBtnText'] = sprintf(
			// Translators: 1 - A noun for something that's being edited ("Post", "Page", "Article", "Product", etc.).
			esc_html__( 'Edit %1$s', 'all-in-one-seo-pack' ),
			'single' === $suffix ? $fakePostTypeData['singular'] : $fakePostTypeData['label']
		);

		list( , $component ) = explode( '-', $postType );

		$data['editObjectUrl'] = 'single' === $suffix
			? BuddyPressIntegration::getComponentEditUrl( $component, $componentId ?? 0 )
			: BuddyPressIntegration::callFunc( 'bp_get_admin_url', add_query_arg( 'page', 'bp-rewrites', 'admin.php' ) );

		return $data;
	}

	/**
	 * Retrieves the BuddyPress fake post types.
	 *
	 * @since 4.7.6
	 *
	 * @return array The BuddyPress fake post types.
	 */
	public function getFakePostTypes() {
		return [
			'bp-activity' => [
				'name'               => 'bp-activity',
				'label'              => sprintf(
					// Translators: 1 - The hard coded string 'BuddyPress'.
					_x( 'Activities (%1$s)', 'BuddyPress', 'all-in-one-seo-pack' ),
					'BuddyPress'
				),
				'singular'           => 'Activity',
				'icon'               => 'dashicons-buddicons-buddypress-logo',
				'hasExcerpt'         => false,
				'hasArchive'         => true,
				'hierarchical'       => false,
				'taxonomies'         => [],
				'slug'               => 'bp-activity',
				'buddyPress'         => true,
				'defaultTags'        => [
					'postTypes' => [
						'title'       => [
							'bp_activity_action',
							'separator_sa',
							'site_title',
						],
						'description' => [
							'bp_activity_content',
							'separator_sa'
						]
					]
				],
				'defaultTitle'       => '#bp_activity_action #separator_sa #site_title',
				'defaultDescription' => '#bp_activity_content',
			],
			'bp-group'    => [
				'name'               => 'bp-group',
				'label'              => sprintf(
					// Translators: 1 - The hard coded string 'BuddyPress'.
					_x( 'Groups (%1$s)', 'BuddyPress', 'all-in-one-seo-pack' ),
					'BuddyPress'
				),
				'singular'           => 'Group',
				'icon'               => 'dashicons-buddicons-buddypress-logo',
				'hasExcerpt'         => false,
				'hasArchive'         => true,
				'hierarchical'       => false,
				'taxonomies'         => [],
				'slug'               => 'bp-group',
				'buddyPress'         => true,
				'defaultTags'        => [
					'postTypes' => [
						'title'       => [
							'bp_group_name',
							'separator_sa',
							'site_title',
						],
						'description' => [
							'bp_group_description',
							'separator_sa'
						]
					]
				],
				'defaultTitle'       => '#bp_group_name #separator_sa #site_title',
				'defaultDescription' => '#bp_group_description',
			],
			'bp-member'   => [
				'name'               => 'bp-member',
				'label'              => sprintf(
					// Translators: 1 - The hard coded string 'BuddyPress'.
					_x( 'Members (%1$s)', 'BuddyPress', 'all-in-one-seo-pack' ),
					'BuddyPress'
				),
				'singular'           => 'Member',
				'icon'               => 'dashicons-buddicons-buddypress-logo',
				'hasExcerpt'         => false,
				'hasArchive'         => true,
				'hierarchical'       => false,
				'taxonomies'         => [],
				'slug'               => 'bp-member',
				'buddyPress'         => true,
				'defaultTags'        => [
					'postTypes' => [
						'title'       => [
							'author_name',
							'separator_sa',
							'site_title',
						],
						'description' => [
							'author_bio',
							'separator_sa'
						]
					]
				],
				'defaultTitle'       => '#author_name #separator_sa #site_title',
				'defaultDescription' => '#author_bio',
			],
		];
	}
}BuddyPress/Tags.php000066600000023531151147516220010253 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\BuddyPress;

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

/**
 * BuddyPress Tags class.
 *
 * @since 4.7.6
 */
class Tags {
	/**
	 * Class constructor.
	 *
	 * @since 4.7.6
	 */
	public function __construct() {
		aioseo()->tags->addContext( $this->getContexts() );
		aioseo()->tags->addTags( $this->getTags() );
	}

	/**
	 * Retrieves the contexts for BuddyPress.
	 *
	 * @since 4.7.6
	 *
	 * @return array An array of contextual data.
	 */
	public function getContexts() {
		return [
			'bp-activityTitle'              => [
				'author_first_name',
				'author_last_name',
				'author_name',
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'post_date',
				'post_day',
				'post_month',
				'post_year',
				'separator_sa',
				'site_title',
				'tagline',
				'bp_activity_action',
				'bp_activity_content',
			],
			'bp-activityArchiveTitle'       => [
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'separator_sa',
				'site_title',
				'tagline',
				'archive_title',
			],
			'bp-activityDescription'        => [
				'author_first_name',
				'author_last_name',
				'author_name',
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'post_date',
				'post_day',
				'post_month',
				'post_year',
				'separator_sa',
				'site_title',
				'tagline',
				'bp_activity_action',
				'bp_activity_content',
			],
			'bp-activityArchiveDescription' => [
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'separator_sa',
				'site_title',
				'tagline',
				'archive_title',
			],
			'bp-groupTitle'                 => [
				'author_first_name',
				'author_last_name',
				'author_name',
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'post_date',
				'post_day',
				'post_month',
				'post_year',
				'separator_sa',
				'site_title',
				'tagline',
				'bp_group_name',
				'bp_group_description',
			],
			'bp-groupArchiveTitle'          => [
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'separator_sa',
				'site_title',
				'tagline',
				'archive_title',
				'bp_group_type_singular_name',
				'bp_group_type_plural_name',
			],
			'bp-groupDescription'           => [
				'author_first_name',
				'author_last_name',
				'author_name',
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'post_date',
				'post_day',
				'post_month',
				'post_year',
				'separator_sa',
				'site_title',
				'tagline',
				'bp_group_name',
				'bp_group_description',
			],
			'bp-groupArchiveDescription'    => [
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'separator_sa',
				'site_title',
				'tagline',
				'archive_title',
				'bp_group_type_singular_name',
				'bp_group_type_plural_name',
			],
			'bp-memberTitle'                => [
				'author_first_name',
				'author_last_name',
				'author_name',
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'separator_sa',
				'site_title',
				'tagline',
			],
			'bp-memberArchiveTitle'         => [
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'separator_sa',
				'site_title',
				'tagline',
				'archive_title',
			],
			'bp-memberDescription'          => [
				'author_first_name',
				'author_last_name',
				'author_name',
				'author_bio',
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'separator_sa',
				'site_title',
				'tagline',
			],
			'bp-memberArchiveDescription'   => [
				'current_date',
				'current_day',
				'current_month',
				'current_year',
				'separator_sa',
				'site_title',
				'tagline',
				'archive_title',
			],
		];
	}

	/**
	 * Retrieves the custom tags for BuddyPress.
	 *
	 * @since 4.7.6
	 *
	 * @return array An array of tags.
	 */
	public function getTags() {
		return [
			[
				'id'          => 'bp_activity_action',
				'name'        => _x( 'Activity Action', 'BuddyPress', 'all-in-one-seo-pack' ),
				'description' => _x( 'The activity action.', 'BuddyPress', 'all-in-one-seo-pack' ),
				'instance'    => $this,
			],
			[
				'id'          => 'bp_activity_content',
				'name'        => _x( 'Activity Content', 'BuddyPress', 'all-in-one-seo-pack' ),
				'description' => _x( 'The activity content.', 'BuddyPress', 'all-in-one-seo-pack' ),
				'instance'    => $this,
			],
			[
				'id'          => 'bp_group_name',
				'name'        => _x( 'Group Name', 'BuddyPress', 'all-in-one-seo-pack' ),
				'description' => _x( 'The group name.', 'BuddyPress', 'all-in-one-seo-pack' ),
				'instance'    => $this,
			],
			[
				'id'          => 'bp_group_description',
				'name'        => _x( 'Group Description', 'BuddyPress', 'all-in-one-seo-pack' ),
				'description' => _x( 'The group description.', 'BuddyPress', 'all-in-one-seo-pack' ),
				'instance'    => $this,
			],
			[
				'id'          => 'bp_group_type_singular_name',
				'name'        => _x( 'Group Type Singular Name', 'BuddyPress', 'all-in-one-seo-pack' ),
				'description' => _x( 'The group type singular name.', 'BuddyPress', 'all-in-one-seo-pack' ),
				'instance'    => $this,
			],
			[
				'id'          => 'bp_group_type_plural_name',
				'name'        => _x( 'Group Type Plural Name', 'BuddyPress', 'all-in-one-seo-pack' ),
				'description' => _x( 'The group type plural name.', 'BuddyPress', 'all-in-one-seo-pack' ),
				'instance'    => $this,
			],
		];
	}

	/**
	 * Replace the tags in the string provided.
	 *
	 * @since 4.7.6
	 *
	 * @param  string $string The string to look for tags in.
	 * @param  int    $id     The object ID.
	 * @return string         The string with tags replaced.
	 */
	public function replaceTags( $string, $id ) {
		if ( ! $string || ! preg_match( '/' . aioseo()->tags->denotationChar . '/', $string ) ) {
			return $string;
		}

		foreach ( array_unique( aioseo()->helpers->flatten( $this->getContexts() ) ) as $tag ) {
			$tagId   = aioseo()->tags->denotationChar . $tag;
			$pattern = "/$tagId(?![a-zA-Z0-9_])/im";
			if ( preg_match( $pattern, $string ) ) {
				$tagValue = $this->getTagValue( [ 'id' => $tag ], $id );
				$string   = preg_replace( $pattern, '%|%' . aioseo()->helpers->escapeRegexReplacement( $tagValue ), $string );
			}
		}

		return str_replace( '%|%', '', $string );
	}

	/**
	 * Get the value of the tag to replace.
	 *
	 * @since 4.7.6
	 *
	 * @param  array    $tag        The tag to look for.
	 * @param  int|null $id         The object ID.
	 * @param  bool     $sampleData Whether to fill empty values with sample data.
	 * @return string               The value of the tag.
	 */
	public function getTagValue( $tag, $id = null, $sampleData = false ) {
		$sampleData = $sampleData || empty( aioseo()->standalone->buddyPress->component->templateType );

		switch ( $tag['id'] ) {
			case 'author_bio':
				$out = $sampleData
					? __( 'Sample author biography', 'all-in-one-seo-pack' )
					: aioseo()->standalone->buddyPress->component->author->description;
				break;
			case 'author_first_name':
				$out = $sampleData
					? wp_get_current_user()->first_name
					: aioseo()->standalone->buddyPress->component->author->first_name;
				break;
			case 'author_last_name':
				$out = $sampleData
					? wp_get_current_user()->last_name
					: aioseo()->standalone->buddyPress->component->author->last_name;
				break;
			case 'author_name':
				$out = $sampleData
					? wp_get_current_user()->display_name
					: aioseo()->standalone->buddyPress->component->author->display_name;
				break;
			case 'post_date':
				$out = $sampleData
					? aioseo()->tags->formatDateAsI18n( date_i18n( 'U' ) )
					: aioseo()->tags->formatDateAsI18n( aioseo()->standalone->buddyPress->component->date );
				break;
			case 'post_day':
				$out = $sampleData
					? date_i18n( 'd' )
					: date( 'd', aioseo()->standalone->buddyPress->component->date );
				break;
			case 'post_month':
				$out = $sampleData
					? date_i18n( 'F' )
					: date( 'F', aioseo()->standalone->buddyPress->component->date );
				break;
			case 'post_year':
				$out = $sampleData
					? date_i18n( 'Y' )
					: date( 'Y', aioseo()->standalone->buddyPress->component->date );
				break;
			case 'archive_title':
				$out = $sampleData
					? __( 'Sample Archive Title', 'all-in-one-seo-pack' )
					: esc_html( get_the_title() );
				break;
			case 'bp_activity_action':
				$out = $sampleData
					? _x( 'Sample Activity Action', 'BuddyPress', 'all-in-one-seo-pack' )
					: aioseo()->standalone->buddyPress->component->activity['action'];
				break;
			case 'bp_activity_content':
				$out = $sampleData
					? _x( 'Sample activity content', 'BuddyPress', 'all-in-one-seo-pack' )
					: aioseo()->standalone->buddyPress->component->activity['content_rendered'];
				break;
			case 'bp_group_name':
				$out = $sampleData
					? _x( 'Sample Group Name', 'BuddyPress', 'all-in-one-seo-pack' )
					: aioseo()->standalone->buddyPress->component->group['name'];
				break;
			case 'bp_group_description':
				$out = $sampleData
					? _x( 'Sample group description', 'BuddyPress', 'all-in-one-seo-pack' )
					: aioseo()->standalone->buddyPress->component->group['description'];
				break;
			case 'bp_group_type_singular_name':
				$out = $sampleData ? _x( 'Sample Type Singular', 'BuddyPress', 'all-in-one-seo-pack' ) : '';
				if ( ! empty( aioseo()->standalone->buddyPress->component->groupType ) ) {
					$out = aioseo()->standalone->buddyPress->component->groupType['singular'];
				}
				break;
			case 'bp_group_type_plural_name':
				$out = $sampleData ? _x( 'Sample Type Plural', 'BuddyPress', 'all-in-one-seo-pack' ) : '';
				if ( ! empty( aioseo()->standalone->buddyPress->component->groupType ) ) {
					$out = aioseo()->standalone->buddyPress->component->groupType['plural'];
				}
				break;
			default:
				$out = aioseo()->tags->getTagValue( $tag, $id );
		}

		return $out ?? '';
	}
}DetailsColumn.php000066600000020310151147516220010024 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

use AIOSEO\Plugin\Common\Models;

/**
 * Handles the AIOSEO Details post column.
 *
 * @since 4.2.0
 */
class DetailsColumn {
	/**
	 * The slug for the script.
	 *
	 * @since 4.2.0
	 *
	 * @var string
	 */
	protected $scriptSlug = 'src/vue/standalone/posts-table/main.js';

	/**
	 * Class constructor.
	 *
	 * @since 4.2.0
	 */
	public function __construct() {
		if ( wp_doing_ajax() ) {
			add_action( 'init', [ $this, 'addPostColumnsAjax' ], 1 );
		}

		if ( ! is_admin() || wp_doing_cron() ) {
			return;
		}

		add_action( 'current_screen', [ $this, 'registerColumnHooks' ], 1 );
	}

	/**
	 * Adds the columns to the page/post types.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function registerColumnHooks() {
		$screen = aioseo()->helpers->getCurrentScreen();
		if ( empty( $screen->base ) || empty( $screen->post_type ) ) {
			return;
		}

		if ( ! $this->shouldRegisterColumn( $screen->base, $screen->post_type ) ) {
			return;
		}

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueScripts' ] );

		if ( 'product' === $screen->post_type ) {
			add_filter( 'manage_edit-product_columns', [ $this, 'addColumn' ] );
			add_action( 'manage_posts_custom_column', [ $this, 'renderColumn' ], 10, 2 );

			return;
		}

		if ( 'attachment' === $screen->post_type ) {
			$enabled = apply_filters( 'aioseo_image_seo_media_columns', true );
			if ( ! $enabled ) {
				return;
			}

			add_filter( 'manage_media_columns', [ $this, 'addColumn' ] );
			add_action( 'manage_media_custom_column', [ $this, 'renderColumn' ], 10, 2 );

			return;
		}

		add_filter( "manage_edit-{$screen->post_type}_columns", [ $this, 'addColumn' ] );
		add_action( "manage_{$screen->post_type}_posts_custom_column", [ $this, 'renderColumn' ], 10, 2 );
	}

	/**
	 * Registers our post columns after a post has been quick-edited.
	 *
	 * @since 4.2.3
	 *
	 * @return void
	 */
	public function addPostColumnsAjax() {
		if (
			! isset( $_POST['_inline_edit'], $_POST['post_ID'], $_POST['aioseo-has-details-column'] ) ||
			! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_inline_edit'] ) ), 'inlineeditnonce' )
		) {
			return;
		}

		$postId = (int) $_POST['post_ID'];
		if ( ! $postId ) {
			return;
		}

		$post     = get_post( $postId );
		$postType = $post->post_type;

		add_filter( "manage_edit-{$postType}_columns", [ $this, 'addColumn' ] );
		add_action( "manage_{$postType}_posts_custom_column", [ $this, 'renderColumn' ], 10, 2 );
	}

	/**
	 * Enqueues the JS/CSS for the page/posts table page.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function enqueueScripts() {
		$data          = aioseo()->helpers->getVueData();
		$data['posts'] = [];
		$data['terms'] = [];

		aioseo()->core->assets->load( $this->scriptSlug, [], $data );
	}

	/**
	 * Adds the AIOSEO Details column to the page/post tables in the admin.
	 *
	 * @since 4.0.0
	 *
	 * @param  array $columns The columns we are adding ours onto.
	 * @return array          The modified columns.
	 */
	public function addColumn( $columns ) {
		$canManageSeo = apply_filters( 'aioseo_manage_seo', 'aioseo_manage_seo' );
		if (
			! current_user_can( $canManageSeo ) &&
			(
				! current_user_can( 'aioseo_page_general_settings' ) &&
				! current_user_can( 'aioseo_page_analysis' )
			)
		) {
			return $columns;
		}

		// Translators: 1 - The short plugin name ("AIOSEO").
		$columns['aioseo-details'] = sprintf( esc_html__( '%1$s Details', 'all-in-one-seo-pack' ), AIOSEO_PLUGIN_SHORT_NAME );

		return $columns;
	}

	/**
	 * Renders the column in the page/post table.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $columnName The column name.
	 * @param  int    $postId     The current rows, post id.
	 * @return void
	 */
	public function renderColumn( $columnName, $postId = 0 ) {
		if ( ! current_user_can( 'edit_post', $postId ) && ! current_user_can( 'aioseo_manage_seo' ) ) {
			return;
		}

		if ( 'aioseo-details' !== $columnName ) {
			return;
		}

		// Add this column/post to the localized array.
		global $wp_scripts; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		if (
			! is_object( $wp_scripts ) || // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			! method_exists( $wp_scripts, 'get_data' ) || // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			! method_exists( $wp_scripts, 'add_data' ) // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		) {
			return;
		}

		$data = null;
		if ( is_object( $wp_scripts ) ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			$data = $wp_scripts->get_data( 'aioseo/js/' . $this->scriptSlug, 'data' ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		}

		if ( ! is_array( $data ) ) {
			$data = json_decode( str_replace( 'var aioseo = ', '', substr( $data, 0, -1 ) ), true );
		}

		// We have to temporarily modify the query here since the query incorrectly identifies
		// the current page as a category page when posts are filtered by a specific category.
		// phpcs:disable Squiz.NamingConventions.ValidVariableName
		global $wp_query;
		$originalQuery         = clone $wp_query;
		$wp_query->is_category = false;
		$wp_query->is_tag      = false;
		$wp_query->is_tax      = false;
		// phpcs:enable Squiz.NamingConventions.ValidVariableName

		$posts    = ! empty( $data['posts'] ) ? $data['posts'] : [];
		$postData = $this->getPostData( $postId, $columnName );

		$addonsColumnData = array_filter( aioseo()->addons->doAddonFunction( 'admin', 'renderColumnData', [
			$columnName,
			$postId,
			$postData
		] ) );

		$wp_query = $originalQuery; // phpcs:ignore Squiz.NamingConventions.ValidVariableName

		foreach ( $addonsColumnData as $addonColumnData ) {
			$postData = array_merge( $postData, $addonColumnData );
		}

		$posts[]       = $postData;
		$data['posts'] = $posts;

		$wp_scripts->add_data( 'aioseo/js/' . $this->scriptSlug, 'data', '' ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		wp_localize_script( 'aioseo/js/' . $this->scriptSlug, 'aioseo', $data );

		require AIOSEO_DIR . '/app/Common/Views/admin/posts/columns.php';
	}

	/**
	 * Gets the post data for the column.
	 *
	 * @since 4.5.0
	 *
	 * @param  int    $postId     The Post ID.
	 * @param  string $columnName The column name.
	 * @return array              The post data.
	 */
	protected function getPostData( $postId, $columnName ) {
		$nonce    = wp_create_nonce( "aioseo_meta_{$columnName}_{$postId}" );
		$thePost  = Models\Post::getPost( $postId );
		$postType = get_post_type( $postId );
		$postData = [
			'id'                 => $postId,
			'columnName'         => $columnName,
			'nonce'              => $nonce,
			'title'              => $thePost->title,
			'defaultTitle'       => aioseo()->meta->title->getPostTypeTitle( $postType ),
			'showTitle'          => apply_filters( 'aioseo_details_column_post_show_title', true, $postId ),
			'description'        => $thePost->description,
			'defaultDescription' => aioseo()->meta->description->getPostTypeDescription( $postType ),
			'showDescription'    => apply_filters( 'aioseo_details_column_post_show_description', true, $postId ),
			'value'              => ! empty( $thePost->seo_score ) ? (int) $thePost->seo_score : 0,
			'showMedia'          => false,
			'isSpecialPage'      => aioseo()->helpers->isSpecialPage( $postId ),
			'postType'           => $postType,
			'isPostVisible'      => aioseo()->helpers->isPostPubliclyViewable( $postId )
		];

		return $postData;
	}

	/**
	 * Checks whether the AIOSEO Details column should be registered.
	 *
	 * @since 4.0.0
	 *
	 * @return bool Whether the column should be registered.
	 */
	public function shouldRegisterColumn( $screen, $postType ) {
		// Only allow users with the correct permissions to see the column.
		if ( ! current_user_can( 'aioseo_page_general_settings' ) ) {
			return false;
		}

		if ( 'type' === $postType ) {
			$postType = '_aioseo_type';
		}

		if ( 'edit' === $screen || 'upload' === $screen ) {
			if (
				aioseo()->options->advanced->postTypes->all &&
				in_array( $postType, aioseo()->helpers->getPublicPostTypes( true ), true )
			) {
				return true;
			}

			$postTypes = aioseo()->options->advanced->postTypes->included;
			if ( in_array( $postType, $postTypes, true ) ) {
				return true;
			}
		}

		return false;
	}
}WpCode.php000066600000001354151147516220006451 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

/**
 * Handles registering the AIOSEO username in the WPCode snippets library.
 *
 * @since 4.3.8
 */
class WpCode {
	/**
	 * Class constructor.
	 *
	 * @since 4.3.8
	 */
	public function __construct() {
		if ( ! is_admin() || wp_doing_ajax() || wp_doing_cron() ) {
			return;
		}

		add_action( 'wpcode_loaded', [ $this, 'registerUsername' ] );
	}

	/**
	 * Enqueues the script.
	 *
	 * @since 4.3.8
	 *
	 * @return void
	 */
	public function registerUsername() {
		if ( ! function_exists( 'wpcode_register_library_username' ) ) {
			return;
		}

		wpcode_register_library_username( 'aioseo', AIOSEO_PLUGIN_SHORT_NAME );
	}
}LimitModifiedDate.php000066600000016355151147516220010614 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

use AIOSEO\Plugin\Common\Models;

/**
 * Limit Modified Date class.
 *
 * @since 4.1.8
 */
class LimitModifiedDate {
	/**
	 * Class constructor.
	 *
	 * @since 4.1.8
	 *
	 * @return void
	 */
	public function __construct() {
		if ( apply_filters( 'aioseo_last_modified_date_disable', false ) ) {
			return;
		}

		// Reset modified date when the post is updated.
		add_filter( 'wp_insert_post_data', [ $this, 'resetModifiedDate' ], 99999, 2 );
		add_filter( 'wp_insert_attachment_data', [ $this, 'resetModifiedDate' ], 99999, 2 );
		add_action( 'woocommerce_before_product_object_save', [ $this, 'limitWooCommerceModifiedDate' ] );

		add_action( 'rest_api_init', [ $this, 'registerRestHooks' ] );

		if ( ! is_admin() ) {
			return;
		}

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueScripts' ], 20 );
		add_action( 'post_submitbox_misc_actions', [ $this, 'classicEditorField' ] );
	}

	/**
	 * Register the REST API hooks.
	 *
	 * @since 4.1.8
	 *
	 * @return void
	 */
	public function registerRestHooks() {
		// Prevent REST API from dropping limit modified date value before updating the post.
		foreach ( aioseo()->helpers->getPublicPostTypes( true ) as $postType ) {
			add_filter( "rest_pre_insert_$postType", [ $this, 'addLimitModifiedDateValue' ], 10, 2 );
		}
	}

	/**
	 * Enqueues the scripts for the Limited Modified Date functionality.
	 *
	 * @since 4.1.8
	 *
	 * @return void
	 */
	public function enqueueScripts() {
		if ( ! $this->isAllowed() || ! aioseo()->helpers->isScreenBase( 'post' ) ) {
			return;
		}

		// Only enqueue this script if the post-settings-metabox is already enqueued.
		if ( wp_script_is( 'aioseo/js/src/vue/standalone/post-settings/main.js', 'enqueued' ) ) {
			aioseo()->core->assets->load( 'src/vue/standalone/limit-modified-date/main.js' );
		}
	}

	/**
	 * Adds the Limit Modified Date field to the post object to prevent it from being dropped.
	 *
	 * @since 4.1.8
	 *
	 * @param  object           $preparedPost The post data.
	 * @param  \WP_REST_Request $restRequest  The request.
	 * @return object                         The modified post data.
	 */
	public function addLimitModifiedDateValue( $preparedPost, $restRequest = null ) {
		if ( 'PUT' !== $restRequest->get_method() ) {
			return $preparedPost;
		}

		$params = $restRequest->get_json_params();
		if ( empty( $params ) || ! isset( $params['aioseo_limit_modified_date'] ) ) {
			return $preparedPost;
		}

		$preparedPost->aioseo_limit_modified_date = $params['aioseo_limit_modified_date'];

		return $preparedPost;
	}

	/**
	 * Resets the modified date when a post is updated if the Limit Modified Date option is enabled.
	 *
	 * @since 4.1.8
	 *
	 * @param  array $data      An array of slashed, sanitized, and processed post data.
	 * @param  array $postArray An array of sanitized (and slashed) but otherwise unmodified post data.
	 * @return array            The modified sanitized post data.
	 */
	public function resetModifiedDate( $data, $postArray = [] ) {
		// If the ID isn't set, a new post is being inserted.
		if ( ! isset( $postArray['ID'] ) ) {
			return $data;
		}

		static $shouldReset = false;

		// Handle the REST API request from the Block Editor.
		if ( aioseo()->helpers->isRestApiRequest() ) {
			// If the value isn't set, then the value wasn't changed in the editor, and we can grab it from the post.
			if ( ! isset( $postArray['aioseo_limit_modified_date'] ) ) {
				$aioseoPost = Models\Post::getPost( $postArray['ID'] );
				if ( $aioseoPost->exists() && $aioseoPost->limit_modified_date ) {
					$shouldReset = true;
				}
			} else {
				if ( $postArray['aioseo_limit_modified_date'] ) {
					$shouldReset = true;
				}
			}
		}

		// Handle the POST request.
		if ( isset( $postArray['aioseo-post-settings'] ) ) {
			$aioseoData = json_decode( stripslashes( $postArray['aioseo-post-settings'] ) );
			if ( ! empty( $aioseoData->limit_modified_date ) ) {
				$shouldReset = true;
			}
		}

		// Handle post revision.
		if ( ! empty( $GLOBALS['action'] ) && in_array( $GLOBALS['action'], [ 'restore',  'inline-save' ], true ) ) {
			$aioseoPost = Models\Post::getPost( $postArray['ID'] );
			if ( $aioseoPost->exists() && $aioseoPost->limit_modified_date ) {
				$shouldReset = true;
			}
		}

		foreach ( aioseo()->standalone->pageBuilderIntegrations as $pageBuilder ) {
			if ( $pageBuilder->isBuiltWith( $postArray['ID'] ) && $pageBuilder->limitModifiedDate( $postArray['ID'] ) ) {
				$shouldReset = true;
				break;
			}
		}

		if ( $shouldReset && isset( $postArray['post_modified'], $postArray['post_modified_gmt'] ) ) {
			$originalPost = get_post( $postArray['ID'] );

			$data['post_modified']     = $originalPost->post_modified;
			$data['post_modified_gmt'] = $originalPost->post_modified_gmt;
		}

		return $data;
	}

	/**
	 * Limits the modified date for WooCommerce products.
	 *
	 * @since 4.8.1
	 *
	 * @param  \WC_Product $product The WooCommerce product.
	 * @return void
	 */
	public function limitWooCommerceModifiedDate( $product ) {
		if ( ! isset( $_POST['PostSettingsNonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['PostSettingsNonce'] ) ), 'aioseoPostSettingsNonce' ) ) {
			return;
		}

		if ( ! isset( $_POST['aioseo-post-settings'] ) ) {
			return;
		}

		$aioseoData = json_decode( sanitize_text_field( wp_unslash( ( $_POST['aioseo-post-settings'] ) ) ) );
		if ( empty( $aioseoData ) || empty( $aioseoData->limit_modified_date ) ) {
			return;
		}

		$product->set_date_modified( get_post_field( 'post_modified', $product->get_id() ) );
	}

	/**
	 * Add the checkbox in the Classic Editor.
	 *
	 * @since 4.1.8
	 *
	 * @param  \WP_Post $post The post object.
	 * @return void
	 */
	public function classicEditorField( $post ) {
		if ( ! $this->isAllowed( $post->post_type ) ) {
			return;
		}

		?>
		<div class="misc-pub-section">
			<div id="aioseo-limit-modified-date"></div>
		</div>
		<?php
	}

	/**
	 * Check if the Limit Modified Date functionality is allowed to run.
	 *
	 * @since 4.1.8
	 *
	 * @param  string $postType The current post type.
	 * @return bool             Whether the functionality is allowed.
	 */
	private function isAllowed( $postType = '' ) {
		if ( empty( $postType ) ) {
			$postType = get_post_type();
		}

		if ( class_exists( 'Limit_Modified_Date', false ) ) {
			return false;
		}

		if ( ! $this->isAllowedPostType( $postType ) ) {
			return false;
		}

		if ( ! aioseo()->access->hasCapability( 'aioseo_page_general_settings' ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Check if the given post type is allowed to limit the modified date.
	 *
	 * @since 4.1.8
	 *
	 * @param  string $postType The post type name.
	 * @return bool             Whether the post type is allowed.
	 */
	private function isAllowedPostType( $postType ) {
		$dynamicOptions = aioseo()->dynamicOptions->noConflict();
		$postTypes      = aioseo()->helpers->getPublicPostTypes( true );
		$postTypes      = apply_filters( 'aioseo_limit_modified_date_post_types', $postTypes );

		if ( ! in_array( $postType, $postTypes, true ) ) {
			return false;
		}

		if ( ! $dynamicOptions->searchAppearance->postTypes->has( $postType ) || ! $dynamicOptions->searchAppearance->postTypes->$postType->advanced->showMetaBox ) {
			return false;
		}

		return true;
	}
}SetupWizard.php000066600000015073151147516220007554 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

/**
 * Class that holds our setup wizard.
 *
 * @since 4.0.0
 */
class SetupWizard {
	/**
	 * Class constructor.
	 *
	 * @since 4.0.0
	 */
	public function __construct() {
		if ( ! is_admin() || wp_doing_cron() || wp_doing_ajax() ) {
			return;
		}

		add_action( 'admin_menu', [ $this, 'addDashboardPage' ] );
		add_action( 'admin_head', [ $this, 'hideDashboardPageFromMenu' ] );
		add_action( 'admin_init', [ $this, 'maybeLoadOnboardingWizard' ] );
		add_action( 'admin_init', [ $this, 'redirect' ], 9999 );
	}

	/**
	 * Onboarding Wizard redirect.
	 *
	 * This function checks if a new install or update has just occurred. If so,
	 * then we redirect the user to the appropriate page.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function redirect() {
		// Check if we should consider redirection.
		if ( ! aioseo()->core->cache->get( 'activation_redirect' ) ) {
			return;
		}

		// If we are redirecting, clear the transient so it only happens once.
		aioseo()->core->cache->delete( 'activation_redirect' );

		// Check option to disable welcome redirect.
		if ( get_option( 'aioseo_activation_redirect', false ) ) {
			return;
		}

		// Only do this for single site installs.
		if ( isset( $_GET['activate-multi'] ) || is_network_admin() ) { // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
			return;
		}

		wp_safe_redirect( admin_url( 'index.php?page=aioseo-setup-wizard' ) );
		exit;
	}

	/**
	 * Adds a dashboard page for our setup wizard.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function addDashboardPage() {
		add_dashboard_page( '', '', aioseo()->admin->getPageRequiredCapability( 'aioseo-setup-wizard' ), 'aioseo-setup-wizard', '' );
	}

	/**
	 * Hide the dashboard page from the menu.
	 *
	 * @since 4.1.5
	 *
	 * @return void
	 */
	public function hideDashboardPageFromMenu() {
		remove_submenu_page( 'index.php', 'aioseo-setup-wizard' );
	}

	/**
	 * Checks to see if we should load the setup wizard.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function maybeLoadOnboardingWizard() {
		// Don't load the interface if doing an ajax call.
		if ( wp_doing_ajax() || wp_doing_cron() ) {
			return;
		}

		// Check for wizard-specific parameter
		// Allow plugins to disable the setup wizard
		// Check if current user is allowed to save settings.
		if (
			// phpcs:disable HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
			! isset( $_GET['page'] ) ||
			'aioseo-setup-wizard' !== sanitize_text_field( wp_unslash( $_GET['page'] ) ) ||
			// phpcs:enable
			! current_user_can( aioseo()->admin->getPageRequiredCapability( 'aioseo-setup-wizard' ) )
		) {
			return;
		}

		set_current_screen();

		// Remove an action in the Gutenberg plugin ( not core Gutenberg ) which throws an error.
		remove_action( 'admin_print_styles', 'gutenberg_block_editor_admin_print_styles' );

		// If we are redirecting, clear the transient so it only happens once.
		aioseo()->core->cache->delete( 'activation_redirect' );

		$this->loadOnboardingWizard();
	}

	/**
	 * Load the Onboarding Wizard template.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	private function loadOnboardingWizard() {
		$this->enqueueScripts();
		$this->setupWizardHeader();
		$this->setupWizardContent();
		$this->setupWizardFooter();
		exit;
	}

	/**
	 * Enqueue's scripts for the setup wizard.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function enqueueScripts() {
		// We don't want any plugin adding notices to our screens. Let's clear them out here.
		remove_all_actions( 'admin_notices' );
		remove_all_actions( 'network_admin_notices' );
		remove_all_actions( 'all_admin_notices' );

		aioseo()->core->assets->load( 'src/vue/standalone/setup-wizard/main.js', [], aioseo()->helpers->getVueData( 'setup-wizard' ) );

		aioseo()->main->enqueueTranslations();

		wp_enqueue_style( 'common' );
		wp_enqueue_media();
	}

	/**
	 * Outputs the simplified header used for the Onboarding Wizard.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function setupWizardHeader() {
		?>
		<!DOCTYPE html>
		<html <?php language_attributes(); ?>>
		<head>
			<meta name="viewport" content="width=device-width"/>
			<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
			<title>
			<?php
				// Translators: 1 - The plugin name ("All in One SEO").
				echo sprintf( esc_html__( '%1$s &rsaquo; Onboarding Wizard', 'all-in-one-seo-pack' ), esc_html( AIOSEO_PLUGIN_SHORT_NAME ) );
			?>
			</title>
		</head>
		<body class="aioseo-setup-wizard">
		<?php
	}

	/**
	 * Outputs the content of the current step.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function setupWizardContent() {
		echo '<div id="aioseo-app">';
		aioseo()->templates->getTemplate( 'admin/settings-page.php' );
		echo '</div>';
	}

	/**
	 * Outputs the simplified footer used for the Onboarding Wizard.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function setupWizardFooter() {
		?>
		<?php
		wp_print_scripts( 'aioseo-vendors' );
		wp_print_scripts( 'aioseo-common' );
		wp_print_scripts( 'aioseo-setup-wizard-script' );
		do_action( 'admin_footer', '' );
		do_action( 'admin_print_footer_scripts' );
		// do_action( 'customize_controls_print_footer_scripts' );
		?>
		</body>
		</html>
		<?php
	}

	/**
	 * Check whether or not the Setup Wizard is completed.
	 *
	 * @since 4.2.0
	 *
	 * @return boolean Whether or not the Setup Wizard is completed.
	 */
	public function isCompleted() {
		$wizard = (string) aioseo()->internalOptions->internal->wizard;
		$wizard = json_decode( $wizard );
		if ( ! $wizard ) {
			return false;
		}

		$totalStageCount   = count( $wizard->stages );
		$currentStageCount = array_search( $wizard->currentStage, $wizard->stages, true );

		// If not found, let's assume it's completed.
		if ( false === $currentStageCount ) {
			return true;
		}

		return $currentStageCount + 1 === $totalStageCount;
	}

	/**
	 * Get the next stage of the wizard.
	 *
	 * @since 4.6.2
	 *
	 * @return string The next stage or empty.
	 */
	public function getNextStage() {
		$wizard    = (string) aioseo()->internalOptions->internal->wizard;
		$wizard    = json_decode( $wizard );
		if ( ! $wizard ) {
			return '';
		}

		// Default to success.
		$nextStage = 'success';

		// Get the next stage of the wizard.
		$currentStageIndex = array_search( $wizard->currentStage, $wizard->stages, true );
		if ( ! empty( $wizard->stages[ $currentStageIndex + 1 ] ) ) {
			$nextStage = $wizard->stages[ $currentStageIndex + 1 ];
		}

		return $nextStage;
	}
}UserProfileTab.php000066600000012537151147516220010163 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

use AIOSEO\Plugin\Pro\Standalone as ProStandalone;

/**
 * Registers the standalone components.
 *
 * @since 4.2.2
 */
class UserProfileTab {
	/**
	 * Class constructor.
	 *
	 * @since 4.2.2
	 */
	public function __construct() {
		if ( ! is_admin() ) {
			return;
		}

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueScript' ] );
		add_action( 'profile_update', [ $this, 'updateUserSocialProfiles' ] );
	}

	/**
	 * Enqueues the script.
	 *
	 * @since 4.2.2
	 *
	 * @return void
	 */
	public function enqueueScript() {
		if ( apply_filters( 'aioseo_user_profile_tab_disable', false ) ) {
			return;
		}

		$screen = aioseo()->helpers->getCurrentScreen();
		if ( empty( $screen->id ) ) {
			return;
		}

		if ( ! in_array( $screen->id, [ 'user-edit', 'profile' ], true ) ) {
			if ( 'follow-up_page_followup-emails-reports' === $screen->id ) {
				aioseo()->core->assets->load( 'src/vue/standalone/user-profile-tab/follow-up-emails-nav-bar.js' );
			}

			return;
		}

		global $user_id; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		if ( ! intval( $user_id ) ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
			return;
		}

		aioseo()->core->assets->load( 'src/vue/standalone/user-profile-tab/main.js', [], $this->getVueData() );
		// Load script again so we can add extra data to localize the strings.
		aioseo()->core->assets->load( 'src/vue/standalone/user-profile-tab/main.js', [], [
			'translations' => aioseo()->helpers->getJedLocaleData( 'aioseo-eeat' )
		], 'eeat' );
	}

	/**
	 * Returns the data Vue requires.
	 *
	 * @since 4.2.2
	 *
	 * @return array
	 */
	public function getVueData() {
		global $user_id; // phpcs:ignore Squiz.NamingConventions.ValidVariableName

		$socialProfiles = $this->getSocialProfiles();
		foreach ( $socialProfiles as $platformKey => $v ) {
			$metaName                        = 'aioseo_' . aioseo()->helpers->toSnakeCase( $platformKey );
			$socialProfiles[ $platformKey ] = get_user_meta( $user_id, $metaName, true ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		}

		$sameUsername = get_user_meta( $user_id, 'aioseo_profiles_same_username', true ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName
		if ( empty( $sameUsername ) ) {
			$sameUsername = [
				'enable'   => false,
				'username' => '',
				'included' => [ 'facebookPageUrl', 'twitterUrl', 'tiktokUrl', 'pinterestUrl', 'instagramUrl', 'youtubeUrl', 'linkedinUrl' ] // Same as in Options.php.
			];
		}

		$additionalurls = get_user_meta( $user_id, 'aioseo_profiles_additional_urls', true ); // phpcs:ignore Squiz.NamingConventions.ValidVariableName

		$extraVueData = [
			'userProfile' => [
				'userData'                          => get_userdata( $user_id )->data, // phpcs:ignore Squiz.NamingConventions.ValidVariableName
				'profiles'                          => [
					'sameUsername'   => $sameUsername,
					'urls'           => $socialProfiles,
					'additionalUrls' => $additionalurls
				],
				'isWooCommerceFollowupEmailsActive' => aioseo()->helpers->isWooCommerceFollowupEmailsActive()
			]
		];

		$vueData = aioseo()->helpers->getVueData();
		$vueData = array_merge( $vueData, $extraVueData );

		return $vueData;
	}

	/**
	 * Updates the user social profile URLs when a user's profile is updated.
	 *
	 * @since 4.2.2
	 *
	 * @param  int  $userId The user ID.
	 * @return void
	 */
	public function updateUserSocialProfiles( $userId ) {
		if ( empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $userId ) ) {
			return;
		}

		if ( empty( $_POST['aioseo-user-social-profiles'] ) ) {
			return;
		}

		$data = json_decode( sanitize_text_field( wp_unslash( $_POST['aioseo-user-social-profiles'] ) ), true );
		if ( empty( $data ) ) {
			return;
		}

		$sanitizedIncluded = [];
		foreach ( $data['sameUsername']['included'] as $platformKey ) {
			$sanitizedIncluded[] = sanitize_text_field( $platformKey );
		}

		$sanitizedSameUsernameData = [
			'enable'   => (bool) $data['sameUsername']['enable'],
			'username' => sanitize_text_field( $data['sameUsername']['username'] ),
			'included' => array_filter( $sanitizedIncluded )
		];

		update_user_meta( $userId, 'aioseo_profiles_same_username', $sanitizedSameUsernameData );

		foreach ( $data['urls'] as $platformKey => $value ) {
			$value    = sanitize_text_field( $value );
			$metaName = 'aioseo_' . aioseo()->helpers->toSnakeCase( $platformKey );
			update_user_meta( $userId, $metaName, $value );
		}

		$additionalUrls          = sanitize_text_field( $data['additionalUrls'] );
		$sanitizedAdditionalUrls = preg_replace( '/\h/', "\n", (string) $additionalUrls );
		update_user_meta( $userId, 'aioseo_profiles_additional_urls', $sanitizedAdditionalUrls );
	}

	/**
	 * Returns a list of supported social profiles.
	 *
	 * @since 4.2.2
	 *
	 * @return array
	 */
	public function getSocialProfiles() {
		return [
			'facebookPageUrl' => '',
			'twitterUrl'      => '',
			'instagramUrl'    => '',
			'tiktokUrl'       => '',
			'pinterestUrl'    => '',
			'youtubeUrl'      => '',
			'linkedinUrl'     => '',
			'tumblrUrl'       => '',
			'yelpPageUrl'     => '',
			'soundCloudUrl'   => '',
			'wikipediaUrl'    => '',
			'myspaceUrl'      => '',
			'wordPressUrl'    => '',
			'blueskyUrl'      => '',
			'threadsUrl'      => ''
		];
	}
}BbPress/Component.php000066600000005100151147516220010563 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\BbPress;

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

/**
 * bbPress Component class.
 *
 * @since 4.8.1
 */
class Component {
	/**
	 * The current component template type.
	 *
	 * @since 4.8.1
	 *
	 * @var string|null
	 */
	public $templateType = null;

	/**
	 * The topic single page data.
	 *
	 * @since 4.8.1
	 *
	 * @var array
	 */
	public $topic = [];

	/**
	 * Class constructor.
	 *
	 * @since 4.8.1
	 */
	public function __construct() {
		if ( is_admin() ) {
			return;
		}

		$this->setTemplateType();
		$this->setTopic();
	}

	/**
	 * Sets the template type.
	 *
	 * @since 4.8.1
	 *
	 * @return void
	 */
	private function setTemplateType() {
		if ( function_exists( 'bbp_is_single_topic' ) && bbp_is_single_topic() ) {
			$this->templateType = 'bbp-topic_single';
		}
	}

	/**
	 * Sets the topic data.
	 *
	 * @since 4.8.1
	 *
	 * @return void
	 */
	private function setTopic() {
		if ( 'bbp-topic_single' !== $this->templateType ) {
			return;
		}

		if (
			! function_exists( 'bbpress' ) ||
			! function_exists( 'bbp_has_replies' ) ||
			! bbp_has_replies()
		) {
			return;
		}

		$replyQuery = bbpress()->reply_query ?? null;
		$replies    = $replyQuery->posts ?? [];
		$mainTopic  = is_array( $replies ) && ! empty( $replies ) ? array_shift( $replies ) : null;

		if ( $mainTopic instanceof \WP_Post ) {
			$this->topic = [
				'title'   => $mainTopic->post_title,
				'content' => $mainTopic->post_content,
				'date'    => $mainTopic->post_date,
				'author'  => get_the_author_meta( 'display_name', $mainTopic->post_author ),
			];

			$comments = [];
			if ( ! empty( $replies ) ) {
				foreach ( $replies as $reply ) {
					if ( ! $reply instanceof \WP_Post ) {
						continue;
					}

					$comments[ $reply->ID ] = [
						'content'       => $reply->post_content,
						'date_recorded' => $reply->post_date,
						'user_fullname' => get_the_author_meta( 'display_name', $reply->post_author ),
					];

					if ( ! empty( $reply->reply_to ) ) {
						$comments[ $reply->reply_to ]['children'][] = $comments[ $reply->ID ];

						unset( $comments[ $reply->ID ] );
					}
				}

				$this->topic['comment'] = array_values( $comments );
			}

			return;
		}

		$this->resetComponent();
	}

	/**
	 * Resets some of the component properties.
	 *
	 * @since 4.8.1
	 *
	 * @return void
	 */
	private function resetComponent() {
		$this->templateType = null;
	}

	/**
	 * Determines the schema type for the current component.
	 *
	 * @since 4.8.1
	 *
	 * @return void
	 */
	public function determineSchemaGraphsAndContext() {
	}
}BbPress/BbPress.php000066600000002273151147516220010171 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\BbPress;

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

/**
 * Handles the bbPress integration with AIOSEO.
 *
 * @since 4.8.1
 */
class BbPress {
	/**
	 * Instance of the Component class.
	 *
	 * @since 4.8.1
	 *
	 * @var Component
	 */
	public $component;

	/**
	 * Class constructor.
	 *
	 * @since 4.8.1
	 */
	public function __construct() {
		if (
			aioseo()->helpers->isAjaxCronRestRequest() ||
			! aioseo()->helpers->isPluginActive( 'bbpress' )
		) {
			return;
		}

		// Hook into `plugins_loaded` to ensure bbPress has loaded some necessary functions.
		add_action( 'plugins_loaded', [ $this, 'maybeLoad' ], 20 );
	}

	/**
	 * Hooked into `plugins_loaded` action hook.
	 *
	 * @since 4.8.1
	 *
	 * @return void
	 */
	public function maybeLoad() {
		// If the bbPress version is below 2 we bail.
		if ( ! function_exists( 'bbp_get_version' ) || version_compare( bbp_get_version(), '2', '<' ) ) {
			return;
		}

		add_action( 'wp', [ $this, 'setComponent' ] );
	}

	/**
	 * Hooked into `wp` action hook.
	 *
	 * @since 4.8.1
	 *
	 * @return void
	 */
	public function setComponent() {
		$this->component = new Component();
	}
}FlyoutMenu.php000066600000003405151147516220007376 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

/**
 * Handles the flyout menu.
 *
 * @since 4.2.0
 */
class FlyoutMenu {
	/**
	 * Class constructor.
	 *
	 * @since 4.2.0
	 */
	public function __construct() {
		if (
			! is_admin() ||
			wp_doing_ajax() ||
			wp_doing_cron() ||
			! $this->isEnabled()
		) {
			return;
		}

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAssets' ], 11 );
		add_filter( 'admin_body_class', [ $this, 'addBodyClass' ] );
	}

	/**
	 * Enqueues the required assets.
	 *
	 * @since 4.2.0
	 *
	 * @return void
	 */
	public function enqueueAssets() {
		if ( ! $this->shouldEnqueue() ) {
			return;
		}

		aioseo()->core->assets->load( 'src/vue/standalone/flyout-menu/main.js' );
	}

	/**
	 * Filters the CSS classes for the body tag in the admin.
	 *
	 * @since 4.2.0
	 *
	 * @param  string $classes Space-separated list of CSS classes.
	 * @return string          Space-separated list of CSS classes.
	 */
	public function addBodyClass( $classes ) {
		if ( $this->shouldEnqueue() ) {
			// This adds a bottom margin to our menu so that we push the footer down and prevent the flyout menu from overlapping the "Save Changes" button.
			$classes .= ' aioseo-flyout-menu-enabled ';
		}

		return $classes;
	}

	/**
	 * Checks whether the flyout menu script should be enqueued.
	 *
	 * @since 4.2.0
	 *
	 * @return bool Whether the flyout menu script should be enqueued.
	 */
	private function shouldEnqueue() {
		return aioseo()->admin->isAioseoScreen();
	}

	/**
	 * Checks whether the flyout menu is enabled.
	 *
	 * @since 4.2.0
	 *
	 * @return bool Whether the flyout menu is enabled.
	 */
	public function isEnabled() {
		return apply_filters( 'aioseo_flyout_menu_enable', true );
	}
}Blocks/FaqPage.php000066600000001075151147516220010011 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\Blocks;

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

/**
 * FaqPage Block.
 *
 * @since 4.2.3
 */
class FaqPage extends Blocks {
	/**
	 * Register the block.
	 *
	 * @since 4.2.3
	 *
	 * @return void
	 */
	public function register() {
		aioseo()->blocks->registerBlock( 'aioseo/faq',
			[
				'render_callback' => function( $attributes, $content ) {
					if ( isset( $attributes['hidden'] ) && true === $attributes['hidden'] ) {
						return '';
					}

					return $content;
				},
			]
		);
	}
}Blocks/Blocks.php000066600000001166151147516220007723 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\Blocks;

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

/**
 * Loads core classes.
 *
 * @since 4.2.3
 */
abstract class Blocks {
	/**
	 * Class constructor.
	 *
	 * @since 4.2.3
	 */
	public function __construct() {
		add_action( 'init', [ $this, 'init' ] );
	}

	/**
	 * Initializes our blocks.
	 *
	 * @since 4.2.3
	 *
	 * @return void
	 */
	public function init() {
		$this->register();
	}

	/**
	 * Registers the block. This is a wrapper to be extended in the child class.
	 *
	 * @since 4.2.3
	 *
	 * @return void
	 */
	public function register() {}
}Blocks/TableOfContents.php000066600000000616151147516220011537 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\Blocks;

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

/**
 * Table of Contents Block.
 *
 * @since 4.2.3
 */
class TableOfContents extends Blocks {
	/**
	 * Register the block.
	 *
	 * @since 4.2.3
	 *
	 * @return void
	 */
	public function register() {
		aioseo()->blocks->registerBlock( 'aioseo/table-of-contents' );
	}
}PublishPanel.php000066600000001365151147516220007660 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone;

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

use AIOSEO\Plugin\Common\Models;

/**
 * Handles the Publish Panel in the Block Editor.
 *
 * @since 4.2.0
 */
class PublishPanel {
	/**
	 * Class constructor.
	 *
	 * @since 4.2.0
	 */
	public function __construct() {
		if ( ! is_admin() || wp_doing_ajax() || wp_doing_cron() ) {
			return;
		}

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueScript' ] );
	}

	/**
	 * Enqueues the script.
	 *
	 * @since 4.2.0
	 *
	 * @return void
	 */
	public function enqueueScript() {
		if ( ! aioseo()->helpers->isScreenBase( 'post' ) ) {
			return;
		}

		aioseo()->core->assets->load( 'src/vue/standalone/publish-panel/main.js' );
	}
}PageBuilders/Avada.php000066600000005617151147516220010660 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\PageBuilders;

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

/**
 * Integrate our SEO Panel with Avada Page Builder.
 *
 * @since 4.5.2
 */
class Avada extends Base {
	/**
	 * The plugin files.
	 *
	 * @since 4.5.2
	 *
	 * @var array
	 */
	public $plugins = [
		'fusion-builder/fusion-builder.php'
	];

	/**
	 * The integration slug.
	 *
	 * @since 4.5.2
	 *
	 * @var string
	 */
	public $integrationSlug = 'avada';

	/**
	 * Init the integration.
	 *
	 * @since 4.5.2
	 *
	 * @return void
	 */
	public function init() {
		add_action( 'fusion_enqueue_live_scripts', [ $this, 'enqueue' ] );
		add_action( 'fusion_builder_admin_scripts_hook', [ $this, 'enqueue' ] );
		add_action( 'wp_footer', [ $this, 'addSidebarWrapper' ] );
	}

	/**
	 * Check if we are in the front-end builder.
	 *
	 * @since 4.5.2
	 *
	 * @return boolean Whether or not we are in the front-end builder.
	 */
	public function isBuilder() {
		return function_exists( 'fusion_is_builder_frame' ) && fusion_is_builder_frame();
	}

	/**
	 * Check if we are in the front-end preview.
	 *
	 * @since 4.5.2
	 *
	 * @return boolean Whether or not we are in the front-end preview.
	 */
	public function isPreview() {
		return function_exists( 'fusion_is_preview_frame' ) && fusion_is_preview_frame();
	}

	/**
	 * Adds the sidebar wrapper in footer when is in page builder.
	 *
	 * @since 4.5.2
	 *
	 * @return void
	 */
	public function addSidebarWrapper() {
		if ( ! $this->isBuilder() ) {
			return;
		}

		echo '<div id="fusion-builder-aioseo-sidebar"></div>';
	}

	/**
	 * Enqueue the scripts and styles.
	 *
	 * @since 4.5.2
	 *
	 * @return void
	 */
	public function enqueue() {
		if ( ! aioseo()->postSettings->canAddPostSettingsMetabox( get_post_type( $this->getPostId() ) ) ) {
			return;
		}

		parent::enqueue();
	}

	/**
	 * Returns whether or not the given Post ID was built with WPBakery.
	 *
	 * @since 4.5.2
	 *
	 * @param  int $postId The Post ID.
	 * @return boolean     Whether or not the Post was built with WPBakery.
	 */
	public function isBuiltWith( $postId ) {
		return 'active' === get_post_meta( $postId, 'fusion_builder_status', true );
	}

	/**
	 * Returns whether should or not limit the modified date.
	 *
	 * @since 4.5.2
	 *
	 * @param  int     $postId The Post ID.
	 * @return boolean         Whether or not sholud limit the modified date.
	 */
	public function limitModifiedDate( $postId ) {
		// This method is supposed to be used in the `wp_ajax_fusion_app_save_post_content` action.
		if ( ! isset( $_POST['fusion_load_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['fusion_load_nonce'] ) ), 'fusion_load_nonce' ) ) {
			return false;
		}

		$editorPostId = ! empty( $_REQUEST['post_id'] ) ? intval( $_REQUEST['post_id'] ) : 0;
		if ( $editorPostId !== $postId ) {
			return false;
		}

		return ! empty( $_REQUEST['query']['aioseo_limit_modified_date'] );
	}
}PageBuilders/SeedProd.php000066600000007154151147516220011347 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\PageBuilders;

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

/**
 * Integrate our SEO Panel with SeedProd Page Builder.
 *
 * @since 4.1.7
 */
class SeedProd extends Base {
	/**
	 * The plugin files.
	 *
	 * @since 4.1.7
	 *
	 * @var array
	 */
	public $plugins = [
		'coming-soon/coming-soon.php',
		'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php',
	];

	/**
	 * The integration slug.
	 *
	 * @since 4.1.7
	 *
	 * @var string
	 */
	public $integrationSlug = 'seedprod';

	/**
	 * Init the integration.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function init() {
		$postType = get_post_type( $this->getPostId() );

		if ( ! aioseo()->postSettings->canAddPostSettingsMetabox( $postType ) ) {
			return;
		}

		// SeedProd de-enqueues and de-register scripts/styles on priority PHP_INT_MAX.
		// Thus, we need to enqueue our scripts at the same priority for more compatibility.
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ], PHP_INT_MAX );
		add_filter( 'style_loader_tag', [ $this, 'replaceStyleTag' ], 10, 2 );
	}

	/**
	 * Enqueue the scripts and styles.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function enqueue() {
		if ( ! $this->isBuilderScreen() ) {
			return;
		}

		parent::enqueue();
	}

	/**
	 * Check whether or not is builder screen.
	 *
	 * @since 4.1.7
	 *
	 * @return boolean Whether or not is builder screen.
	 */
	public function isBuilderScreen() {
		$currentScreen = aioseo()->helpers->getCurrentScreen();

		return $currentScreen && preg_match( '/seedprod.*?_builder$/i', (string) $currentScreen->base );
	}

	/**
	 * Replace original tag to prevent being removed by SeedProd.
	 *
	 * @param  string $tag    The <link> tag for the enqueued style.
	 * @param  string $handle The style's registered handle.
	 * @return string         The tag.
	 */
	public function replaceStyleTag( $tag, $handle = '' ) {
		if ( ! $this->isBuilderScreen() ) {
			return $tag;
		}

		$aioseoCommonHandle = 'aioseo-' . $this->integrationSlug . '-common';

		if ( $aioseoCommonHandle === $handle ) {
			// All the *common.css links are removed from SeedProd.
			// https://github.com/awesomemotive/seedprod-plugins/blob/32854442979bfa068aadf9b8a8a929e5f9f353e5/seedprod-pro/resources/views/builder.php#L406
			$tag = str_ireplace( 'href=', 'data-href=', $tag );
		}

		return $tag;
	}

	/**
	 * Returns whether or not the given Post ID was built with SeedProd.
	 *
	 * @since 4.1.7
	 *
	 * @param  int $postId The Post ID.
	 * @return boolean     Whether or not the Post was built with SeedProd.
	 */
	public function isBuiltWith( $postId ) {
		$isSeedProd = get_post_meta( $postId, '_seedprod_page', true );
		if ( ! empty( $isSeedProd ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Checks whether or not we should prevent the date from being modified.
	 *
	 * @since 4.5.2
	 *
	 * @param  int  $postId The Post ID.
	 * @return bool         Whether or not we should prevent the date from being modified.
	 */
	public function limitModifiedDate( $postId ) {
		// This method is supposed to be used in the `wp_ajax_seedprod_pro_save_lpage` action.
		if ( wp_doing_ajax() && ! check_ajax_referer( 'seedprod_nonce', false, false ) ) {
			return false;
		}

		$landingPageId = ! empty( $_REQUEST['lpage_id'] ) ? (int) $_REQUEST['lpage_id'] : false;
		if ( $landingPageId !== $postId ) {
			return false;
		}

		$settings = ! empty( $_REQUEST['settings'] ) ? json_decode( sanitize_text_field( wp_unslash( $_REQUEST['settings'] ) ) ) : false;
		if ( empty( $settings ) || empty( $settings->aioseo_limit_modified_date ) ) {
			return false;
		}

		return true;
	}
}PageBuilders/ThriveArchitect.php000066600000031364151147516220012732 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\PageBuilders;

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

/**
 * Integrate our SEO Panel with Thrive Architect Page Builder.
 *
 * @since 4.6.6
 */
class ThriveArchitect extends Base {
	/**
	 * The plugin files.
	 *
	 * @since 4.6.6
	 *
	 * @var array
	 */
	public $plugins = [
		'thrive-visual-editor/thrive-visual-editor.php'
	];

	/**
	 * The integration slug.
	 *
	 * @since 4.6.6
	 *
	 * @var string
	 */
	public $integrationSlug = 'thrive-architect';

	/**
	 * Init the integration.
	 *
	 * @since 4.6.6
	 *
	 * @return void
	 */
	public function init() {
		add_filter( 'tcb_allowed_ajax_options', [ $this, 'makeSettingsAllowed' ] );

		if ( ! aioseo()->postSettings->canAddPostSettingsMetabox( get_post_type( $this->getPostId() ) ) ) {
			return;
		}

		add_action( 'tcb_main_frame_enqueue', [ $this, 'enqueue' ] );
		add_filter( 'tve_main_js_dependencies', [ $this, 'mainJsDependencies' ] );
		add_action( 'tcb_right_sidebar_content_settings', [ $this, 'addSettingsTab' ] );
		add_action( 'tcb_sidebar_extra_links', [ $this, 'addSidebarButton' ] );
		add_filter( 'tcb_main_frame_localize', [ $this, 'localizeData' ] );
	}

	/**
	 * Overrides the parent enqueue to add WordPress styles that we need.
	 *
	 * @since 4.6.6
	 *
	 * @return void
	 */
	public function enqueue() {
		wp_enqueue_style( 'common' );
		wp_enqueue_style( 'buttons' );
		wp_enqueue_style( 'forms' );
		wp_enqueue_style( 'list-tables' );
		wp_enqueue_style( 'wp-components' );

		print_admin_styles();

		parent::enqueue();
	}

	/**
	 * Add our javascript to the plugin dependencies.
	 *
	 * @since 4.6.6
	 *
	 * @param  array $dependencies The dependencies.
	 * @return array               The dependencies.
	 */
	public function mainJsDependencies( $dependencies ) {
		$dependencies[] = aioseo()->core->assets->jsHandle( "src/vue/standalone/page-builders/{$this->integrationSlug}/main.js" );

		return $dependencies;
	}

	/**
	 * Add the extra link to the sidebar.
	 *
	 * @since 4.6.6
	 *
	 * @return void
	 */
	public function addSidebarButton() {
		$tooltip = sprintf(
			// Translators: 1 - The plugin short name ("AIOSEO").
			esc_html__( '%1$s Settings', 'all-in-one-seo-pack' ),
			AIOSEO_PLUGIN_SHORT_NAME
		);

		// phpcs:disable Generic.Files.LineLength.MaxExceeded
		?>
		<a href="javascript:void(0)" class="mouseenter mouseleave sidebar-item tcb-sidebar-icon-aioseo" data-position="left" data-toggle="settings" data-tooltip="<?php echo esc_attr( $tooltip ); ?>">
			<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
				<path d="M10.0011 19C14.9722 19 19.0021 14.9706 19.0021 10C19.0021 5.02944 14.9722 1 10.0011 1C5.02991 1 1 5.02944 1 10C1 14.9706 5.02991 19 10.0011 19Z" stroke="currentColor"/>
				<path d="M9.99664 13.3228C9.99896 13.2104 9.47813 13.1752 9.37307 13.141C8.56228 12.8777 8.04027 12.3293 7.78204 11.5205C7.6493 11.1043 7.68851 10.6765 7.68163 10.2515C7.68162 10.2511 7.68134 10.2507 7.68093 10.2506V10.2506C7.68051 10.2504 7.68023 10.25 7.68023 10.2496C7.68069 9.99579 7.67884 9.74246 7.68115 9.48867C7.683 9.31099 7.74131 9.25453 7.92133 9.25222C8.07128 9.25037 8.22168 9.24482 8.37116 9.25407C8.48454 9.26101 8.86945 9.25407 8.86945 9.25407M9.99664 13.3228C9.98877 13.7037 10.0003 14.1192 10.0008 14.5M9.99664 13.3228C10.0036 13.7152 9.99664 14.1076 10.0008 14.5M9.99664 13.3228C9.99863 13.2265 10.5453 13.1946 10.6332 13.1715C11.6884 12.8929 12.42 11.955 12.4311 10.8639C12.4358 10.4137 12.433 9.96389 12.432 9.51366C12.432 9.30312 12.3788 9.25268 12.1641 9.25176C12.0197 9.25083 11.8749 9.24435 11.7314 9.25407C11.6213 9.26148 11.2793 9.25176 11.2793 9.25176M10.0008 14.5C10.0008 14.7878 9.72149 14.8117 9.50075 15C9.29018 15.1795 8.77054 15.5587 8.52758 15.6966C8.32442 15.8118 8.12033 15.8428 7.90097 15.7401C7.72373 15.6573 7.53862 15.5916 7.36276 15.5059C7.13877 15.3963 7.04344 15.1936 7.08879 14.947C7.1323 14.7091 7.17765 14.4718 7.22578 14.2348C7.27529 13.9933 7.21745 13.7897 7.02632 13.6291C6.78706 13.4283 6.5677 13.2071 6.37195 12.9637C6.21321 12.7671 6.01098 12.7102 5.76941 12.762C5.5445 12.8101 5.31866 12.8559 5.09282 12.9008C4.84339 12.9503 4.64902 12.8587 4.53425 12.6329C4.42457 12.4173 4.33433 12.1924 4.2589 11.9624C4.1793 11.719 4.24085 11.5191 4.4491 11.3683C4.64532 11.2262 4.84616 11.0911 5.04794 10.9574C5.26961 10.8107 5.34828 10.6071 5.31866 10.348C5.2858 10.0606 5.28673 9.7714 5.31635 9.48405C5.34411 9.21613 5.25711 9.01253 5.02757 8.86585C4.8383 8.74461 4.65318 8.6169 4.46853 8.4878C4.23576 8.32492 4.16541 8.12086 4.26028 7.85525C4.33803 7.6387 4.42318 7.42446 4.51852 7.21531C4.62218 6.98811 4.80822 6.89186 5.05349 6.93258C5.28025 6.97006 5.50609 7.0168 5.731 7.06585C5.99154 7.12322 6.20812 7.06631 6.3775 6.84929C6.56261 6.61238 6.77781 6.40322 7.00503 6.2061C7.18829 6.04693 7.27205 5.8549 7.21143 5.60549C7.16006 5.3931 7.12906 5.17608 7.08509 4.96184C7.01429 4.61803 7.1036 4.42924 7.4257 4.28024C7.6085 4.19603 7.79453 4.11783 7.98335 4.04842C8.22399 3.9605 8.42762 4.02574 8.57385 4.23536C8.71546 4.43849 8.84967 4.64718 8.98341 4.85587C9.12317 5.07382 9.32032 5.15896 9.57392 5.12703C9.86131 5.09094 10.1492 5.09325 10.437 5.12564C10.6763 5.15248 10.8669 5.0715 11.0002 4.86698C11.1261 4.67402 11.251 4.48014 11.3778 4.28765C11.5611 4.01001 11.7467 3.94384 12.054 4.05952C12.2479 4.13217 12.4395 4.21176 12.6264 4.3006C12.8731 4.41813 12.9684 4.6134 12.9198 4.87993C12.8763 5.11777 12.8291 5.35469 12.7828 5.59207C12.737 5.82621 12.7944 6.0261 12.9804 6.18297C13.2234 6.38842 13.4437 6.61561 13.6473 6.86086C13.8005 7.04502 13.993 7.11443 14.2337 7.05474C14.4567 6.99921 14.6839 6.95896 14.9098 6.91407C15.1648 6.86317 15.3661 6.95988 15.4822 7.19217C15.5882 7.40364 15.6761 7.6225 15.7506 7.84693C15.8335 8.09726 15.771 8.29901 15.5558 8.45495C15.3596 8.59654 15.1583 8.73166 14.9565 8.86585C14.7473 9.00466 14.6645 9.19855 14.6913 9.44425C14.7237 9.74363 14.7242 10.0435 14.6946 10.3429C14.6691 10.6038 14.7552 10.8033 14.9783 10.9467C15.1624 11.0652 15.3429 11.1897 15.523 11.3146C15.7816 11.4946 15.8506 11.6973 15.7451 11.9879C15.6766 12.1771 15.5993 12.3636 15.5174 12.5473C15.3818 12.8504 15.1907 12.9401 14.8621 12.8721C14.6423 12.8263 14.4211 12.7888 14.2017 12.7398C13.9939 12.6935 13.8213 12.7574 13.689 12.911C13.4627 13.1738 13.2234 13.4218 12.9638 13.6527C12.8088 13.7906 12.7467 13.9706 12.7958 14.1849C12.8485 14.4148 12.8874 14.6476 12.9337 14.879C12.9957 15.189 12.8999 15.3856 12.6088 15.5235C12.4478 15.5999 12.2789 15.66 12.1178 15.7364C11.911 15.8345 11.7175 15.8058 11.5236 15.7003C11.2265 15.5388 10.741 15.2332 10.5009 15C10.3403 14.8441 10.0031 14.7207 10.0008 14.5ZM11.2793 9.25176C11.2848 8.85382 11.2873 8.01509 11.2804 7.61719C11.2795 7.56905 11.2791 7.5401 11.279 7.52286C11.2788 7.50546 11.2793 7.50858 11.2794 7.52598C11.2798 7.63906 11.2816 8.09163 11.2833 8.28906C11.2796 8.687 11.2714 8.85382 11.2793 9.25176ZM11.2793 9.25176C11.2793 9.25176 10.9086 9.25685 10.7873 9.255C10.2968 9.24806 9.80624 9.24852 9.31569 9.255C9.19999 9.25638 8.86945 9.25407 8.86945 9.25407M8.86945 9.25407C8.87593 8.8677 8.87547 8.34389 8.87408 7.95752C8.87346 7.78806 8.87262 7.62829 8.87143 7.54953C8.8709 7.51441 8.86954 7.51752 8.86963 7.55263C8.86985 7.62907 8.8701 7.7811 8.86945 7.95752C8.86853 8.34435 8.86251 8.8677 8.86945 9.25407Z" stroke="currentColor"/>
			</svg>
		</a>
		<?php
		//phpcs:enable Generic.Files.LineLength.MaxExceeded
	}

	/**
	 * Adds the settings tab for AIOSEO in the Thrive Architect page builder.
	 *
	 * @since 4.6.6
	 *
	 * @return void
	 */
	public function addSettingsTab() {
		//phpcs:disable Generic.Files.LineLength.MaxExceeded
		?>
		<div class="tve-component s-item tcb-aioseo">
			<div class="dropdown-header">
				<div class="group-description s-name">
					<?php echo esc_html( AIOSEO_PLUGIN_SHORT_NAME ); ?>
				</div>
			</div>
			<div class="dropdown-content">
				<div class="tcb-aioseo-settings">
					<button class="click tcb-settings-modal-open-button s-item inside-button">
						<span class="s-name">
							<?php
								printf(
									// Translators: 1 - The plugin short name ("AIOSEO").
									esc_html__( '%1$s Settings', 'all-in-one-seo-pack' ),
									esc_html( AIOSEO_PLUGIN_SHORT_NAME )
								);
							?>
						</span>
					</button>
					<div class="mt-10 button-group">
						<div id="aioseo-score-btn-settings"></div>
						<button type="button" class="p-3 action-btn click" id="settings-action-btn">
							<svg class="when-active" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
								<path d="M9.61433 9.94582C10.145 11.0636 10.2253 12.3535 9.45767 13.2683C9.24696 13.5194 8.89896 13.533 8.66555 13.3372L6.22379 11.2883L4.64347 13.1717C4.62842 13.1896 4.59542 13.1925 4.58036 13.2104L3.42703 13.7098C3.28287 13.7722 3.12128 13.6366 3.15772 13.4838L3.44925 12.2613C3.4643 12.2434 3.47935 12.2254 3.4944 12.2075L5.07472 10.3241L2.63295 8.27524C2.3816 8.06433 2.35256 7.73431 2.56327 7.4832C3.3158 6.58636 4.59718 6.40838 5.7901 6.73691L7.81453 4.7983L7.06045 4.16555C6.80909 3.95464 6.78006 3.62462 6.99077 3.37351L7.7132 2.51255C7.90885 2.27937 8.25395 2.23272 8.50531 2.44363L13.3888 6.54141C13.6222 6.73725 13.6542 7.10028 13.4585 7.33345L12.7361 8.19441C12.5254 8.44552 12.1774 8.45917 11.944 8.26332L11.172 7.61551L9.61433 9.94582Z" fill="#FFFFFF"/>
							</svg>

							<svg class="when-inactive" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
								<path fill-rule="evenodd" clip-rule="evenodd" d="M5.87874 6.50704C5.88995 6.50899 5.90115 6.51097 5.91236 6.513L7.23686 5.29914L6.55461 4.72665C6.3212 4.5308 6.27436 4.18554 6.48527 3.93418L7.6905 2.49785C7.88635 2.26445 8.24956 2.23267 8.48297 2.42852L13.3665 6.52629C13.6179 6.73721 13.6317 7.08535 13.4358 7.31876L12.2306 8.75509C12.0197 9.00645 11.6895 9.03534 11.4381 8.82442L10.7738 8.26701L9.80841 9.78218C9.81235 9.79286 9.81625 9.80354 9.82011 9.81424L9.23164 9.32046L10.6275 7.16519L11.7766 8.12937L12.7408 6.9803L8.14451 3.12358L7.18033 4.27264L8.3294 5.23682L6.4644 6.99846L5.87874 6.50704ZM4.72914 6.45619C3.84314 6.53709 3.0184 6.91015 2.43354 7.63253C2.31301 7.77616 2.31817 8.02525 2.47976 8.16084L5.35242 10.5713L3.54458 12.7258L3.51445 12.7617L3.176 13.4568C3.09138 13.6305 3.28888 13.7962 3.44531 13.6827L4.07103 13.2287L4.10116 13.1928L5.909 11.0383L8.78167 13.4488C8.94326 13.5844 9.18946 13.5462 9.30998 13.4025C9.90734 12.6906 10.1364 11.8178 10.0663 10.9346L9.15211 10.1675C9.42355 10.9846 9.399 11.8779 8.95854 12.6181L3.26707 7.84242C3.90443 7.26739 4.79027 7.09684 5.64573 7.2253L4.72914 6.45619Z" fill="#50565F"/>
								<path fill-rule="evenodd" clip-rule="evenodd" d="M12.4242 11.9993L3.23163 4.28583L3.68158 3.7496L12.8741 11.463L12.4242 11.9993Z" fill="#50565F"/>
							</svg>
						</button>
					</div>
				</div>
			</div>
		</div>
		<?php
		//phpcs:enable Generic.Files.LineLength.MaxExceeded
	}

	/**
	 * Localizes the data by adding the 'is_aioseo_settings_enabled' option to the provided data array.
	 *
	 * @since 4.6.6
	 *
	 * @param  array $data The data array to be localized.
	 * @return array       The localized data array with the 'is_aioseo_settings_enabled' option added.
	 */
	public function localizeData( $data ) {
		// We use get_option here since it is how Thrive Architect saves the settings.
		$data['is_aioseo_settings_enabled'] = get_option( 'is_aioseo_settings_enabled', true );

		return $data;
	}

	/**
	 * Adds 'is_aioseo_settings_enabled' to the list of allowed settings.
	 *
	 * @since 4.6.6
	 *
	 * @param  array $options The array of allowed settings.
	 * @return array          The updated array of allowed settings.
	 */
	public function makeSettingsAllowed( $options ) {
		$options[] = 'is_aioseo_settings_enabled';

		return $options;
	}

	/**
	 * Returns whether or not the given Post ID was built with Thrive Architect.
	 *
	 * @since 4.6.6
	 *
	 * @param  int     $postId The Post ID.
	 * @return boolean         Whether or not the Post was built with Thrive Architect.
	 */
	public function isBuiltWith( $postId ) {
		if ( ! function_exists( 'tcb_post' ) ) {
			return false;
		}

		return tcb_post( $postId )->editor_enabled();
	}

	/**
	 * Returns whether should or not limit the modified date.
	 *
	 * @since 4.6.6
	 *
	 * @param  int     $postId The Post ID.
	 * @return boolean         Whether or not sholud limit the modified date.
	 */
	public function limitModifiedDate( $postId ) {
		if ( ! class_exists( 'TCB_Editor_Ajax' ) ) {
			return false;
		}

		// This method is supposed to be used in the `wp_ajax_tcb_editor_ajax` action.
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), \TCB_Editor_Ajax::NONCE_KEY ) ) {
			return false;
		}

		$editorPostId = ! empty( $_REQUEST['post_id'] ) ? intval( $_REQUEST['post_id'] ) : 0;
		if ( $editorPostId !== $postId ) {
			return false;
		}

		return ! empty( $_REQUEST['aioseo_limit_modified_date'] ) && 'true' === $_REQUEST['aioseo_limit_modified_date'];
	}
}PageBuilders/Base.php000066600000012307151147516220010510 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\PageBuilders;

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

/**
 * Base class for each of our page builder integrations.
 *
 * @since 4.1.7
 */
abstract class Base {
	/**
	 * The plugin files we can integrate with.
	 *
	 * @since 4.1.7
	 *
	 * @var array
	 */
	public $plugins = [];

	/**
	 * The themes names we can integrate with.
	 *
	 * @since 4.1.7
	 *
	 * @var array
	 */
	public $themes = [];

	/**
	 * The integration slug.
	 *
	 * @since 4.1.7
	 *
	 * @var string
	 */
	public $integrationSlug = '';

	/**
	 * Class constructor.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function __construct() {
		// We need to delay it to give other plugins a chance to register custom post types.
		add_action( 'init', [ $this, '_init' ], PHP_INT_MAX );
	}

	/**
	 * The internal init function.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function _init() {
		// Check if we do have an integration slug.
		if ( empty( $this->integrationSlug ) ) {
			return;
		}

		// Check if the plugin or theme to integrate with is active.
		if ( ! $this->isPluginActive() && ! $this->isThemeActive() ) {
			return;
		}

		// Check if we can proceed with the integration.
		if ( apply_filters( 'aioseo_page_builder_integration_disable', false, $this->integrationSlug ) ) {
			return;
		}

		$this->init();
	}

	/**
	 * The init function.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function init() {}

	/**
	 * Check if the integration is active.
	 *
	 * @since 4.4.8
	 *
	 * @return bool Whether or not the integration is active.
	 */
	public function isActive() {
		return $this->isPluginActive() || $this->isThemeActive();
	}

	/**
	 * Check whether or not the plugin is active.
	 *
	 * @since 4.1.7
	 *
	 * @return bool Whether or not the plugin is active.
	 */
	public function isPluginActive() {
		include_once ABSPATH . 'wp-admin/includes/plugin.php';

		$plugins = apply_filters( 'aioseo_page_builder_integration_plugins', $this->plugins, $this->integrationSlug );

		foreach ( $plugins as $basename ) {
			if ( is_plugin_active( $basename ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Check whether or not the theme is active.
	 *
	 * @since 4.1.7
	 *
	 * @return bool Whether or not the theme is active.
	 */
	public function isThemeActive() {
		$themes = apply_filters( 'aioseo_page_builder_integration_themes', $this->themes, $this->integrationSlug );

		$theme = wp_get_theme();
		foreach ( $themes as $name ) {
			if ( $name === $theme->stylesheet || $name === $theme->template ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Enqueue the scripts and styles.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function enqueue() {
		$integrationSlug = $this->integrationSlug;
		aioseo()->core->assets->load( "src/vue/standalone/page-builders/$integrationSlug/main.js", [], aioseo()->helpers->getVueData( 'post', $this->getPostId(), $integrationSlug ) );

		aioseo()->core->assets->enqueueCss( 'src/vue/assets/scss/integrations/main.scss' );

		aioseo()->admin->addAioseoModalPortal();
		aioseo()->main->enqueueTranslations();
	}

	/**
	 * Get the post ID.
	 *
	 * @since 4.1.7
	 *
	 * @return int|null The post ID or null.
	 */
	public function getPostId() {
		// phpcs:disable HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
		foreach ( [ 'id', 'post', 'post_id' ] as $key ) {
			if ( ! empty( $_GET[ $key ] ) ) {
				return (int) sanitize_text_field( wp_unslash( $_GET[ $key ] ) );
			}
		}
		// phpcs:enable

		if ( ! empty( $GLOBALS['post'] ) ) {
			return (int) $GLOBALS['post']->ID;
		}

		return null;
	}

	/**
	 * Returns the page builder edit url for the given Post ID.
	 *
	 * @since 4.3.1
	 *
	 * @param  int    $postId The Post ID.
	 * @return string         The Edit URL.
	 */
	public function getEditUrl( $postId ) { // phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		return '';
	}

	/**
	 * Returns whether or not the given Post ID was built with the Page Builder.
	 *
	 * @since 4.1.7
	 *
	 * @param  int $postId The Post ID.
	 * @return boolean     Whether or not the Post was built with the Page Builder.
	 */
	public function isBuiltWith( $postId ) { // phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		return false;
	}

	/**
	 * Checks whether or not we should prevent the date from being modified.
	 *
	 * @since 4.5.2
	 *
	 * @param  int  $postId The Post ID.
	 * @return bool         Whether or not we should prevent the date from being modified.
	 */
	public function limitModifiedDate( $postId ) { // phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		return false;
	}

	/**
	 * Returns the processed page builder content.
	 *
	 * @since 4.5.2
	 *
	 * @param  int    $postId  The post id.
	 * @param  string $content The raw content.
	 * @return string          The processed content.
	 */
	public function processContent( $postId, $content = '' ) {
		if ( empty( $content ) ) {
			$post = get_post( $postId );
			if ( is_a( $post, 'WP_Post' ) ) {
				$content = $post->post_content;
			}
		}

		if ( aioseo()->helpers->isAjaxCronRestRequest() ) {
			return apply_filters( 'the_content', $content );
		}

		return $content;
	}
}PageBuilders/WPBakery.php000066600000006772151147516220011333 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\PageBuilders;

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

/**
 * Integrate our SEO Panel with WPBakery Page Builder.
 *
 * @since 4.5.2
 */
class WPBakery extends Base {
	/**
	 * The plugin files.
	 *
	 * @since 4.5.2
	 *
	 * @var array
	 */
	public $plugins = [
		'js_composer/js_composer.php',
		'js_composer_salient/js_composer.php'
	];

	/**
	 * The integration slug.
	 *
	 * @since 4.5.2
	 *
	 * @var string
	 */
	public $integrationSlug = 'wpbakery';

	/**
	 * Init the integration.
	 *
	 * @since 4.5.2
	 *
	 * @return void
	 */
	public function init() {
		// Disable SEO meta tags from WP Bakery.
		if ( defined( 'WPB_VC_VERSION' ) && version_compare( WPB_VC_VERSION, '7.4', '>=' ) ) {
			add_filter( 'get_post_metadata', [ $this, 'maybeDisableWpBakeryMetaTags' ], 10, 3 );
		}

		if ( ! aioseo()->postSettings->canAddPostSettingsMetabox( get_post_type( $this->getPostId() ) ) ) {
			return;
		}

		add_action( 'vc_frontend_editor_enqueue_js_css', [ $this, 'enqueue' ] );
		add_action( 'vc_backend_editor_enqueue_js_css', [ $this, 'enqueue' ] );

		add_filter( 'vc_nav_front_controls', [ $this, 'addNavbarCotnrols' ] );
		add_filter( 'vc_nav_controls', [ $this, 'addNavbarCotnrols' ] );
	}

	/**
	 * Maybe disable WP Bakery meta tags.
	 *
	 * @since 4.7.1
	 *
	 * @param  mixed  $value    The value of the meta.
	 * @param  int    $objectId The object ID.
	 * @param  string $metaKey  The meta key.
	 * @return mixed            The value of the meta.
	 */
	public function maybeDisableWpBakeryMetaTags( $value, $objectId, $metaKey ) {
		if ( is_singular() && '_wpb_post_custom_seo_settings' === $metaKey ) {
			return null;
		}

		return $value;
	}

	public function addNavbarCotnrols( $controlList ) {
		$controlList[] = [
			'aioseo',
			'<li class="vc_show-mobile"><div id="aioseo-wpbakery" style="height: 100%;"></div></li>'
		];

		return $controlList;
	}

	/**
	 * Returns whether or not the given Post ID was built with WPBakery.
	 *
	 * @since 4.5.2
	 *
	 * @param  int $postId The Post ID.
	 * @return boolean     Whether or not the Post was built with WPBakery.
	 */
	public function isBuiltWith( $postId ) {
		$postObj = get_post( $postId );
		if ( ! empty( $postObj ) && preg_match( '/vc_row/', (string) $postObj->post_content ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns whether should or not limit the modified date.
	 *
	 * @since 4.5.2
	 *
	 * @param  int     $postId The Post ID.
	 * @return boolean         Whether or not sholud limit the modified date.
	 */
	public function limitModifiedDate( $postId ) {
		// This method is supposed to be used in the `saveAjaxFe` action.
		if ( empty( $_REQUEST['_vcnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_vcnonce'] ) ), 'vc-nonce-vc-admin-nonce' ) ) {
			return false;
		}

		$editorPostId = ! empty( $_REQUEST['post_id'] ) ? intval( $_REQUEST['post_id'] ) : 0;
		if ( $editorPostId !== $postId ) {
			return false;
		}

		return ! empty( $_REQUEST['aioseo_limit_modified_date'] ) && (bool) $_REQUEST['aioseo_limit_modified_date'];
	}

	/**
	 * Returns the processed page builder content.
	 *
	 * @since 4.5.2
	 *
	 * @param  int    $postId  The post id.
	 * @param  string $content The raw content.
	 * @return string          The processed content.
	 */
	public function processContent( $postId, $content = '' ) {
		if ( method_exists( '\WPBMap', 'addAllMappedShortcodes' ) ) {
			\WPBMap::addAllMappedShortcodes();
		}

		return parent::processContent( $postId, $content );
	}
}PageBuilders/Divi.php000066600000012577151147516220010542 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\PageBuilders;

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

/**
 * Integrate our SEO Panel with Divi Page Builder.
 *
 * @since 4.1.7
 */
class Divi extends Base {
	/**
	 * The theme name.
	 *
	 * @since 4.1.7
	 *
	 * @var array
	 */
	public $themes = [ 'Divi', 'Extra' ];

	/**
	 * The plugin files.
	 *
	 * @since 4.2.0
	 *
	 * @var array
	 */
	public $plugins = [
		'divi-builder/divi-builder.php'
	];

	/**
	 * The integration slug.
	 *
	 * @since 4.1.7
	 *
	 * @var string
	 */
	public $integrationSlug = 'divi';

	/**
	 * Init the integration.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function init() {
		add_action( 'wp', [ $this, 'maybeRun' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAdmin' ] );
	}

	/**
	 * Check if we are in the Page Builder and run the integrations.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function maybeRun() {
		$postType = get_post_type( $this->getPostId() );

		if (
			! defined( 'ET_BUILDER_PRODUCT_VERSION' ) ||
			! version_compare( '4.9.2', ET_BUILDER_PRODUCT_VERSION, '<=' ) ||
			! ( function_exists( 'et_core_is_fb_enabled' ) && et_core_is_fb_enabled() ) ||
			! aioseo()->postSettings->canAddPostSettingsMetabox( $postType )
		) {
			return;
		}

		add_action( 'wp_footer', [ $this, 'addContainers' ] );
		add_action( 'wp_footer', [ $this, 'addIframeWatcher' ] );
		add_action( 'wp_enqueue_scripts', [ $this, 'enqueue' ] );
		add_filter( 'script_loader_tag', [ $this, 'addEtTag' ], 10, 2 );
	}

	/**
	 * Enqueue the required scripts for the admin screen.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function enqueueAdmin() {
		if ( ! aioseo()->helpers->isScreenBase( 'toplevel_page_et_divi_options' ) ) {
			return;
		}

		aioseo()->core->assets->load( 'src/vue/standalone/page-builders/divi-admin/main.js', [], aioseo()->helpers->getVueData() );

		aioseo()->main->enqueueTranslations();
	}

	/**
	 * Add et attributes to script tags.
	 *
	 * @since 4.1.7
	 *
	 * @param  string $tag    The <script> tag for the enqueued script.
	 * @param  string $handle The script's registered handle.
	 * @return string         The tag.
	 */
	public function addEtTag( $tag, $handle = '' ) {
		$scriptHandles = [
			'aioseo/js/src/vue/standalone/page-builders/divi/main.js',
			'aioseo/js/src/vue/standalone/app/main.js'
		];

		if ( in_array( $handle, $scriptHandles, true ) ) {
			// These tags load in parent window only, not in Divi iframe.
			return preg_replace( '/<script/', '<script class="et_fb_ignore_iframe"', (string) $tag );
		}

		return $tag;
	}

	/**
	 * Add the Divi watcher.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function addIframeWatcher() {
		?>
		<script type="text/javascript">
			if (typeof jQuery === 'function') {
				jQuery(window).on('et_builder_api_ready et_fb_section_content_change', function(event) {
					window.parent.postMessage({ eventType : event.type })
				})
			}
		</script>
		<?php
	}

	/**
	 * Add the containers to mount our panel.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function addContainers() {
		echo '<div id="aioseo-app-modal" class="et_fb_ignore_iframe"><div class="et_fb_ignore_iframe"></div></div>';
		echo '<div id="aioseo-settings" class="et_fb_ignore_iframe"></div>';
		echo '<div id="aioseo-admin" class="et_fb_ignore_iframe"></div>';
		echo '<div id="aioseo-modal-portal" class="et_fb_ignore_iframe"></div>';
	}

	/**
	 * Returns whether or not the given Post ID was built with Divi.
	 *
	 * @since 4.1.7
	 *
	 * @param  int $postId The Post ID.
	 * @return boolean     Whether or not the Post was built with Divi.
	 */
	public function isBuiltWith( $postId ) {
		if ( ! function_exists( 'et_pb_is_pagebuilder_used' ) ) {
			return false;
		}

		return et_pb_is_pagebuilder_used( $postId );
	}

	/**
	 * Returns the Divi edit url for the given Post ID.
	 *
	 * @since 4.3.1
	 *
	 * @param  int    $postId The Post ID.
	 * @return string         The Edit URL.
	 */
	public function getEditUrl( $postId ) {
		if ( ! function_exists( 'et_fb_get_vb_url' ) ) {
			return '';
		}

		$isDiviLibrary = 'et_pb_layout' === get_post_type( $postId );
		$editUrl       = $isDiviLibrary ? get_edit_post_link( $postId, 'raw' ) : get_permalink( $postId );

		if ( et_pb_is_pagebuilder_used( $postId ) ) {
			$editUrl = et_fb_get_vb_url( $editUrl );
		} else {
			if ( ! et_pb_is_allowed( 'divi_builder_control' ) ) {
				// Prevent link when user lacks `Toggle Divi Builder` capability.
				return '';
			}

			$editUrl = add_query_arg(
				[ 'et_fb_activation_nonce' => wp_create_nonce( 'et_fb_activation_nonce_' . $postId ) ],
				$editUrl
			);
		}

		return $editUrl;
	}

	/**
	 * Checks whether or not we should prevent the date from being modified.
	 *
	 * @since 4.5.2
	 *
	 * @param  int  $postId The Post ID.
	 * @return bool         Whether or not we should prevent the date from being modified.
	 */
	public function limitModifiedDate( $postId ) {
		// This method is supposed to be used in the `wp_ajax_et_fb_ajax_save` action.
		if ( empty( $_REQUEST['et_fb_save_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['et_fb_save_nonce'] ) ), 'et_fb_save_nonce' ) ) {
			return false;
		}

		$editorPostId = ! empty( $_REQUEST['post_id'] ) ? intval( $_REQUEST['post_id'] ) : 0;
		if ( $editorPostId !== $postId ) {
			return false;
		}

		return ! empty( $_REQUEST['options']['conditional_tags']['aioseo_limit_modified_date'] );
	}
}PageBuilders/SiteOrigin.php000066600000005157151147516220011717 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\PageBuilders;

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

/**
 * Integrate our SEO Panel with SiteOrigin Page Builder.
 *
 * @since 4.6.6
 */
class SiteOrigin extends Base {
	/**
	 * The plugin files.
	 *
	 * @since 4.6.6
	 *
	 * @var array
	 */
	public $plugins = [
		'siteorigin-panels/siteorigin-panels.php'
	];

	/**
	 * The integration slug.
	 *
	 * @since 4.6.6
	 *
	 * @var string
	 */
	public $integrationSlug = 'siteorigin';

	/**
	 * Init the integration.
	 *
	 * @since 4.6.6
	 *
	 * @return void
	 */
	public function init() {
		$postType = get_post_type( $this->getPostId() );
		if ( empty( $postType ) ) {
			$postType = ! empty( $_GET['post_type'] ) ? sanitize_text_field( wp_unslash( $_GET['post_type'] ) ) : 'post'; // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended, Generic.Files.LineLength.MaxExceeded
		}

		if ( ! aioseo()->postSettings->canAddPostSettingsMetabox( $postType ) ) {
			return;
		}

		add_action( 'siteorigin_panel_enqueue_admin_scripts', [ $this, 'enqueue' ] );
	}

	/**
	 * Returns whether or not the given Post ID was built with SiteOrigin.
	 *
	 * @since 4.6.6
	 *
	 * @param  int $postId The Post ID.
	 * @return bool        Whether or not the Post was built with SiteOrigin.
	 */
	public function isBuiltWith( $postId ) {
		$postObj = get_post( $postId );
		if (
			! empty( $postObj ) &&
			(
				preg_match( '/siteorigin_widget/', (string) $postObj->post_content ) ||
				preg_match( '/so-panel widget/', (string) $postObj->post_content )
			)
		) {
			return true;
		}

		return false;
	}

	/**
	 * Returns the processed page builder content.
	 *
	 * @since 4.6.6
	 *
	 * @param  int    $postId  The post id.
	 * @param  string $content The raw content.
	 * @return string          The processed content.
	 */
	public function processContent( $postId, $content = '' ) {
		// When performing a save_post action, we must execute the siteorigin_widget shortcodes if there are image widgets.
		// This ensures that the getFirstImageInContent method can locate the images, as SiteOrigin uses shortcodes for images.
		// We cache the first image in the content during post saving.
		if (
			doing_action( 'save_post' ) &&
			aioseo()->options->searchAppearance->advanced->runShortcodes &&
			(
				stripos( $content, 'SiteOrigin_Widget_Image_Widget' ) !== false ||
				stripos( $content, 'WP_Widget_Media_Image' ) !== false
			)
		) {
			$content = aioseo()->helpers->doAllowedShortcodes( $content, $postId, [ 'siteorigin_widget' ] );
		}

		return parent::processContent( $postId, $content );
	}
}PageBuilders/Elementor.php000066600000013656151147516220011600 0ustar00<?php
namespace AIOSEO\Plugin\Common\Standalone\PageBuilders;

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

use Elementor\Controls_Manager as ControlsManager;
use Elementor\Core\DocumentTypes\PageBase;

/**
 * Integrate our SEO Panel with Elementor Page Builder.
 *
 * @since 4.1.7
 */
class Elementor extends Base {
	/**
	 * The plugin files.
	 *
	 * @since 4.1.7
	 *
	 * @var array
	 */
	public $plugins = [
		'elementor/elementor.php',
		'elementor-pro/elementor-pro.php',
	];

	/**
	 * The integration slug.
	 *
	 * @since 4.1.7
	 *
	 * @var string
	 */
	public $integrationSlug = 'elementor';

	/**
	 * Init the integration.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function init() {
		if ( ! aioseo()->postSettings->canAddPostSettingsMetabox( get_post_type( $this->getPostId() ) ) ) {
			return;
		}

		if ( ! did_action( 'elementor/init' ) ) {
			add_action( 'elementor/init', [ $this, 'addPanelTab' ] );
		} else {
			$this->addPanelTab();
		}

		add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue' ] );
		add_action( 'elementor/documents/register_controls', [ $this, 'registerDocumentControls' ] );
		add_action( 'elementor/editor/footer', [ $this, 'addContainers' ] );

		// Add the SEO tab to the main Elementor panel.
		add_action( 'elementor/editor/footer', [ $this, 'startCapturing' ], 0 );
		add_action( 'elementor/editor/footer', [ $this, 'endCapturing' ], 999 );
	}

	/**
	 * Start capturing buffer.
	 *
	 * @since 4.3.5
	 *
	 * @return void
	 */
	public function startCapturing() {
		ob_start();
	}

	/**
	 * End capturing buffer and add button.
	 * This is a hack to add the SEO tab to the main Elementor panel.
	 * We need to do this because Elementor doesn't provide a filter to add tabs to the main panel.
	 *
	 * @since 4.3.5
	 *
	 * @return void
	 */
	public function endCapturing() {
		$output  = ob_get_clean();
		$search  = '/(<div class="elementor-component-tab elementor-panel-navigation-tab" data-tab="global">.*<\/div>)/m';
		$replace = '${1}<div class="elementor-component-tab elementor-panel-navigation-tab" data-tab="aioseo">SEO</div>';
		echo preg_replace( $search, $replace, $output ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * Add the AIOSEO Panel Tab on Elementor.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function addPanelTab() {
		ControlsManager::add_tab( 'aioseo', AIOSEO_PLUGIN_SHORT_NAME );
	}

	/**
	 * Register the Elementor Document Controls.
	 *
	 * @since 4.1.7
	 *
	 * @return void
	 */
	public function registerDocumentControls( $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(
			'aioseo_section',
			[
				'label' => AIOSEO_PLUGIN_SHORT_NAME,
				'tab'   => 'aioseo',
			]
		);

		$document->end_controls_section();
	}

	/**
	 * Returns whether or not the given Post ID was built with Elementor.
	 *
	 * @since 4.1.7
	 *
	 * @param  int     $postId The Post ID.
	 * @return boolean         Whether or not the Post was built with Elementor.
	 */
	public function isBuiltWith( $postId ) {
		$document = $this->getElementorDocument( $postId );
		if ( ! $document ) {
			return false;
		}

		return $document->is_built_with_elementor();
	}

	/**
	 * Returns the Elementor edit url for the given Post ID.
	 *
	 * @since 4.3.1
	 *
	 * @param  int    $postId The Post ID.
	 * @return string         The Edit URL.
	 */
	public function getEditUrl( $postId ) {
		$document = $this->getElementorDocument( $postId );
		if ( ! $document || ! $document->is_editable_by_current_user() ) {
			return '';
		}

		return esc_url( $document->get_edit_url() );
	}

	/**
	 * Add the containers to mount our panel.
	 *
	 * @since 4.1.9
	 *
	 * @return void
	 */
	public function addContainers() {
		echo '<div id="aioseo-admin"></div>';
	}

	/**
	 * Returns the Elementor Document instance for the given Post ID.
	 *
	 * @since 4.3.5
	 *
	 * @param  int    $postId The Post ID.
	 * @return object         The Elementor Document instance.
	 */
	private function getElementorDocument( $postId ) {
		if (
			! class_exists( '\Elementor\Plugin' ) ||
			! is_object( \Elementor\Plugin::instance()->documents ) ||
			! method_exists( \Elementor\Plugin::instance()->documents, 'get' )
		) {
			return false;
		}

		$elementorDocument = \Elementor\Plugin::instance()->documents->get( $postId );
		if ( empty( $elementorDocument ) ) {
			return false;
		}

		return $elementorDocument;
	}

	/**
	 * Checks whether or not we should prevent the date from being modified.
	 * This method is supposed to be used in the `wp_ajax_seedprod_pro_save_lpage` action.
	 *
	 * @since 4.5.2
	 *
	 * @param  int  $postId The Post ID.
	 * @return bool         Whether or not we should prevent the date from being modified.
	 */
	public function limitModifiedDate( $postId ) {
		// This method is supposed to be used in the `wp_ajax_elementor_ajax` action.
		if ( empty( $_REQUEST['_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_nonce'] ) ), 'elementor_ajax' ) ) {
			return false;
		}

		$editorPostId = ! empty( $_REQUEST['editor_post_id'] ) ? (int) $_REQUEST['editor_post_id'] : false;
		if ( $editorPostId !== $postId ) {
			return false;
		}

		return ! empty( $_REQUEST['aioseo_limit_modified_date'] );
	}

	/**
	 * Get the post ID.
	 *
	 * @since 4.6.9
	 *
	 * @return int|null The post ID or null.
	 */
	public function getPostId() {
		// phpcs:disable HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
		if ( aioseo()->helpers->isAjaxCronRestRequest() ) {
			foreach ( [ 'editor_post_id', 'initial_document_id' ] as $key ) {
				if ( ! empty( $_REQUEST[ $key ] ) ) {
					return intval( wp_unslash( $_REQUEST[ $key ] ) );
				}
			}
		}
		// phpcs:enable

		return parent::getPostId();
	}
}