first commit

This commit is contained in:
User A0264400
2026-04-01 23:20:16 +03:00
commit a766acdc90
23071 changed files with 4933189 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
<?php
namespace Yoast\WP\SEO\Plans\Application;
use WPSEO_Addon_Manager;
use Yoast\WP\SEO\Plans\Domain\Add_Ons\Add_On_Interface;
/**
* The collector to get add-ons.
*/
class Add_Ons_Collector {
/**
* All add-ons.
*
* @var array<Add_On_Interface>
*/
private $add_ons;
/**
* Holds the WPSEO_Addon_Manager.
*
* @var WPSEO_Addon_Manager
*/
private $addon_manager;
/**
* Constructs the instance.
*
* @param WPSEO_Addon_Manager $addon_manager The WPSEO_Addon_Manager.
* @param Add_On_Interface ...$add_ons All add-ons.
*/
public function __construct( WPSEO_Addon_Manager $addon_manager, Add_On_Interface ...$add_ons ) {
$this->addon_manager = $addon_manager;
$this->add_ons = $add_ons;
}
/**
* Returns all the add-ons.
*
* @return array<Add_On_Interface> All the add-ons.
*/
public function get(): array {
return $this->add_ons;
}
/**
* Returns the data for the add-ons in an array format.
*
* @return array<string, string|bool|array<string, string>> The add-ons in an array format.
*/
public function to_array(): array {
$result = [];
$active_addons = $this->addon_manager->has_active_addons();
foreach ( $this->add_ons as $add_on ) {
$result[ $add_on->get_id() ] = [
'id' => $add_on->get_id(),
'isActive' => $add_on->is_active(),
'hasLicense' => $active_addons && $add_on->has_license(),
'ctb' => [
'action' => $add_on->get_ctb_action(),
'id' => $add_on->get_ctb_id(),
],
];
}
return $result;
}
}

View File

@@ -0,0 +1,51 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Plans\Application;
use WPSEO_Admin_Utils;
/**
* The Yoast SEO Duplicate Post plugin Manager.
*/
class Duplicate_Post_Manager {
/**
* The Duplicate post main file.
*
* @var string
*/
public const PLUGIN_FILE = 'duplicate-post/duplicate-post.php';
/**
* Checks if the plugin is installed and activated in WordPress.
*
* @return bool True when installed and activated.
*/
protected function is_activated() {
return \is_plugin_active( self::PLUGIN_FILE );
}
/**
* Checks if the plugin is installed in WordPress.
*
* @return bool True when installed.
*/
protected function is_installed() {
return \file_exists( \WP_PLUGIN_DIR . '/' . self::PLUGIN_FILE );
}
/**
* Gets the duplicate post plans page params
*
* @return array<string|bool> The list of params.
*/
public function get_params() {
return [
'isInstalled' => $this->is_installed(),
'isActivated' => $this->is_activated(),
'installationUrl' => \html_entity_decode( WPSEO_Admin_Utils::get_install_url( self::PLUGIN_FILE ) ),
'activationUrl' => \html_entity_decode( WPSEO_Admin_Utils::get_activation_url( self::PLUGIN_FILE ) ),
];
}
}

View File

@@ -0,0 +1,45 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Plans\Domain\Add_Ons;
/**
* This interface describes an add-on for a plan.
*/
interface Add_On_Interface {
/**
* Returns the ID.
*
* @return string
*/
public function get_id(): string;
/**
* Returns whether the add-on is installed and activated.
*
* @return bool
*/
public function is_active(): bool;
/**
* Returns whether the add-on has an valid license.
*
* @return bool
*/
public function has_license(): bool;
/**
* Returns the click-to-buy action.
*
* @return string
*/
public function get_ctb_action(): string;
/**
* Returns the click-to-buy ID.
*
* @return string
*/
public function get_ctb_id(): string;
}

View File

@@ -0,0 +1,47 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Plans\Domain\Add_Ons;
use WPSEO_Addon_Manager;
use Yoast\WP\SEO\Plans\Infrastructure\Add_Ons\Managed_Add_On;
/**
* The Yoast Premium SEO add-on.
*/
class Premium extends Managed_Add_On implements Add_On_Interface {
/**
* The slug of the add-on.
*
* @var string
*/
protected const SLUG = WPSEO_Addon_Manager::PREMIUM_SLUG;
/**
* Returns the ID.
*
* @return string
*/
public function get_id(): string {
return 'premium';
}
/**
* Returns the click-to-buy action.
*
* @return string
*/
public function get_ctb_action(): string {
return 'load-nfd-ctb';
}
/**
* Returns the click-to-buy ID.
*
* @return string
*/
public function get_ctb_id(): string {
return 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2';
}
}

View File

@@ -0,0 +1,47 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Plans\Domain\Add_Ons;
use WPSEO_Addon_Manager;
use Yoast\WP\SEO\Plans\Infrastructure\Add_Ons\Managed_Add_On;
/**
* The Yoast WooCommerce SEO add-on.
*/
class Woo extends Managed_Add_On implements Add_On_Interface {
/**
* The slug of the add-on.
*
* @var string
*/
protected const SLUG = WPSEO_Addon_Manager::WOOCOMMERCE_SLUG;
/**
* Returns the ID.
*
* @return string
*/
public function get_id(): string {
return 'woo';
}
/**
* Returns the click-to-buy action.
*
* @return string
*/
public function get_ctb_action(): string {
return 'load-nfd-woo-ctb';
}
/**
* Returns the click-to-buy ID.
*
* @return string
*/
public function get_ctb_id(): string {
return '5b32250e-e6f0-44ae-ad74-3cefc8e427f9';
}
}

View File

@@ -0,0 +1,62 @@
<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\Plans\Infrastructure\Add_Ons;
use InvalidArgumentException;
use WPSEO_Addon_Manager;
use Yoast\WP\SEO\Plans\Domain\Add_Ons\Add_On_Interface;
/**
* Represents a managed add-on.
* Uses the WPSEO_Addon_Manager to check if the add-on is installed and activated, and if it has a valid license.
*/
abstract class Managed_Add_On implements Add_On_Interface {
/**
* The slug of the add-on.
*
* @var string
*/
protected const SLUG = '';
/**
* Holds the WPSEO_Addon_Manager.
*
* @var WPSEO_Addon_Manager
*/
private $addon_manager;
/**
* Constructs the instance.
*
* @param WPSEO_Addon_Manager $addon_manager The WPSEO_Addon_Manager.
*
* @throws InvalidArgumentException If the slug is not set.
*/
public function __construct( WPSEO_Addon_Manager $addon_manager ) {
if ( static::SLUG === '' ) {
throw new InvalidArgumentException( 'The add-on slug must be set.' );
}
$this->addon_manager = $addon_manager;
}
/**
* Returns whether the add-on is installed and activated.
*
* @return bool
*/
public function is_active(): bool {
return $this->addon_manager->is_installed( static::SLUG );
}
/**
* Returns whether the add-on has an valid license.
*
* @return bool
*/
public function has_license(): bool {
return $this->addon_manager->has_valid_subscription( static::SLUG );
}
}

View File

@@ -0,0 +1,204 @@
<?php
namespace Yoast\WP\SEO\Plans\User_Interface;
use WPSEO_Admin_Asset_Manager;
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\General\User_Interface\General_Page_Integration;
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
use Yoast\WP\SEO\Helpers\Short_Link_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Plans\Application\Add_Ons_Collector;
use Yoast\WP\SEO\Plans\Application\Duplicate_Post_Manager;
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
/**
* Adds the plans page to the Yoast admin menu.
*/
class Plans_Page_Integration implements Integration_Interface {
use No_Conditionals;
/**
* The page name.
*/
public const PAGE = 'wpseo_licenses';
/**
* The assets name.
*/
public const ASSETS_NAME = 'plans';
/**
* Holds the WPSEO_Admin_Asset_Manager.
*
* @var WPSEO_Admin_Asset_Manager
*/
private $asset_manager;
/**
* Holds the Add_Ons_Collector.
*
* @var Add_Ons_Collector
*/
private $add_ons_collector;
/**
* Holds the Current_Page_Helper.
*
* @var Current_Page_Helper
*/
private $current_page_helper;
/**
* Holds the Short_Link_Helper.
*
* @var Short_Link_Helper
*/
private $short_link_helper;
/**
* Holds the Admin_Conditional.
*
* @var Admin_Conditional
*/
private $admin_conditional;
/**
* The promotion manager.
*
* @var Promotion_Manager
*/
private $promotion_manager;
/**
* The Duplicate Post plugin manager.
*
* @var Duplicate_Post_Manager
*/
private $duplicate_post_manager;
/**
* Constructs the instance.
*
* @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager.
* @param Add_Ons_Collector $add_ons_collector The Add_Ons_Collector.
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
* @param Short_Link_Helper $short_link_helper The Short_Link_Helper.
* @param Admin_Conditional $admin_conditional The Admin_Conditional.
* @param Promotion_Manager $promotion_manager The promotion manager.
* @param Duplicate_Post_Manager $duplicate_post_manager The Duplicate_Post_Manager.
*/
public function __construct(
WPSEO_Admin_Asset_Manager $asset_manager,
Add_Ons_Collector $add_ons_collector,
Current_Page_Helper $current_page_helper,
Short_Link_Helper $short_link_helper,
Admin_Conditional $admin_conditional,
Promotion_Manager $promotion_manager,
Duplicate_Post_Manager $duplicate_post_manager
) {
$this->asset_manager = $asset_manager;
$this->add_ons_collector = $add_ons_collector;
$this->current_page_helper = $current_page_helper;
$this->short_link_helper = $short_link_helper;
$this->admin_conditional = $admin_conditional;
$this->promotion_manager = $promotion_manager;
$this->duplicate_post_manager = $duplicate_post_manager;
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
// Add page with priority 7 to add it above the workouts.
\add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ], 7 );
\add_filter( 'wpseo_network_submenu_pages', [ $this, 'add_page' ], 7 );
// Are we on our page?
if ( $this->admin_conditional->is_met() && $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
\add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
}
}
/**
* Adds the page to the (currently) last position in the array.
*
* @param array<string, array<string, array<static|string>>> $pages The pages.
*
* @return array<string, array<string, array<static|string>>> The pages.
*/
public function add_page( $pages ) {
$pages[] = [
General_Page_Integration::PAGE,
'',
\__( 'Plans', 'wordpress-seo' ),
'wpseo_manage_options',
self::PAGE,
[ $this, 'display_page' ],
];
return $pages;
}
/**
* Displays the page.
*
* @return void
*/
public function display_page() {
echo '<div id="yoast-seo-plans"></div>';
}
/**
* Enqueues the assets.
*
* @return void
*/
public function enqueue_assets() {
// Remove the emoji script as it is incompatible with both React and any contenteditable fields.
\remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
$this->asset_manager->enqueue_script( self::ASSETS_NAME );
$this->asset_manager->enqueue_style( self::ASSETS_NAME );
$this->asset_manager->localize_script( self::ASSETS_NAME, 'wpseoScriptData', $this->get_script_data() );
}
/**
* Creates the script data.
*
* @return array<string,array<string, string|bool|array<string, string>>> The script data.
*/
private function get_script_data(): array {
return [
'addOns' => $this->add_ons_collector->to_array(),
'linkParams' => $this->short_link_helper->get_query_params(),
'preferences' => [
'isRtl' => \is_rtl(),
],
'currentPromotions' => $this->promotion_manager->get_current_promotions(),
'duplicatePost' => $this->duplicate_post_manager->get_params(),
'userCan' => [
'installPlugin' => \current_user_can( 'install_plugins' ),
'activatePlugin' => \current_user_can( 'activate_plugins' ),
],
];
}
/**
* Removes all current WP notices.
*
* @return void
*/
public function remove_notices() {
\remove_all_actions( 'admin_notices' );
\remove_all_actions( 'user_admin_notices' );
\remove_all_actions( 'network_admin_notices' );
\remove_all_actions( 'all_admin_notices' );
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace Yoast\WP\SEO\Plans\User_Interface;
use WPSEO_Addon_Manager;
use WPSEO_Shortlinker;
use Yoast\WP\SEO\Conditionals\Traits\Admin_Conditional_Trait;
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
use Yoast\WP\SEO\General\User_Interface\General_Page_Integration;
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
use Yoast\WP\SEO\Helpers\Product_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
/**
* Adds the plans page to the Yoast admin menu.
*/
class Upgrade_Sidebar_Menu_Integration implements Integration_Interface {
use Admin_Conditional_Trait;
/**
* The page name.
*/
public const PAGE = 'wpseo_upgrade_sidebar';
/**
* The WooCommerce conditional.
*
* @var WooCommerce_Conditional
*/
private $woocommerce_conditional;
/**
* The shortlinker.
*
* @var WPSEO_Shortlinker
*/
private $shortlinker;
/**
* The product helper.
*
* @var Product_Helper
*/
private $product_helper;
/**
* The current page helper.
*
* @var Current_Page_Helper
*/
private $current_page_helper;
/**
* The promotion manager.
*
* @var Promotion_Manager
*/
private $promotion_manager;
/**
* The addon manager.
*
* @var WPSEO_Addon_Manager
*/
private $addon_manager;
/**
* Constructor.
*
* @param WooCommerce_Conditional $woocommerce_conditional The WooCommerce conditional.
* @param WPSEO_Shortlinker $shortlinker The shortlinker.
* @param Product_Helper $product_helper The product helper.
* @param Current_Page_Helper $current_page_helper The current page helper.
* @param Promotion_Manager $promotion_manager The promotion manager.
* @param WPSEO_Addon_Manager $addon_manager The addon manager.
*/
public function __construct(
WooCommerce_Conditional $woocommerce_conditional,
WPSEO_Shortlinker $shortlinker,
Product_Helper $product_helper,
Current_Page_Helper $current_page_helper,
Promotion_Manager $promotion_manager,
WPSEO_Addon_Manager $addon_manager
) {
$this->woocommerce_conditional = $woocommerce_conditional;
$this->shortlinker = $shortlinker;
$this->product_helper = $product_helper;
$this->current_page_helper = $current_page_helper;
$this->promotion_manager = $promotion_manager;
$this->addon_manager = $addon_manager;
}
/**
* Initializes the integration.
*
* This is the place to register hooks and filters.
*
* @return void
*/
public function register_hooks() {
// Add page with PHP_INT_MAX - 1 to allow other items (like Brand Insights) to be positioned after.
\add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ], ( \PHP_INT_MAX - 1 ) );
\add_filter( 'wpseo_network_submenu_pages', [ $this, 'add_page' ], ( \PHP_INT_MAX - 1 ) );
\add_action( 'admin_init', [ $this, 'do_redirect' ], 1 );
}
/**
* Adds the page to the (currently) last position in the array.
*
* @param array<string, array<string, array<static|string>>> $pages The pages.
*
* @return array<string, array<string, array<static|string>>> The pages.
*/
public function add_page( $pages ) {
// Don't show the Upgrade button if Yoast SEO WooCommerce addon is active.
if ( $this->addon_manager->is_installed( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) ) {
return $pages;
}
// Don't show the Upgrade button if Premium is active without the WooCommerce plugin.
if ( $this->product_helper->is_premium() && ! $this->woocommerce_conditional->is_met() ) {
return $pages;
}
$button_content = \__( 'Upgrade', 'wordpress-seo' );
if ( $this->promotion_manager->is( 'black-friday-promotion' ) ) {
$button_content = ( $this->product_helper->is_premium() ) ? \__( 'Get 30% off', 'wordpress-seo' ) : \__( '30% off - BF Sale', 'wordpress-seo' );
}
$pages[] = [
General_Page_Integration::PAGE,
'',
'<span class="yst-root"><span class="yst-button yst-w-full yst-whitespace-nowrap yst-button--upsell yst-button--small">' . $button_content . ' </span></span>',
'wpseo_manage_options',
self::PAGE,
static function () {
echo 'redirecting...';
},
];
return $pages;
}
/**
* Redirects to the yoast.com.
*
* @return void
*/
public function do_redirect(): void {
if ( $this->current_page_helper->get_current_yoast_seo_page() !== self::PAGE ) {
return;
}
$link = $this->shortlinker->build_shortlink( 'https://yoa.st/wordpress-menu-upgrade-premium' );
if ( $this->woocommerce_conditional->is_met() ) {
$link = $this->shortlinker->build_shortlink( 'https://yoa.st/wordpress-menu-upgrade-woocommerce' );
}
\wp_redirect( $link );//phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Safe redirect is used here.
exit();
}
}