File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/Utils.tar
Access.php 0000644 00000020005 15154136225 0006457 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Access {
/**
* Capabilities for our users.
*
* @since 4.0.0
*
* @var array
*/
protected $capabilities = [
'aioseo_about_us_page',
'aioseo_dashboard',
'aioseo_feature_manager_settings',
'aioseo_general_settings',
'aioseo_link_assistant_settings',
'aioseo_local_seo_settings',
'aioseo_page_advanced_settings',
'aioseo_page_ai_content_settings',
'aioseo_page_analysis',
'aioseo_page_general_settings',
'aioseo_page_link_assistant_settings',
'aioseo_page_local_seo_settings',
'aioseo_page_redirects_manage',
'aioseo_page_schema_settings',
'aioseo_page_seo_revisions_settings',
'aioseo_page_social_settings',
'aioseo_page_writing_assistant_settings',
'aioseo_redirects_manage',
'aioseo_redirects_settings',
'aioseo_search_appearance_settings',
'aioseo_search_statistics_settings',
'aioseo_seo_analysis_settings',
'aioseo_setup_wizard',
'aioseo_sitemap_settings',
'aioseo_social_networks_settings',
'aioseo_tools_settings'
];
/**
* Whether we're already updating the roles during this request.
*
* @since 4.2.7
*
* @var bool
*/
protected $isUpdatingRoles = false;
/**
* Roles we check capabilities against.
*
* @since 4.0.0
*
* @var array
*/
protected $roles = [
'superadmin' => 'superadmin',
'administrator' => 'administrator',
'editor' => 'editor',
'author' => 'author',
'contributor' => 'contributor'
];
/**
* Class constructor.
*
* @since 4.0.0
*/
public function __construct() {
// First load the roles so that we can pull the roles from the other plugins.
add_action( 'plugins_loaded', [ $this, 'setRoles' ], 999 );
// Load later again so that we can pull the roles lately registered.
// This needs to run before 1000 so that our update migrations and other hook callbacks can pull the roles.
add_action( 'init', [ $this, 'setRoles' ], 999 );
}
/**
* Sets the roles on the instance.
*
* @since 4.1.5
*
* @return void
*/
public function setRoles() {
$adminRoles = [];
$allRoles = aioseo()->helpers->getUserRoles();
foreach ( $allRoles as $roleName => $wpRole ) {
$role = get_role( $roleName );
if ( $this->isAdmin( $roleName ) || $role->has_cap( 'publish_posts' ) ) {
$adminRoles[ $roleName ] = $roleName;
}
}
$this->roles = array_merge( $this->roles, $adminRoles );
}
/**
* Adds capabilities into WordPress for the current user.
* Only on activation or settings saved.
*
* @since 4.0.0
*
* @return void
*/
public function addCapabilities() {
$this->isUpdatingRoles = true;
foreach ( $this->roles as $wpRole => $role ) {
$roleObject = get_role( $wpRole );
if ( ! is_object( $roleObject ) ) {
continue;
}
if ( $this->isAdmin( $role ) ) {
$roleObject->add_cap( 'aioseo_manage_seo' );
}
if ( $roleObject->has_cap( 'edit_posts' ) ) {
$postCapabilities = [
'aioseo_page_advanced_settings',
'aioseo_page_ai_content_settings',
'aioseo_page_analysis',
'aioseo_page_general_settings',
'aioseo_page_schema_settings',
'aioseo_page_social_settings'
];
foreach ( $postCapabilities as $capability ) {
$roleObject->add_cap( $capability );
}
}
}
}
/**
* Removes capabilities for any unknown role.
*
* @since 4.0.0
*
* @return void
*/
public function removeCapabilities() {
$this->isUpdatingRoles = true;
// Clear out capabilities for unknown roles.
$wpRoles = wp_roles();
$allRoles = $wpRoles->roles;
foreach ( $allRoles as $key => $wpRole ) {
$checkRole = is_multisite() ? 'superadmin' : 'administrator';
if ( $checkRole === $key ) {
continue;
}
if ( array_key_exists( $key, $this->roles ) ) {
continue;
}
$role = get_role( $key );
if ( ! is_a( $role, 'WP_Role' ) || ! is_array( $role->capabilities ) ) {
continue;
}
// We don't need to remove the capabilities for administrators.
if ( $this->isAdmin( $key ) ) {
continue;
}
foreach ( $this->capabilities as $capability ) {
if ( $role->has_cap( $capability ) ) {
$role->remove_cap( $capability );
}
}
$role->remove_cap( 'aioseo_manage_seo' );
}
}
/**
* Checks if the current user has the capability.
*
* @since 4.0.0
*
* @param string|array $capability The capability to check against.
* @param string|null $checkRole A role to check against.
* @return bool Whether or not the user has this capability.
*/
public function hasCapability( $capability, $checkRole = null ) {
if ( $this->isAdmin( $checkRole ) ) {
return true;
}
$canPublishOrEdit = $this->can( 'publish_posts', $checkRole ) || $this->can( 'edit_posts', $checkRole );
if ( ! $canPublishOrEdit ) {
return false;
}
if ( is_array( $capability ) ) {
foreach ( $capability as $cap ) {
if ( false !== strpos( $cap, 'aioseo_page_' ) ) {
return true;
}
}
return false;
}
return false !== strpos( $capability, 'aioseo_page_' );
}
/**
* Gets all the capabilities for the current user.
*
* @since 4.0.0
*
* @param string|null $role A role to check against.
* @return array An array of capabilities.
*/
public function getAllCapabilities( $role = null ) {
$capabilities = [];
foreach ( $this->getCapabilityList() as $capability ) {
$capabilities[ $capability ] = $this->hasCapability( $capability, $role );
}
$capabilities['aioseo_admin'] = $this->isAdmin( $role );
$capabilities['aioseo_manage_seo'] = $this->isAdmin( $role );
$capabilities['aioseo_about_us_page'] = $this->canManage( $role );
return $capabilities;
}
/**
* Returns the capability list.
*
* @return 4.1.3
*
* @return array An array of capabilities.
*/
public function getCapabilityList() {
return $this->capabilities;
}
/**
* If the current user is an admin, or superadmin, they have access to all caps regardless.
*
* @since 4.0.0
*
* @param string|null $role The role to check admin privileges if we have one.
* @return bool Whether not the user/role is an admin.
*/
public function isAdmin( $role = null ) {
if ( $role ) {
if ( ( is_multisite() && 'superadmin' === $role ) || 'administrator' === $role ) {
return true;
}
return false;
}
if ( ! function_exists( 'wp_get_current_user' ) ) {
return false;
}
if ( ( is_multisite() && current_user_can( 'superadmin' ) ) || current_user_can( 'administrator' ) ) {
return true;
}
return false;
}
/**
* Check if the passed in role can publish posts.
*
* @since 4.0.9
*
* @param string $capability The capability to check against.
* @param string $role The role to check.
* @return boolean True if the role can publish.
*/
protected function can( $capability, $role ) {
if ( empty( $role ) ) {
return current_user_can( $capability );
}
$wpRoles = wp_roles();
$allRoles = $wpRoles->roles;
foreach ( $allRoles as $key => $wpRole ) {
if ( $key === $role ) {
$r = get_role( $key );
if ( $r->has_cap( $capability ) ) {
return true;
}
}
}
return false;
}
/**
* Checks if the current user can manage AIOSEO.
*
* @since 4.0.0
*
* @param string|null $checkRole A role to check against.
* @return bool Whether or not the user can manage AIOSEO.
*/
public function canManage( $checkRole = null ) {
return $this->isAdmin( $checkRole );
}
/**
* Gets all options that the user does not have access to manage.
*
* @since 4.1.3
*
* @return array An array with the option names.
*/
public function getNotAllowedOptions() {
return [];
}
/**
* Gets all page fields that the user does not have access to manage.
*
* @since 4.1.3
*
* @return array An array with the field names.
*/
public function getNotAllowedPageFields() {
return [];
}
/**
* Returns Roles.
*
* @since 4.0.17
*
* @return array An array of role names.
*/
public function getRoles() {
return $this->roles;
}
} ActionScheduler.php 0000644 00000021010 15154136225 0010327 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles all Action Scheduler related tasks.
*
* @since 4.0.0
*/
class ActionScheduler {
/**
* The Action Scheduler group.
*
* @since 4.1.5
* @version 4.2.7
*
* @var string
*/
private $actionSchedulerGroup = 'aioseo';
/**
* Class constructor.
*
* @since 4.0.0
*/
public function __construct() {
add_action( 'action_scheduler_after_execute', [ $this, 'cleanup' ], 1000, 2 );
// Note: \ActionScheduler is first loaded on `plugins_loaded` action hook.
add_action( 'plugins_loaded', [ $this, 'maybeRecreateTables' ] );
}
/**
* Maybe register the `{$table_prefix}_actionscheduler_{$suffix}` tables with WordPress and create them if needed.
* Hooked into `plugins_loaded` action hook.
*
* @since 4.2.7
*
* @return void
*/
public function maybeRecreateTables() {
if ( ! is_admin() ) {
return;
}
if ( ! apply_filters( 'action_scheduler_enable_recreate_data_store', true ) ) {
return;
}
if (
! class_exists( 'ActionScheduler' ) ||
! class_exists( 'ActionScheduler_HybridStore' ) ||
! class_exists( 'ActionScheduler_StoreSchema' ) ||
! class_exists( 'ActionScheduler_LoggerSchema' )
) {
return;
}
$store = \ActionScheduler::store();
if ( ! is_a( $store, 'ActionScheduler_HybridStore' ) ) {
$store = new \ActionScheduler_HybridStore();
}
$tableList = [
'actionscheduler_actions',
'actionscheduler_logs',
'actionscheduler_groups',
'actionscheduler_claims',
];
foreach ( $tableList as $tableName ) {
if ( ! aioseo()->core->db->tableExists( $tableName ) ) {
add_action( 'action_scheduler/created_table', [ $store, 'set_autoincrement' ], 10, 2 );
$storeSchema = new \ActionScheduler_StoreSchema();
$loggerSchema = new \ActionScheduler_LoggerSchema();
$storeSchema->register_tables( true );
$loggerSchema->register_tables( true );
remove_action( 'action_scheduler/created_table', [ $store, 'set_autoincrement' ] );
break;
}
}
}
/**
* Cleans up the Action Scheduler tables after one of our actions completes.
* Hooked into `action_scheduler_after_execute` action hook.
*
* @since 4.0.10
*
* @param int $actionId The action ID processed.
* @param \ActionScheduler_Action $action Class instance.
* @return void
*/
public function cleanup( $actionId, $action = null ) {
if (
// Bail if this isn't one of our actions or if we're in a dev environment.
'aioseo' !== $action->get_group() ||
( defined( 'WP_ENVIRONMENT_TYPE' ) && 'development' === WP_ENVIRONMENT_TYPE ) ||
// Bail if the tables don't exist.
! aioseo()->core->db->tableExists( 'actionscheduler_actions' ) ||
! aioseo()->core->db->tableExists( 'actionscheduler_groups' ) ||
// Bail if it hasn't been long enough since the last cleanup.
aioseo()->core->cache->get( 'action_scheduler_log_cleanup' )
) {
return;
}
$prefix = aioseo()->core->db->db->prefix;
// Clean up logs associated with entries in the actions table.
aioseo()->core->db->execute(
"DELETE al FROM {$prefix}actionscheduler_logs as al
JOIN {$prefix}actionscheduler_actions as aa on `aa`.`action_id` = `al`.`action_id`
LEFT JOIN {$prefix}actionscheduler_groups as ag on `ag`.`group_id` = `aa`.`group_id`
WHERE (
(`ag`.`slug` = '{$this->actionSchedulerGroup}' AND `aa`.`status` IN ('complete', 'failed', 'canceled'))
OR
(`aa`.`hook` LIKE 'aioseo_%' AND `aa`.`group_id` = 0 AND `aa`.`status` IN ('complete', 'failed', 'canceled'))
);"
);
// Clean up actions.
aioseo()->core->db->execute(
"DELETE aa FROM {$prefix}actionscheduler_actions as aa
LEFT JOIN {$prefix}actionscheduler_groups as ag on `ag`.`group_id` = `aa`.`group_id`
WHERE (
(`ag`.`slug` = '{$this->actionSchedulerGroup}' AND `aa`.`status` IN ('complete', 'failed', 'canceled'))
OR
(`aa`.`hook` LIKE 'aioseo_%' AND `aa`.`group_id` = 0 AND `aa`.`status` IN ('complete', 'failed', 'canceled'))
);"
);
// Set a transient to prevent this from running again for a while.
aioseo()->core->cache->update( 'action_scheduler_log_cleanup', true, DAY_IN_SECONDS );
}
/**
* Schedules a single action at a specific time in the future.
*
* @since 4.0.13
* @version 4.2.7
*
* @param string $actionName The action name.
* @param int $time The time to add to the current time.
* @param array $args Args passed down to the action.
* @param bool $forceSchedule Whether we should schedule a new action regardless of whether one is already set.
* @return boolean Whether the action was scheduled.
*/
public function scheduleSingle( $actionName, $time = 0, $args = [], $forceSchedule = false ) {
try {
if ( $forceSchedule || ! $this->isScheduled( $actionName, $args ) ) {
as_schedule_single_action( time() + $time, $actionName, $args, $this->actionSchedulerGroup );
return true;
}
} catch ( \RuntimeException $e ) {
// Nothing needs to happen.
}
return false;
}
/**
* Checks if a given action is already scheduled.
*
* @since 4.0.13
* @version 4.2.7
*
* @param string $actionName The action name.
* @param array $args Args passed down to the action.
* @return boolean Whether the action is already scheduled.
*/
public function isScheduled( $actionName, $args = [] ) {
$scheduledActions = $this->getScheduledActions();
$hooks = [];
foreach ( $scheduledActions as $action ) {
$hooks[] = $action->hook;
}
$isScheduled = in_array( $actionName, array_filter( $hooks ), true );
if ( empty( $args ) ) {
return $isScheduled;
}
// If there are arguments, we need to check if the action is scheduled with the same arguments.
if ( $isScheduled ) {
foreach ( $scheduledActions as $action ) {
if ( $action->hook === $actionName ) {
foreach ( $args as $k => $v ) {
if ( ! isset( $action->args[ $k ] ) || $action->args[ $k ] !== $v ) {
continue;
}
return true;
}
}
}
}
return false;
}
/**
* Returns all AIOSEO scheduled actions.
*
* @since 4.7.7
*
* @return array The scheduled actions.
*/
private function getScheduledActions() {
static $scheduledActions = null;
if ( null !== $scheduledActions ) {
return $scheduledActions;
}
$scheduledActions = aioseo()->core->db->start( 'actionscheduler_actions as aa' )
->select( 'aa.hook, aa.args' )
->join( 'actionscheduler_groups as ag', 'ag.group_id', 'aa.group_id' )
->where( 'ag.slug', $this->actionSchedulerGroup )
->whereIn( 'status', [ 'pending', 'in-progress', 'past-due' ] )
->run()
->result();
// Decode the args.
foreach ( $scheduledActions as $key => $action ) {
$scheduledActions[ $key ]->args = json_decode( $action->args, true );
}
return $scheduledActions;
}
/**
* Unschedule an action.
*
* @since 4.1.4
* @version 4.2.7
*
* @param string $actionName The action name to unschedule.
* @param array $args Args passed down to the action.
* @return void
*/
public function unschedule( $actionName, $args = [] ) {
try {
if ( as_next_scheduled_action( $actionName, $args ) ) {
as_unschedule_action( $actionName, $args, $this->actionSchedulerGroup );
}
} catch ( \Exception $e ) {
// Do nothing.
}
}
/**
* Schedules a recurring action.
*
* @since 4.1.5
* @version 4.2.7
*
* @param string $actionName The action name.
* @param int $time The seconds to add to the current time.
* @param int $interval The interval in seconds.
* @param array $args Args passed down to the action.
* @return boolean Whether the action was scheduled.
*/
public function scheduleRecurrent( $actionName, $time, $interval = 60, $args = [] ) {
try {
if ( ! $this->isScheduled( $actionName, $args ) ) {
as_schedule_recurring_action( time() + $time, $interval, $actionName, $args, $this->actionSchedulerGroup );
return true;
}
} catch ( \RuntimeException $e ) {
// Nothing needs to happen.
}
return false;
}
/**
* Schedule a single async action.
*
* @since 4.1.6
* @version 4.2.7
*
* @param string $actionName The name of the action.
* @param array $args Any relevant arguments.
* @return void
*/
public function scheduleAsync( $actionName, $args = [] ) {
try {
// Run the task immediately using an async action.
as_enqueue_async_action( $actionName, $args, $this->actionSchedulerGroup );
} catch ( \Exception $e ) {
// Do nothing.
}
}
} Helpers.php 0000644 00000043000 15154136225 0006660 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\WritingAssistant\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use AIOSEO\Plugin\Common\Models;
/**
* Helper functions.
*
* @since 4.7.4
*/
class Helpers {
/**
* Gets the data for vue.
*
* @since 4.7.4
*
* @return array An array of data.
*/
public function getStandaloneVueData() {
$keyword = Models\WritingAssistantPost::getKeyword( get_the_ID() );
return [
'postId' => get_the_ID(),
'report' => $keyword,
'keywordText' => ! empty( $keyword->keyword ) ? $keyword->keyword : '',
'contentAnalysis' => Models\WritingAssistantPost::getContentAnalysis( get_the_ID() ),
'seoBoost' => [
'isLoggedIn' => aioseo()->writingAssistant->seoBoost->isLoggedIn(),
'loginUrl' => aioseo()->writingAssistant->seoBoost->getLoginUrl(),
'createAccountUrl' => aioseo()->writingAssistant->seoBoost->getCreateAccountUrl(),
'userOptions' => aioseo()->writingAssistant->seoBoost->getUserOptions()
]
];
}
/**
* Gets the data for vue.
*
* @since 4.7.4
*
* @return array An array of data.
*/
public function getSettingsVueData() {
return [
'seoBoost' => [
'isLoggedIn' => aioseo()->writingAssistant->seoBoost->isLoggedIn(),
'loginUrl' => aioseo()->writingAssistant->seoBoost->getLoginUrl(),
'createAccountUrl' => aioseo()->writingAssistant->seoBoost->getCreateAccountUrl(),
'userOptions' => aioseo()->writingAssistant->seoBoost->getUserOptions(),
'countries' => $this->getCountries(),
'languages' => $this->getLanguages(),
'searchEngines' => $this->getSearchEngines()
]
];
}
/**
* Returns the list of countries.
*
* @since 4.7.7.1
* @version 4.8.3 Moved from SeoBoost/SeoBoost.php
*
* @return array The list of countries.
*/
private function getCountries() {
$countries = [
'AF' => __( 'Afghanistan', 'all-in-one-seo-pack' ),
'AL' => __( 'Albania', 'all-in-one-seo-pack' ),
'DZ' => __( 'Algeria', 'all-in-one-seo-pack' ),
'AS' => __( 'American Samoa', 'all-in-one-seo-pack' ),
'AD' => __( 'Andorra', 'all-in-one-seo-pack' ),
'AO' => __( 'Angola', 'all-in-one-seo-pack' ),
'AI' => __( 'Anguilla', 'all-in-one-seo-pack' ),
'AG' => __( 'Antigua & Barbuda', 'all-in-one-seo-pack' ),
'AR' => __( 'Argentina', 'all-in-one-seo-pack' ),
'AM' => __( 'Armenia', 'all-in-one-seo-pack' ),
'AU' => __( 'Australia', 'all-in-one-seo-pack' ),
'AT' => __( 'Austria', 'all-in-one-seo-pack' ),
'AZ' => __( 'Azerbaijan', 'all-in-one-seo-pack' ),
'BS' => __( 'Bahamas', 'all-in-one-seo-pack' ),
'BH' => __( 'Bahrain', 'all-in-one-seo-pack' ),
'BD' => __( 'Bangladesh', 'all-in-one-seo-pack' ),
'BY' => __( 'Belarus', 'all-in-one-seo-pack' ),
'BE' => __( 'Belgium', 'all-in-one-seo-pack' ),
'BZ' => __( 'Belize', 'all-in-one-seo-pack' ),
'BJ' => __( 'Benin', 'all-in-one-seo-pack' ),
'BT' => __( 'Bhutan', 'all-in-one-seo-pack' ),
'BO' => __( 'Bolivia', 'all-in-one-seo-pack' ),
'BA' => __( 'Bosnia & Herzegovina', 'all-in-one-seo-pack' ),
'BW' => __( 'Botswana', 'all-in-one-seo-pack' ),
'BR' => __( 'Brazil', 'all-in-one-seo-pack' ),
'VG' => __( 'British Virgin Islands', 'all-in-one-seo-pack' ),
'BN' => __( 'Brunei', 'all-in-one-seo-pack' ),
'BG' => __( 'Bulgaria', 'all-in-one-seo-pack' ),
'BF' => __( 'Burkina Faso', 'all-in-one-seo-pack' ),
'BI' => __( 'Burundi', 'all-in-one-seo-pack' ),
'KH' => __( 'Cambodia', 'all-in-one-seo-pack' ),
'CM' => __( 'Cameroon', 'all-in-one-seo-pack' ),
'CA' => __( 'Canada', 'all-in-one-seo-pack' ),
'CV' => __( 'Cape Verde', 'all-in-one-seo-pack' ),
'CF' => __( 'Central African Republic', 'all-in-one-seo-pack' ),
'TD' => __( 'Chad', 'all-in-one-seo-pack' ),
'CL' => __( 'Chile', 'all-in-one-seo-pack' ),
'CO' => __( 'Colombia', 'all-in-one-seo-pack' ),
'CG' => __( 'Congo - Brazzaville', 'all-in-one-seo-pack' ),
'CD' => __( 'Congo - Kinshasa', 'all-in-one-seo-pack' ),
'CK' => __( 'Cook Islands', 'all-in-one-seo-pack' ),
'CR' => __( 'Costa Rica', 'all-in-one-seo-pack' ),
'CI' => __( 'Côte d’Ivoire', 'all-in-one-seo-pack' ),
'HR' => __( 'Croatia', 'all-in-one-seo-pack' ),
'CU' => __( 'Cuba', 'all-in-one-seo-pack' ),
'CY' => __( 'Cyprus', 'all-in-one-seo-pack' ),
'CZ' => __( 'Czechia', 'all-in-one-seo-pack' ),
'DK' => __( 'Denmark', 'all-in-one-seo-pack' ),
'DJ' => __( 'Djibouti', 'all-in-one-seo-pack' ),
'DM' => __( 'Dominica', 'all-in-one-seo-pack' ),
'DO' => __( 'Dominican Republic', 'all-in-one-seo-pack' ),
'EC' => __( 'Ecuador', 'all-in-one-seo-pack' ),
'EG' => __( 'Egypt', 'all-in-one-seo-pack' ),
'SV' => __( 'El Salvador', 'all-in-one-seo-pack' ),
'EE' => __( 'Estonia', 'all-in-one-seo-pack' ),
'ET' => __( 'Ethiopia', 'all-in-one-seo-pack' ),
'FJ' => __( 'Fiji', 'all-in-one-seo-pack' ),
'FI' => __( 'Finland', 'all-in-one-seo-pack' ),
'FR' => __( 'France', 'all-in-one-seo-pack' ),
'GA' => __( 'Gabon', 'all-in-one-seo-pack' ),
'GM' => __( 'Gambia', 'all-in-one-seo-pack' ),
'GE' => __( 'Georgia', 'all-in-one-seo-pack' ),
'DE' => __( 'Germany', 'all-in-one-seo-pack' ),
'GH' => __( 'Ghana', 'all-in-one-seo-pack' ),
'GI' => __( 'Gibraltar', 'all-in-one-seo-pack' ),
'GR' => __( 'Greece', 'all-in-one-seo-pack' ),
'GL' => __( 'Greenland', 'all-in-one-seo-pack' ),
'GT' => __( 'Guatemala', 'all-in-one-seo-pack' ),
'GG' => __( 'Guernsey', 'all-in-one-seo-pack' ),
'GY' => __( 'Guyana', 'all-in-one-seo-pack' ),
'HT' => __( 'Haiti', 'all-in-one-seo-pack' ),
'HN' => __( 'Honduras', 'all-in-one-seo-pack' ),
'HK' => __( 'Hong Kong', 'all-in-one-seo-pack' ),
'HU' => __( 'Hungary', 'all-in-one-seo-pack' ),
'IS' => __( 'Iceland', 'all-in-one-seo-pack' ),
'IN' => __( 'India', 'all-in-one-seo-pack' ),
'ID' => __( 'Indonesia', 'all-in-one-seo-pack' ),
'IQ' => __( 'Iraq', 'all-in-one-seo-pack' ),
'IE' => __( 'Ireland', 'all-in-one-seo-pack' ),
'IM' => __( 'Isle of Man', 'all-in-one-seo-pack' ),
'IL' => __( 'Israel', 'all-in-one-seo-pack' ),
'IT' => __( 'Italy', 'all-in-one-seo-pack' ),
'JM' => __( 'Jamaica', 'all-in-one-seo-pack' ),
'JP' => __( 'Japan', 'all-in-one-seo-pack' ),
'JE' => __( 'Jersey', 'all-in-one-seo-pack' ),
'JO' => __( 'Jordan', 'all-in-one-seo-pack' ),
'KZ' => __( 'Kazakhstan', 'all-in-one-seo-pack' ),
'KE' => __( 'Kenya', 'all-in-one-seo-pack' ),
'KI' => __( 'Kiribati', 'all-in-one-seo-pack' ),
'KW' => __( 'Kuwait', 'all-in-one-seo-pack' ),
'KG' => __( 'Kyrgyzstan', 'all-in-one-seo-pack' ),
'LA' => __( 'Laos', 'all-in-one-seo-pack' ),
'LV' => __( 'Latvia', 'all-in-one-seo-pack' ),
'LB' => __( 'Lebanon', 'all-in-one-seo-pack' ),
'LS' => __( 'Lesotho', 'all-in-one-seo-pack' ),
'LY' => __( 'Libya', 'all-in-one-seo-pack' ),
'LI' => __( 'Liechtenstein', 'all-in-one-seo-pack' ),
'LT' => __( 'Lithuania', 'all-in-one-seo-pack' ),
'LU' => __( 'Luxembourg', 'all-in-one-seo-pack' ),
'MG' => __( 'Madagascar', 'all-in-one-seo-pack' ),
'MW' => __( 'Malawi', 'all-in-one-seo-pack' ),
'MY' => __( 'Malaysia', 'all-in-one-seo-pack' ),
'MV' => __( 'Maldives', 'all-in-one-seo-pack' ),
'ML' => __( 'Mali', 'all-in-one-seo-pack' ),
'MT' => __( 'Malta', 'all-in-one-seo-pack' ),
'MU' => __( 'Mauritius', 'all-in-one-seo-pack' ),
'MX' => __( 'Mexico', 'all-in-one-seo-pack' ),
'FM' => __( 'Micronesia', 'all-in-one-seo-pack' ),
'MD' => __( 'Moldova', 'all-in-one-seo-pack' ),
'MN' => __( 'Mongolia', 'all-in-one-seo-pack' ),
'ME' => __( 'Montenegro', 'all-in-one-seo-pack' ),
'MS' => __( 'Montserrat', 'all-in-one-seo-pack' ),
'MA' => __( 'Morocco', 'all-in-one-seo-pack' ),
'MZ' => __( 'Mozambique', 'all-in-one-seo-pack' ),
'MM' => __( 'Myanmar (Burma)', 'all-in-one-seo-pack' ),
'NA' => __( 'Namibia', 'all-in-one-seo-pack' ),
'NR' => __( 'Nauru', 'all-in-one-seo-pack' ),
'NP' => __( 'Nepal', 'all-in-one-seo-pack' ),
'NL' => __( 'Netherlands', 'all-in-one-seo-pack' ),
'NZ' => __( 'New Zealand', 'all-in-one-seo-pack' ),
'NI' => __( 'Nicaragua', 'all-in-one-seo-pack' ),
'NE' => __( 'Niger', 'all-in-one-seo-pack' ),
'NG' => __( 'Nigeria', 'all-in-one-seo-pack' ),
'NU' => __( 'Niue', 'all-in-one-seo-pack' ),
'MK' => __( 'North Macedonia', 'all-in-one-seo-pack' ),
'NO' => __( 'Norway', 'all-in-one-seo-pack' ),
'OM' => __( 'Oman', 'all-in-one-seo-pack' ),
'PK' => __( 'Pakistan', 'all-in-one-seo-pack' ),
'PS' => __( 'Palestine', 'all-in-one-seo-pack' ),
'PA' => __( 'Panama', 'all-in-one-seo-pack' ),
'PG' => __( 'Papua New Guinea', 'all-in-one-seo-pack' ),
'PY' => __( 'Paraguay', 'all-in-one-seo-pack' ),
'PE' => __( 'Peru', 'all-in-one-seo-pack' ),
'PH' => __( 'Philippines', 'all-in-one-seo-pack' ),
'PN' => __( 'Pitcairn Islands', 'all-in-one-seo-pack' ),
'PL' => __( 'Poland', 'all-in-one-seo-pack' ),
'PT' => __( 'Portugal', 'all-in-one-seo-pack' ),
'PR' => __( 'Puerto Rico', 'all-in-one-seo-pack' ),
'QA' => __( 'Qatar', 'all-in-one-seo-pack' ),
'RO' => __( 'Romania', 'all-in-one-seo-pack' ),
'RU' => __( 'Russia', 'all-in-one-seo-pack' ),
'RW' => __( 'Rwanda', 'all-in-one-seo-pack' ),
'WS' => __( 'Samoa', 'all-in-one-seo-pack' ),
'SM' => __( 'San Marino', 'all-in-one-seo-pack' ),
'ST' => __( 'São Tomé & Príncipe', 'all-in-one-seo-pack' ),
'SA' => __( 'Saudi Arabia', 'all-in-one-seo-pack' ),
'SN' => __( 'Senegal', 'all-in-one-seo-pack' ),
'RS' => __( 'Serbia', 'all-in-one-seo-pack' ),
'SC' => __( 'Seychelles', 'all-in-one-seo-pack' ),
'SL' => __( 'Sierra Leone', 'all-in-one-seo-pack' ),
'SG' => __( 'Singapore', 'all-in-one-seo-pack' ),
'SK' => __( 'Slovakia', 'all-in-one-seo-pack' ),
'SI' => __( 'Slovenia', 'all-in-one-seo-pack' ),
'SB' => __( 'Solomon Islands', 'all-in-one-seo-pack' ),
'SO' => __( 'Somalia', 'all-in-one-seo-pack' ),
'ZA' => __( 'South Africa', 'all-in-one-seo-pack' ),
'KR' => __( 'South Korea', 'all-in-one-seo-pack' ),
'ES' => __( 'Spain', 'all-in-one-seo-pack' ),
'LK' => __( 'Sri Lanka', 'all-in-one-seo-pack' ),
'SH' => __( 'St. Helena', 'all-in-one-seo-pack' ),
'VC' => __( 'St. Vincent & Grenadines', 'all-in-one-seo-pack' ),
'SR' => __( 'Suriname', 'all-in-one-seo-pack' ),
'SE' => __( 'Sweden', 'all-in-one-seo-pack' ),
'CH' => __( 'Switzerland', 'all-in-one-seo-pack' ),
'TW' => __( 'Taiwan', 'all-in-one-seo-pack' ),
'TJ' => __( 'Tajikistan', 'all-in-one-seo-pack' ),
'TZ' => __( 'Tanzania', 'all-in-one-seo-pack' ),
'TH' => __( 'Thailand', 'all-in-one-seo-pack' ),
'TL' => __( 'Timor-Leste', 'all-in-one-seo-pack' ),
'TG' => __( 'Togo', 'all-in-one-seo-pack' ),
'TO' => __( 'Tonga', 'all-in-one-seo-pack' ),
'TT' => __( 'Trinidad & Tobago', 'all-in-one-seo-pack' ),
'TN' => __( 'Tunisia', 'all-in-one-seo-pack' ),
'TR' => __( 'Turkey', 'all-in-one-seo-pack' ),
'TM' => __( 'Turkmenistan', 'all-in-one-seo-pack' ),
'VI' => __( 'U.S. Virgin Islands', 'all-in-one-seo-pack' ),
'UG' => __( 'Uganda', 'all-in-one-seo-pack' ),
'UA' => __( 'Ukraine', 'all-in-one-seo-pack' ),
'AE' => __( 'United Arab Emirates', 'all-in-one-seo-pack' ),
'GB' => __( 'United Kingdom', 'all-in-one-seo-pack' ),
'US' => __( 'United States', 'all-in-one-seo-pack' ),
'UY' => __( 'Uruguay', 'all-in-one-seo-pack' ),
'UZ' => __( 'Uzbekistan', 'all-in-one-seo-pack' ),
'VU' => __( 'Vanuatu', 'all-in-one-seo-pack' ),
'VE' => __( 'Venezuela', 'all-in-one-seo-pack' ),
'VN' => __( 'Vietnam', 'all-in-one-seo-pack' ),
'ZM' => __( 'Zambia', 'all-in-one-seo-pack' ),
'ZW' => __( 'Zimbabwe', 'all-in-one-seo-pack' )
];
return $countries;
}
/**
* Returns the list of languages.
*
* @since 4.7.7.1
* @version 4.8.3 Moved from SeoBoost/SeoBoost.php
*
* @return array The list of languages.
*/
private function getLanguages() {
$languages = [
'ca' => __( 'Catalan', 'all-in-one-seo-pack' ),
'da' => __( 'Danish', 'all-in-one-seo-pack' ),
'nl' => __( 'Dutch', 'all-in-one-seo-pack' ),
'en' => __( 'English', 'all-in-one-seo-pack' ),
'fr' => __( 'French', 'all-in-one-seo-pack' ),
'de' => __( 'German', 'all-in-one-seo-pack' ),
'id' => __( 'Indonesian', 'all-in-one-seo-pack' ),
'it' => __( 'Italian', 'all-in-one-seo-pack' ),
'no' => __( 'Norwegian', 'all-in-one-seo-pack' ),
'pt' => __( 'Portuguese', 'all-in-one-seo-pack' ),
'ro' => __( 'Romanian', 'all-in-one-seo-pack' ),
'es' => __( 'Spanish', 'all-in-one-seo-pack' ),
'sv' => __( 'Swedish', 'all-in-one-seo-pack' ),
'tr' => __( 'Turkish', 'all-in-one-seo-pack' )
];
return $languages;
}
/**
* Returns the list of search engines.
*
* @since 4.7.7.1
* @version 4.8.3 Moved from SeoBoost/SeoBoost.php
*
* @return array The list of search engines.
*/
private function getSearchEngines() {
$searchEngines = [
'AF' => 'google.com.af',
'AL' => 'google.al',
'DZ' => 'google.dz',
'AS' => 'google.as',
'AD' => 'google.ad',
'AO' => 'google.it.ao',
'AI' => 'google.com.ai',
'AG' => 'google.com.ag',
'AR' => 'google.com.ar',
'AM' => 'google.am',
'AU' => 'google.com.au',
'AT' => 'google.at',
'AZ' => 'google.az',
'BS' => 'google.bs',
'BH' => 'google.com.bh',
'BD' => 'google.com.bd',
'BY' => 'google.com.by',
'BE' => 'google.be',
'BZ' => 'google.com.bz',
'BJ' => 'google.bj',
'BT' => 'google.bt',
'BO' => 'google.com.bo',
'BA' => 'google.ba',
'BW' => 'google.co.bw',
'BR' => 'google.com.br',
'VG' => 'google.vg',
'BN' => 'google.com.bn',
'BG' => 'google.bg',
'BF' => 'google.bf',
'BI' => 'google.bi',
'KH' => 'google.com.kh',
'CM' => 'google.cm',
'CA' => 'google.ca',
'CV' => 'google.cv',
'CF' => 'google.cf',
'TD' => 'google.td',
'CL' => 'google.cl',
'CO' => 'google.com.co',
'CG' => 'google.cg',
'CD' => 'google.cd',
'CK' => 'google.co.ck',
'CR' => 'google.co.cr',
'CI' => 'google.ci',
'HR' => 'google.hr',
'CU' => 'google.com.cu',
'CY' => 'google.com.cy',
'CZ' => 'google.cz',
'DK' => 'google.dk',
'DJ' => 'google.dj',
'DM' => 'google.dm',
'DO' => 'google.com.do',
'EC' => 'google.com.ec',
'EG' => 'google.com.eg',
'SV' => 'google.com.sv',
'EE' => 'google.ee',
'ET' => 'google.com.et',
'FJ' => 'google.com.fj',
'FI' => 'google.fi',
'FR' => 'google.fr',
'GA' => 'google.ga',
'GM' => 'google.gm',
'GE' => 'google.ge',
'DE' => 'google.de',
'GH' => 'google.com.gh',
'GI' => 'google.com.gi',
'GR' => 'google.gr',
'GL' => 'google.gl',
'GT' => 'google.com.gt',
'GG' => 'google.gg',
'GY' => 'google.gy',
'HT' => 'google.ht',
'HN' => 'google.hn',
'HK' => 'google.com.hk',
'HU' => 'google.hu',
'IS' => 'google.is',
'IN' => 'google.co.in',
'ID' => 'google.co.id',
'IQ' => 'google.iq',
'IE' => 'google.ie',
'IM' => 'google.co.im',
'IL' => 'google.co.il',
'IT' => 'google.it',
'JM' => 'google.com.jm',
'JP' => 'google.co.jp',
'JE' => 'google.co.je',
'JO' => 'google.jo',
'KZ' => 'google.kz',
'KE' => 'google.co.ke',
'KI' => 'google.ki',
'KW' => 'google.com.kw',
'KG' => 'google.com.kg',
'LA' => 'google.la',
'LV' => 'google.lv',
'LB' => 'google.com.lb',
'LS' => 'google.co.ls',
'LY' => 'google.com.ly',
'LI' => 'google.li',
'LT' => 'google.lt',
'LU' => 'google.lu',
'MG' => 'google.mg',
'MW' => 'google.mw',
'MY' => 'google.com.my',
'MV' => 'google.mv',
'ML' => 'google.ml',
'MT' => 'google.com.mt',
'MU' => 'google.mu',
'MX' => 'google.com.mx',
'FM' => 'google.fm',
'MD' => 'google.md',
'MN' => 'google.mn',
'ME' => 'google.me',
'MS' => 'google.ms',
'MA' => 'google.co.ma',
'MZ' => 'google.co.mz',
'MM' => 'google.com.mm',
'NA' => 'google.com.na',
'NR' => 'google.nr',
'NP' => 'google.com.np',
'NL' => 'google.nl',
'NZ' => 'google.co.nz',
'NI' => 'google.com.ni',
'NE' => 'google.ne',
'NG' => 'google.com.ng',
'NU' => 'google.nu',
'MK' => 'google.mk',
'NO' => 'google.no',
'OM' => 'google.com.om',
'PK' => 'google.com.pk',
'PS' => 'google.ps',
'PA' => 'google.com.pa',
'PG' => 'google.com.pg',
'PY' => 'google.com.py',
'PE' => 'google.com.pe',
'PH' => 'google.com.ph',
'PN' => 'google.pn',
'PL' => 'google.pl',
'PT' => 'google.pt',
'PR' => 'google.com.pr',
'QA' => 'google.com.qa',
'RO' => 'google.ro',
'RU' => 'google.ru',
'RW' => 'google.rw',
'WS' => 'google.as',
'SM' => 'google.sm',
'ST' => 'google.st',
'SA' => 'google.com.sa',
'SN' => 'google.sn',
'RS' => 'google.rs',
'SC' => 'google.sc',
'SL' => 'google.com.sl',
'SG' => 'google.com.sg',
'SK' => 'google.sk',
'SI' => 'google.si',
'SB' => 'google.com.sb',
'SO' => 'google.so',
'ZA' => 'google.co.za',
'KR' => 'google.co.kr',
'ES' => 'google.es',
'LK' => 'google.lk',
'SH' => 'google.sh',
'VC' => 'google.com.vc',
'SR' => 'google.sr',
'SE' => 'google.se',
'CH' => 'google.ch',
'TW' => 'google.com.tw',
'TJ' => 'google.com.tj',
'TZ' => 'google.co.tz',
'TH' => 'google.co.th',
'TL' => 'google.tl',
'TG' => 'google.tg',
'TO' => 'google.to',
'TT' => 'google.tt',
'TN' => 'google.tn',
'TR' => 'google.com.tr',
'TM' => 'google.tm',
'VI' => 'google.co.vi',
'UG' => 'google.co.ug',
'UA' => 'google.com.ua',
'AE' => 'google.ae',
'GB' => 'google.co.uk',
'US' => 'google.com',
'UY' => 'google.com.uy',
'UZ' => 'google.co.uz',
'VU' => 'google.vu',
'VE' => 'google.co.ve',
'VN' => 'google.com.vn',
'ZM' => 'google.co.zm',
'ZW' => 'google.co.zw'
];
return $searchEngines;
}
}PluginUpgraderSilentAjax.php 0000644 00000023674 15154136225 0012210 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WP_Error;
/** \WP_Upgrader class */
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
/** \Plugin_Upgrader class */
require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
/**
* In WP 5.3 a PHP 5.6 splat operator (...$args) was added to \WP_Upgrader_Skin::feedback().
* We need to remove all calls to *Skin::feedback() method, as we can't override it in own Skins
* without breaking support for PHP 5.3-5.5.
*
* @internal Please do not use this class outside of core AIOSEO development. May be removed at any time.
*
* @since 1.5.6.1
*/
class PluginUpgraderSilentAjax extends \Plugin_Upgrader {
/**
* An array of links to install the plugins from.
*
* @since 4.0.0
*
* @var array
*/
public $pluginLinks = [
'brokenLinkChecker' => 'https://downloads.wordpress.org/plugin/broken-link-checker-seo.zip',
'optinMonster' => 'https://downloads.wordpress.org/plugin/optinmonster.zip',
'wpForms' => 'https://downloads.wordpress.org/plugin/wpforms-lite.zip',
'miLite' => 'https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.zip',
'emLite' => 'https://downloads.wordpress.org/plugin/google-analytics-dashboard-for-wp.zip',
'wpMail' => 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip',
'rafflePress' => 'https://downloads.wordpress.org/plugin/rafflepress.zip',
'seedProd' => 'https://downloads.wordpress.org/plugin/coming-soon.zip',
'trustPulse' => 'https://downloads.wordpress.org/plugin/trustpulse-api.zip',
'instagramFeed' => 'https://downloads.wordpress.org/plugin/instagram-feed.zip',
'facebookFeed' => 'https://downloads.wordpress.org/plugin/custom-facebook-feed.zip',
'twitterFeed' => 'https://downloads.wordpress.org/plugin/custom-twitter-feeds.zip',
'youTubeFeed' => 'https://downloads.wordpress.org/plugin/feeds-for-youtube.zip',
'pushEngage' => 'https://downloads.wordpress.org/plugins/pushengage.zip',
'sugarCalendar' => 'https://downloads.wordpress.org/plugins/sugar-calendar-lite.zip',
'wpSimplePay' => 'https://downloads.wordpress.org/plugins/stripe.zip',
'easyDigitalDownloads' => 'https://downloads.wordpress.org/plugins/easy-digital-downloads.zip',
'wpcode' => 'https://downloads.wordpress.org/plugin/insert-headers-and-footers.zip',
'searchWp' => '',
'affiliateWp' => '',
'charitable' => 'https://downloads.wordpress.org/plugin/charitable.zip',
'duplicator' => 'https://downloads.wordpress.org/plugin/duplicator.zip'
];
/**
* An array of links to install the plugins from wordpress.org.
*
* @since 4.0.0
*
* @var array
*/
public $wpPluginLinks = [
'brokenLinkChecker' => 'https://wordpress.org/plugins/broken-link-checker-seo/',
'optinMonster' => 'https://wordpress.org/plugin/optinmonster/',
'wpForms' => 'https://wordpress.org/plugin/wpforms-lite/',
'miLite' => 'https://wordpress.org/plugin/google-analytics-for-wordpress/',
'emLite' => 'https://wordpress.org/plugin/google-analytics-dashboard-for-wp/',
'wpMail' => 'https://wordpress.org/plugin/wp-mail-smtp/',
'rafflePress' => 'https://wordpress.org/plugin/rafflepress/',
'seedProd' => 'https://wordpress.org/plugin/coming-soon/',
'trustPulse' => 'https://wordpress.org/plugin/trustpulse-api/',
'instagramFeed' => 'https://wordpress.org/plugin/instagram-feed/',
'facebookFeed' => 'https://wordpress.org/plugin/custom-facebook-feed/',
'twitterFeed' => 'https://wordpress.org/plugin/custom-twitter-feeds/',
'youTubeFeed' => 'https://wordpress.org/plugin/feeds-for-youtube/',
'pushEngage' => 'https://wordpress.org/plugins/pushengage/',
'sugarCalendar' => 'https://wordpress.org/plugins/sugar-calendar-lite/',
'wpSimplePay' => 'https://wordpress.org/plugins/stripe/',
'searchWp' => 'https://searchwp.com/',
'affiliateWp' => 'https://affiliatewp.com/',
'wpcode' => 'https://wordpress.org/plugins/insert-headers-and-footers/',
'charitable' => 'https://wordpress.org/plugins/charitable/',
'duplicator' => 'https://wordpress.org/plugins/duplicator/'
];
/**
* An array of slugs to check if plugins are activated.
*
* @since 4.0.0
*
* @var array
*/
public $pluginSlugs = [
'brokenLinkChecker' => 'broken-link-checker-seo/aioseo-broken-link-checker.php',
'optinMonster' => 'optinmonster/optin-monster-wp-api.php',
'wpForms' => 'wpforms-lite/wpforms.php',
'wpFormsPro' => 'wpforms/wpforms.php',
'miLite' => 'google-analytics-for-wordpress/googleanalytics.php',
'miPro' => 'google-analytics-premium/googleanalytics-premium.php',
'emLite' => 'google-analytics-dashboard-for-wp/gadwp.php',
'emPro' => 'exactmetrics-premium/exactmetrics-premium.php',
'wpMail' => 'wp-mail-smtp/wp_mail_smtp.php',
'wpMailPro' => 'wp-mail-smtp-pro/wp_mail_smtp.php',
'rafflePress' => 'rafflepress/rafflepress.php',
'rafflePressPro' => 'rafflepress-pro/rafflepress-pro.php',
'seedProd' => 'coming-soon/coming-soon.php',
'seedProdPro' => 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php',
'trustPulse' => 'trustpulse-api/trustpulse.php',
'instagramFeed' => 'instagram-feed/instagram-feed.php',
'instagramFeedPro' => 'instagram-feed-pro/instagram-feed.php',
'facebookFeed' => 'custom-facebook-feed/custom-facebook-feed.php',
'facebookFeedPro' => 'custom-facebook-feed-pro/custom-facebook-feed.php',
'twitterFeed' => 'custom-twitter-feeds/custom-twitter-feed.php',
'twitterFeedPro' => 'custom-twitter-feeds-pro/custom-twitter-feed.php',
'youTubeFeed' => 'feeds-for-youtube/youtube-feed.php',
'youTubeFeedPro' => 'youtube-feed-pro/youtube-feed.php',
'pushEngage' => 'pushengage/main.php',
'sugarCalendar' => 'sugar-calendar-lite/sugar-calendar-lite.php',
'sugarCalendarPro' => 'sugar-calendar/sugar-calendar.php',
'wpSimplePay' => 'stripe/stripe-checkout.php',
'wpSimplePayPro' => 'wp-simple-pay-pro-3/simple-pay.php',
'easyDigitalDownloads' => 'easy-digital-downloads/easy-digital-downloads.php',
'easyDigitalDownloadsPro' => 'easy-digital-downloads-pro/easy-digital-downloads.php',
'searchWp' => 'searchwp/index.php',
'affiliateWp' => 'affiliate-wp/affiliate-wp.php',
'wpcode' => 'insert-headers-and-footers/ihaf.php',
'wpcodePro' => 'wpcode-premium/wpcode.php',
'charitable' => 'charitable/charitable.php',
'duplicator' => 'duplicator/duplicator.php'
];
/**
* An array of links for admin settings.
*
* @since 4.0.0
*
* @var array
*/
public $pluginAdminUrls = [
'brokenLinkChecker' => 'admin.php?page=broken-link-checker#/settings',
'optinMonster' => 'admin.php?page=optin-monster-api-settings',
'wpForms' => 'admin.php?page=wpforms-settings',
'wpFormsPro' => 'admin.php?page=wpforms-settings',
'miLite' => 'admin.php?page=monsterinsights_settings#/',
'miPro' => 'admin.php?page=monsterinsights_settings#/',
'emLite' => 'admin.php?page=exactmetrics_settings#/',
'emPro' => 'admin.php?page=exactmetrics_settings#/',
'wpMail' => 'admin.php?page=wp-mail-smtp',
'wpMailPro' => 'admin.php?page=wp-mail-smtp',
'seedProd' => 'admin.php?page=seedprod_lite',
'seedProdPro' => 'admin.php?page=seedprod_pro',
'rafflePress' => 'admin.php?page=rafflepress_lite#/settings',
'rafflePressPro' => 'admin.php?page=rafflepress_pro#/settings',
'trustPulse' => 'admin.php?page=trustpulse',
'instagramFeed' => 'admin.php?page=sb-instagram-feed',
'instagramFeedPro' => 'admin.php?page=sb-instagram-feed',
'facebookFeed' => 'admin.php?page=cff-top',
'facebookFeedPro' => 'admin.php?page=cff-top',
'twitterFeed' => 'admin.php?page=ctf-settings',
'twitterFeedPro' => 'admin.php?page=ctf-settings',
'youTubeFeed' => 'admin.php?page=youtube-feed-settings',
'youTubeFeedPro' => 'admin.php?page=youtube-feed-settings',
'pushEngage' => 'admin.php?page=pushengage',
'sugarCalendar' => 'admin.php?page=sugar-calendar',
'sugarCalendarPro' => 'admin.php?page=sugar-calendar',
'wpSimplePay' => 'edit.php?post_type=simple-pay',
'wpSimplePayPro' => 'edit.php?post_type=simple-pay',
'easyDigitalDownloads' => 'edit.php?post_type=download&page=edd-settings',
'easyDigitalDownloadsPro' => 'edit.php?post_type=download&page=edd-settings',
'searchWp' => 'options-general.php?page=searchwp',
'affiliateWp' => 'admin.php?page=affiliate-wp',
'wpcode' => 'admin.php?page=wpcode',
'wpcodePro' => 'admin.php?page=wpcode',
'charitable' => 'admin.php?page=charitable-settings',
'duplicator' => 'admin.php?page=duplicator-settings'
];
/**
* An array of slugs that work in the network admin.
*
* @since 4.2.8
*
* @var array
*/
public $hasNetworkAdmin = [
'miLite' => 'admin.php?page=monsterinsights_network',
'miPro' => 'admin.php?page=monsterinsights_network',
'emLite' => 'admin.php?page=exactmetrics_network',
'emPro' => 'admin.php?page=exactmetrics_network',
'wpMail' => 'admin.php?page=wp-mail-smtp',
'wpMailPro' => 'admin.php?page=wp-mail-smtp',
];
} PluginUpgraderSkin.php 0000644 00000003257 15154136225 0011045 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php';
/**
* Class PluginSilentUpgraderSkin.
*
* @internal Please do not use this class outside of core All in One SEO development. May be removed at any time.
*
* @since 4.0.0
*/
class PluginUpgraderSkin extends \WP_Upgrader_Skin {
/**
* Empty out the header of its HTML content and only check to see if it has
* been performed or not.
*
* @since 4.0.0
*/
public function header() {}
/**
* Empty out the footer of its HTML contents.
*
* @since 4.0.0
*/
public function footer() {}
/**
* Instead of outputting HTML for errors, just return them.
* Ajax request will just ignore it.
*
* @since 4.0.0
*
* @param array $errors Array of errors with the install process.
* @return void
*/
public function error( $errors ) {
if ( ! empty( $errors ) ) {
wp_send_json_error( $errors );
}
}
/**
* Empty out JavaScript output that calls function to decrement the update counts.
*
* @since 4.0.0
*
* @param string $type Type of update count to decrement.
*/
public function decrement_update_count( $type ) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, PSR1.Methods.CamelCapsMethodName.NotCamelCaps
/**
* @since 4.2.5
*
* @param string $feedback Message data.
* @param mixed ...$args Optional text replacements.
* @return void
*/
public function feedback( $feedback, ...$args ) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
} VueSettings.php 0000644 00000023223 15154136225 0007543 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Vue Settings for the user.
*
* @since 4.0.0
*/
class VueSettings {
/**
* The name to lookup the settings with.
*
* @since 4.0.0
*
* @var string
*/
private $settingsName = '';
/**
* The settings array.
*
* @since 4.0.0
*
* @var array
*/
private $settings = [];
/**
* All the default settings.
*
* @since 4.0.0
*
* @var array
*/
private $defaults = [
'showUpgradeBar' => true,
'showSetupWizard' => true,
'toggledCards' => [
'dashboardOverview' => true,
'dashboardSeoSetup' => true,
'dashboardSeoSiteScore' => true,
'dashboardNotifications' => true,
'dashboardSupport' => true,
'license' => true,
'webmasterTools' => true,
'enableBreadcrumbs' => true,
'breadcrumbSettings' => true,
'breadcrumbTemplates' => true,
'advanced' => true,
'accessControl' => true,
'rssContent' => true,
'generalSitemap' => true,
'generalSitemapSettings' => true,
'imageSitemap' => true,
'videoSitemap' => true,
'newsSitemap' => true,
'rssSitemap' => true,
'rssSitemapSettings' => true,
'rssAdditionalPages' => true,
'rssAdvancedSettings' => true,
'additionalPages' => true,
'advancedSettings' => true,
'videoSitemapSettings' => true,
'videoAdditionalPages' => true,
'videoAdvancedSettings' => true,
'videoEmbedSettings' => true,
'newsSitemapSettings' => true,
'newsAdditionalPages' => true,
'newsAdvancedSettings' => true,
'newsEmbedSettings' => true,
'socialProfiles' => true,
'facebook' => true,
'facebookHomePageSettings' => true,
'facebookAdvancedSettings' => true,
'twitter' => true,
'twitterHomePageSettings' => true,
'pinterest' => true,
'searchTitleSeparator' => true,
'searchHomePage' => true,
'searchSchema' => true,
'searchMediaAttachments' => true,
'searchAdvanced' => true,
'searchAdvancedCrawlCleanup' => true,
'searchCleanup' => true,
'authorArchives' => true,
'dateArchives' => true,
'searchArchives' => true,
'imageSeo' => true,
'completeSeoChecklist' => true,
'localBusinessInfo' => true,
'localBusinessOpeningHours' => true,
'locationsSettings' => true,
'advancedLocationsSettings' => true,
'localBusinessMapsApiKey' => true,
'localBusinessMapsSettings' => true,
'robotsEditor' => true,
'databaseTools' => true,
'htaccessEditor' => true,
'databaseToolsLogs' => true,
'systemStatusInfo' => true,
'addNewRedirection' => true,
'redirectSettings' => true,
'debug' => true,
'fullSiteRedirectsRelocate' => true,
'fullSiteRedirectsAliases' => true,
'fullSiteRedirectsCanonical' => true,
'fullSiteRedirectsHttpHeaders' => true,
'htmlSitemap' => true,
'htmlSitemapSettings' => true,
'htmlSitemapAdvancedSettings' => true,
'linkAssistantSettings' => true,
'domainActivations' => true,
'404Settings' => true,
'userProfiles' => true,
'queryArgLogs' => true,
'aiContentSettings' => true,
'writingAssistantSettings' => true,
'writingAssistantCta' => true
],
'toggledRadio' => [
'breadcrumbsShowMoreSeparators' => false,
'searchShowMoreSeparators' => false,
'overviewPostType' => 'post',
],
'dismissedAlerts' => [
'searchStatisticsContentRankings' => false,
'searchConsoleNotConnected' => false,
'searchConsoleSitemapErrors' => false
],
'internalTabs' => [
'authorArchives' => 'title-description',
'dateArchives' => 'title-description',
'searchArchives' => 'title-description',
'seoAuditChecklist' => 'all-items'
],
'tablePagination' => [
'networkDomains' => 20,
'redirects' => 20,
'redirectLogs' => 20,
'redirect404Logs' => 20,
'sitemapAdditionalPages' => 20,
'linkAssistantLinksReport' => 20,
'linkAssistantPostsReport' => 20,
'linkAssistantDomainsReport' => 20,
'searchStatisticsSeoStatistics' => 20,
'searchStatisticsKeywordRankings' => 20,
'searchStatisticsContentRankings' => 20,
'searchStatisticsPostDetailKeywords' => 20,
'searchStatisticsKrtKeywords' => 20,
'searchStatisticsKrtGroups' => 20,
'searchStatisticsKrtGroupsTableKeywords' => 10,
'searchStatisticsIndexStatus' => 20,
'queryArgs' => 20
],
'semrushCountry' => 'US'
];
/**
* The Construct method.
*
* @since 4.0.0
*
* @param string $settings An array of settings.
*/
public function __construct( $settings = '_aioseo_settings' ) {
$this->addDynamicDefaults();
$this->settingsName = $settings;
$dbSettings = get_user_meta( get_current_user_id(), $settings, true );
$this->settings = $dbSettings
? array_replace_recursive( $this->defaults, $dbSettings )
: $this->defaults;
}
/**
* Adds some defaults that are dynamically generated.
*
* @since 4.0.0
*
* @return void
*/
private function addDynamicDefaults() {
$postTypes = aioseo()->helpers->getPublicPostTypes( false, false, true, [ 'include' => [ 'buddypress' ] ] );
foreach ( $postTypes as $postType ) {
$this->defaults['toggledCards'][ $postType['name'] . 'SA' ] = true;
$this->defaults['internalTabs'][ $postType['name'] . 'SA' ] = 'title-description';
}
$taxonomies = aioseo()->helpers->getPublicTaxonomies( false, true );
foreach ( $taxonomies as $taxonomy ) {
$this->defaults['toggledCards'][ $taxonomy['name'] . 'SA' ] = true;
$this->defaults['internalTabs'][ $taxonomy['name'] . 'SA' ] = 'title-description';
}
$postTypes = aioseo()->helpers->getPublicPostTypes( false, true, true, [ 'include' => [ 'buddypress' ] ] );
foreach ( $postTypes as $postType ) {
$this->defaults['toggledCards'][ $postType['name'] . 'ArchiveArchives' ] = true;
$this->defaults['internalTabs'][ $postType['name'] . 'ArchiveArchives' ] = 'title-description';
}
// Check any addons for defaults.
$addonsDefaults = array_filter( aioseo()->addons->doAddonFunction( 'vueSettings', 'addDynamicDefaults' ) );
foreach ( $addonsDefaults as $addonDefaults ) {
$this->defaults = array_merge_recursive( $this->defaults, $addonDefaults );
}
}
/**
* Retrieves all settings.
*
* @since 4.0.0
*
* @return array An array of settings.
*/
public function all() {
return array_replace_recursive( $this->defaults, $this->settings );
}
/**
* Retrieve a setting or null if missing.
*
* @since 4.0.0
*
* @param string $name The name of the property that is missing on the class.
* @param array $arguments The arguments passed into the method.
* @return mixed The value from the settings or default/null.
*/
public function __call( $name, $arguments = [] ) {
$value = isset( $this->settings[ $name ] ) ? $this->settings[ $name ] : ( ! empty( $arguments[0] ) ? $arguments[0] : $this->getDefault( $name ) );
return $value;
}
/**
* Retrieve a setting or null if missing.
*
* @since 4.0.0
*
* @param string $name The name of the property that is missing on the class.
* @return mixed The value from the settings or default/null.
*/
public function __get( $name ) {
$value = isset( $this->settings[ $name ] ) ? $this->settings[ $name ] : $this->getDefault( $name );
return $value;
}
/**
* Sets the settings value and saves to the database.
*
* @since 4.0.0
*
* @param string $name The name of the settings.
* @param mixed $value The value to set.
* @return void
*/
public function __set( $name, $value ) {
$this->settings[ $name ] = $value;
$this->update();
}
/**
* Checks if an settings is set or returns null if not.
*
* @since 4.0.0
*
* @param string $name The name of the settings.
* @return mixed True or null.
*/
public function __isset( $name ) {
return isset( $this->settings[ $name ] ) ? false === empty( $this->settings[ $name ] ) : null;
}
/**
* Unsets the settings value and saves to the database.
*
* @since 4.0.0
*
* @param string $name The name of the settings.
* @return void
*/
public function __unset( $name ) {
if ( ! isset( $this->settings[ $name ] ) ) {
return;
}
unset( $this->settings[ $name ] );
$this->update();
}
/**
* Gets the default value for a setting.
*
* @since 4.0.0
*
* @param string $name The settings name.
* @return mixed The default value.
*/
public function getDefault( $name ) {
return isset( $this->defaults[ $name ] ) ? $this->defaults[ $name ] : null;
}
/**
* Updates the settings in the database.
*
* @since 4.0.0
*
* @return void
*/
public function update() {
update_user_meta( get_current_user_id(), $this->settingsName, $this->settings );
}
} Addons.php 0000644 00000071455 15154302615 0006503 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use AIOSEO\Plugin\Common\Utils;
/**
* Contains helper methods specific to the addons.
*
* @since 4.0.0
*/
class Addons {
/**
* Holds our list of loaded addons.
*
* @since 4.1.0
*
* @var array
*/
protected $loadedAddons = [];
/**
* The addons URL.
*
* @since 4.1.8
*
* @var string
*/
protected $addonsUrl = 'https://licensing-cdn.aioseo.com/keys/lite/all-in-one-seo-pack-pro.json';
/**
* The main Image SEO addon class.
*
* @since 4.4.2
*
* @var \AIOSEO\Plugin\Addon\ImageSeo\ImageSeo
*/
private $imageSeo = null;
/**
* The main Index Now addon class.
*
* @since 4.4.2
*
* @var \AIOSEO\Plugin\Addon\IndexNow\IndexNow
*/
private $indexNow = null;
/**
* The main Local Business addon class.
*
* @since 4.4.2
*
* @var \AIOSEO\Plugin\Addon\LocalBusiness\LocalBusiness
*/
private $localBusiness = null;
/**
* The main News Sitemap addon class.
*
* @since 4.4.2
*
* @var \AIOSEO\Plugin\Addon\NewsSitemap\NewsSitemap
*/
private $newsSitemap = null;
/**
* The main Redirects addon class.
*
* @since 4.4.2
*
* @var \AIOSEO\Plugin\Addon\Redirects\Redirects
*/
private $redirects = null;
/**
* The main REST API addon class.
*
* @since 4.4.2
*
* @var \AIOSEO\Plugin\Addon\RestApi\RestApi
*/
private $restApi = null;
/**
* The main Video Sitemap addon class.
*
* @since 4.4.2
*
* @var \AIOSEO\Plugin\Addon\VideoSitemap\VideoSitemap
*/
private $videoSitemap = null;
/**
* The main Link Assistant addon class.
*
* @since 4.4.2
*
* @var \AIOSEO\Plugin\Addon\LinkAssistant\LinkAssistant
*/
private $linkAssistant = null;
/**
* The main EEAT addon class.
*
* @since 4.5.4
*
* @var \AIOSEO\Plugin\Addon\LinkAssistant\LinkAssistant
*/
private $eeat = null;
/**
* Returns our addons.
*
* @since 4.0.0
*
* @param boolean $flushCache Whether or not to flush the cache.
* @return array An array of addon data.
*/
public function getAddons( $flushCache = false ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
$addons = aioseo()->core->cache->get( 'addons' );
$defaultAddons = $this->getDefaultAddons();
if ( null === $addons || $flushCache ) {
$response = aioseo()->helpers->wpRemoteGet( $this->getAddonsUrl() );
if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
$addons = json_decode( wp_remote_retrieve_body( $response ), true );
}
if ( ! $addons || ! empty( $addons->error ) ) {
$addons = $defaultAddons;
}
aioseo()->core->cache->update( 'addons', $addons );
}
// Convert the addons array to objects using JSON. This is essential because we have lots of addons that rely on this to be an object, and changing it to an array would break them.
$addons = json_decode( wp_json_encode( $addons ) );
$installedPlugins = array_keys( get_plugins() );
foreach ( $addons as $key => $addon ) {
if ( ! is_object( $addon ) ) {
continue;
}
$addons[ $key ]->basename = $this->getAddonBasename( $addon->sku );
$addons[ $key ]->installed = in_array( $this->getAddonBasename( $addon->sku ), $installedPlugins, true );
$addons[ $key ]->isActive = is_plugin_active( $addons[ $key ]->basename );
$addons[ $key ]->canInstall = $this->canInstall();
$addons[ $key ]->canActivate = $this->canActivate();
$addons[ $key ]->canUpdate = $this->canUpdate();
$addons[ $key ]->capability = $this->getManageCapability( $addon->sku );
$addons[ $key ]->minimumVersion = '0.0.0';
$addons[ $key ]->hasMinimumVersion = false;
$addons[ $key ]->featured = $this->setFeatured( $addon );
}
return $this->sortAddons( $addons );
}
/**
* Set the featured status for an addon.
*
* @since 4.6.9
*
* @param object $addon The addon.
* @return bool The featured status.
*/
protected function setFeatured( $addon ) {
$defaultAddons = $this->getDefaultAddons();
$featured = false;
// Find the addon in the default addons list and get the featured status.
foreach ( $defaultAddons as $defaultAddon ) {
if ( $addon->sku !== $defaultAddon['sku'] ) {
continue;
}
$featured = ! empty( $addon->featured )
? $addon->featured
: (
! empty( $defaultAddon['featured'] )
? $defaultAddon['featured']
: $featured
);
break;
}
return $featured;
}
/**
* Sort the addons by moving the featured ones to the top.
*
* @since 4.6.9
*
* @param array $addons The addons to sort.
* @return array The sorted addons.
*/
protected function sortAddons( $addons ) {
if ( ! is_array( $addons ) ) {
return $addons;
}
// Sort the addons by moving the featured ones to the top.
usort( $addons, function( $a, $b ) {
// Sort by featured value. It can be false, or numerical. If it's false, it will be moved to the bottom.
// If it's numerical, it will be moved to the top. Numbers will be sorted in descending order.
$featuredA = ! empty( $a->featured ) ? $a->featured : 0;
$featuredB = ! empty( $b->featured ) ? $b->featured : 0;
if ( $featuredA === $featuredB ) {
return 0;
}
return $featuredA > $featuredB ? -1 : 1;
} );
return $addons;
}
/**
* Returns the required capability to manage the addon.
*
* @since 4.1.3
*
* @param string $sku The addon sku.
* @return string The required capability.
*/
protected function getManageCapability( $sku ) {
$capability = apply_filters( 'aioseo_manage_seo', 'aioseo_manage_seo' );
switch ( $sku ) {
case 'aioseo-image-seo':
$capability = 'aioseo_search_appearance_settings';
break;
case 'aioseo-video-sitemap':
case 'aioseo-news-sitemap':
$capability = 'aioseo_sitemap_settings';
break;
case 'aioseo-redirects':
$capability = 'aioseo_redirects_settings';
break;
case 'aioseo-local-business':
$capability = 'aioseo_local_seo_settings';
break;
case 'aioseo-index-now':
$capability = 'aioseo_general_settings';
break;
}
return $capability;
}
/**
* Check to see if there are unlicensed addons installed and activated.
*
* @since 4.1.3
*
* @return boolean True if there are unlicensed addons, false if not.
*/
public function unlicensedAddons() {
$unlicensed = [
'addons' => [],
// Translators: 1 - Opening bold tag, 2 - Plugin short name ("AIOSEO"), 3 - "Pro", 4 - Closing bold tag.
'message' => sprintf(
// Translators: 1 - Opening HTML strong tag, 2 - The short plugin name ("AIOSEO"), 3 - "Pro", 4 - Closing HTML strong tag.
__( 'The following addons cannot be used, because they require %1$s%2$s %3$s%4$s to work:', 'all-in-one-seo-pack' ),
'<strong>',
AIOSEO_PLUGIN_SHORT_NAME,
'Pro',
'</strong>'
)
];
$addons = $this->getAddons();
foreach ( $addons as $addon ) {
if ( ! is_object( $addon ) ) {
continue;
}
if ( $addon->isActive ) {
$unlicensed['addons'][] = $addon;
}
}
return $unlicensed;
}
/**
* Get the data for a specific addon.
*
* We need this function to refresh the data of a given addon because installation links expire after one hour.
*
* @since 4.0.0
*
* @param string $sku The addon sku.
* @param bool $flushCache Whether or not to flush the cache.
* @return null|object The addon.
*/
public function getAddon( $sku, $flushCache = false ) {
$addon = null;
$allAddons = $this->getAddons( $flushCache );
foreach ( $allAddons as $a ) {
if ( $sku === $a->sku ) {
$addon = $a;
}
}
if ( ! $addon || ! empty( $addon->error ) ) {
$addon = $this->getDefaultAddon( $sku );
aioseo()->core->cache->update( 'addon_' . $sku, $addon, 10 * MINUTE_IN_SECONDS );
}
return $addon;
}
/**
* Checks if the specified addon is activated.
*
* @since 4.0.0
*
* @param string $sku The sku to check.
* @return string The addon basename.
*/
public function getAddonBasename( $sku ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
$plugins = get_plugins();
$keys = array_keys( $plugins );
foreach ( $keys as $key ) {
if ( preg_match( '|^' . $sku . '|', (string) $key ) ) {
return $key;
}
}
return $sku;
}
/**
* Returns an array of levels connected to an addon.
*
* @since 4.0.0
*
* @param string $addonName The addon name.
* @return array The array of levels.
*/
public function getAddonLevels( $addonName ) {
$addons = $this->getAddons();
foreach ( $addons as $addon ) {
if ( $addonName !== $addon->sku ) {
continue;
}
if ( ! isset( $addon->levels ) ) {
return [];
}
return $addon->levels;
}
return [];
}
/**
* Returns a list of addon SKUs.
*
* @since 4.5.6
*
* @return array The addon SKUs.
*/
public function getAddonSkus() {
$addons = $this->getAddons();
if ( empty( $addons ) ) {
return [];
}
return array_map( function( $addon ) {
return $addon->sku;
}, $addons );
}
/**
* Get the URL to get addons.
*
* @since 4.1.8
*
* @return string The URL.
*/
protected function getAddonsUrl() {
$url = $this->addonsUrl;
if ( defined( 'AIOSEO_ADDONS_URL' ) ) {
$url = AIOSEO_ADDONS_URL;
}
if ( defined( 'AIOSEO_INTERNAL_ADDONS' ) && AIOSEO_INTERNAL_ADDONS ) {
$url = add_query_arg( 'internal', true, $url );
}
return $url;
}
/**
* Installs and activates a given addon or plugin.
*
* @since 4.0.0
*
* @param string $name The addon name/sku.
* @param bool $network Whether or not we are in a network environment.
* @return bool Whether or not the installation was succesful.
*/
public function installAddon( $name, $network = false ) {
if ( ! $this->canInstall() ) {
return false;
}
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/template.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php';
require_once ABSPATH . 'wp-admin/includes/screen.php';
// Set the current screen to avoid undefined notices.
set_current_screen( 'toplevel_page_aioseo' );
// Prepare variables.
$url = esc_url_raw(
add_query_arg(
[
'page' => 'aioseo-settings',
],
admin_url( 'admin.php' )
)
);
// Do not allow WordPress to search/download translations, as this will break JS output.
remove_action( 'upgrader_process_complete', [ 'Language_Pack_Upgrader', 'async_upgrade' ], 20 );
// Create the plugin upgrader with our custom skin.
$installer = new Utils\PluginUpgraderSilentAjax( new Utils\PluginUpgraderSkin() );
// Activate the plugin silently.
$pluginUrl = ! empty( $installer->pluginSlugs[ $name ] ) ? $installer->pluginSlugs[ $name ] : $name;
$activated = activate_plugin( $pluginUrl, '', $network );
if ( ! is_wp_error( $activated ) ) {
return $name;
}
// Using output buffering to prevent the FTP form from being displayed in the screen.
ob_start();
$creds = request_filesystem_credentials( $url, '', false, false, null );
ob_end_clean();
// Check for file system permissions.
$fs = aioseo()->core->fs->noConflict();
$fs->init( $creds );
if ( false === $creds || ! $fs->isWpfsValid() ) {
return false;
}
// Error check.
if ( ! method_exists( $installer, 'install' ) ) {
return false;
}
$installLink = ! empty( $installer->pluginLinks[ $name ] ) ? $installer->pluginLinks[ $name ] : null;
// Check if this is an addon and if we have a download link.
if ( empty( $installLink ) ) {
$downloadUrl = aioseo()->addons->getDownloadUrl( $name );
if ( empty( $downloadUrl ) ) {
return false;
}
$installLink = $downloadUrl;
}
$installer->install( $installLink );
// Flush the cache and return the newly installed plugin basename.
wp_cache_flush();
$pluginBasename = $installer->plugin_info();
if ( ! $pluginBasename ) {
return false;
}
// Activate the plugin silently.
$activated = activate_plugin( $pluginBasename, '', $network );
if ( is_wp_error( $activated ) ) {
return false;
}
return $pluginBasename;
}
/**
* Determine if addons/plugins can be installed.
*
* @since 4.0.0
*
* @return bool True if yes, false if not.
*/
public function canInstall() {
if (
function_exists( 'wp_get_current_user' ) &&
is_user_logged_in() &&
! current_user_can( 'install_plugins' ) &&
! aioseo()->helpers->isDoingWpCli()
) {
return false;
}
// Determine whether file modifications are allowed.
if ( ! wp_is_file_mod_allowed( 'aioseo_can_install' ) ) {
return false;
}
return true;
}
/**
* Determine if addons/plugins can be updated.
*
* @since 4.1.6
*
* @return bool True if yes, false if not.
*/
public function canUpdate() {
if (
function_exists( 'wp_get_current_user' ) &&
is_user_logged_in() &&
! current_user_can( 'update_plugins' ) &&
! aioseo()->helpers->isDoingWpCli()
) {
return false;
}
// Determine whether file modifications are allowed.
if ( ! wp_is_file_mod_allowed( 'aioseo_can_update' ) ) {
return false;
}
return true;
}
/**
* Determine if addons/plugins can be activated.
*
* @since 4.1.3
*
* @return bool True if yes, false if not.
*/
public function canActivate() {
if (
function_exists( 'wp_get_current_user' ) &&
is_user_logged_in() &&
! current_user_can( 'activate_plugins' ) &&
! aioseo()->helpers->isDoingWpCli()
) {
return false;
}
return true;
}
/**
* Load an addon into aioseo.
*
* @since 4.1.0
*
* @param string $slug
* @param object $addon Addon class instance.
* @return void
*/
public function loadAddon( $slug, $addon ) {
$this->{$slug} = $addon;
$this->loadedAddons[] = $slug;
}
/**
* Return a loaded addon.
*
* @since 4.1.0
*
* @param string $slug
* @return object|null
*/
public function getLoadedAddon( $slug ) {
return isset( $this->{$slug} ) ? $this->{$slug} : null;
}
/**
* Returns loaded addons
*
* @since 4.1.0
*
* @return array
*/
public function getLoadedAddons() {
$loadedAddonsList = [];
if ( ! empty( $this->loadedAddons ) ) {
foreach ( $this->loadedAddons as $addonSlug ) {
$loadedAddonsList[ $addonSlug ] = $this->{$addonSlug};
}
}
return $loadedAddonsList;
}
/**
* Run a function through all addons that support it.
*
* @since 4.2.3
*
* @param string $class The class name.
* @param string $function The function name.
* @param array $args The args for the function.
* @return array The response from each addon.
*/
public function doAddonFunction( $class, $function, $args = [] ) {
$addonResponses = [];
foreach ( $this->getLoadedAddons() as $addonSlug => $addon ) {
if ( isset( $addon->$class ) && method_exists( $addon->$class, $function ) ) {
$addonResponses[ $addonSlug ] = call_user_func_array( [ $addon->$class, $function ], $args );
}
}
return $addonResponses;
}
/**
* Merges the data for Vue.
*
* @since 4.4.1
*
* @param array $data The data to merge.
* @param string $page The current page.
* @return array The data.
*/
public function getVueData( $data = [], $page = null ) {
foreach ( $this->getLoadedAddons() as $addon ) {
if ( isset( $addon->helpers ) && method_exists( $addon->helpers, 'getVueData' ) ) {
$data = array_merge( $data, $addon->helpers->getVueData( $data, $page ) );
}
}
return $data;
}
/**
* Retrieves a default addon with whatever information is needed if the API cannot be reached.
*
* @since 4.0.0
*
* @param string $sku The sku of the addon.
* @return array An array of addon data.
*/
public function getDefaultAddon( $sku ) {
$addons = $this->getDefaultAddons();
$addon = [];
foreach ( $addons as $a ) {
if ( $a['sku'] === $sku ) {
$addon = $a;
}
}
return $addon;
}
/**
* Retrieves a default list of addons if the API cannot be reached.
*
* @since 4.0.0
*
* @return array An array of addons.
*/
protected function getDefaultAddons() {
return json_decode( wp_json_encode( [
[
'sku' => 'aioseo-eeat',
'name' => 'Author SEO (E-E-A-T)',
'version' => '1.0.0',
'image' => null,
'icon' => 'svg-eeat',
'levels' => [
'plus',
'pro',
'elite',
],
'currentLevels' => [
'plus',
'pro',
'elite'
],
'requiresUpgrade' => true,
'description' => '<p>Optimize your site for Google\'s E-E-A-T ranking factor by proving your writer\'s expertise through author schema markup and new UI elements.</p>',
'descriptionVersion' => 0,
'productUrl' => 'https://aioseo.com/author-seo-eeat/',
'learnMoreUrl' => 'https://aioseo.com/author-seo-eeat/',
'manageUrl' => 'https://route#aioseo-search-appearance:author-seo',
'basename' => 'aioseo-eeat/aioseo-eeat.php',
'installed' => false,
'isActive' => false,
'canInstall' => false,
'canActivate' => false,
'canUpdate' => false,
'capability' => $this->getManageCapability( 'aioseo-eeat' ),
'minimumVersion' => '0.0.0',
'hasMinimumVersion' => false,
'featured' => 300
],
[
'sku' => 'aioseo-redirects',
'name' => 'Redirection Manager',
'version' => '1.0.0',
'image' => null,
'icon' => 'svg-redirect',
'levels' => [
'agency',
'business',
'pro',
'elite'
],
'currentLevels' => [
'pro',
'elite'
],
'requiresUpgrade' => true,
'description' => '<p>Our Redirection Manager allows you to easily create and manage redirects for your broken links to avoid confusing search engines and users, as well as losing valuable backlinks. It even automatically sends users and search engines from your old URLs to your new ones.</p>', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
'descriptionVersion' => 0,
'productUrl' => 'https://aioseo.com/features/redirection-manager/',
'learnMoreUrl' => 'https://aioseo.com/features/redirection-manager/',
'manageUrl' => 'https://route#aioseo-redirects:redirects',
'basename' => 'aioseo-redirects/aioseo-redirects.php',
'installed' => false,
'isActive' => false,
'canInstall' => false,
'canActivate' => false,
'canUpdate' => false,
'capability' => $this->getManageCapability( 'aioseo-redirects' ),
'minimumVersion' => '0.0.0',
'hasMinimumVersion' => false,
'featured' => 200
],
[
'sku' => 'aioseo-link-assistant',
'name' => 'Link Assistant',
'version' => '1.0.0',
'image' => null,
'icon' => 'svg-link-assistant',
'levels' => [
'agency',
'pro',
'elite'
],
'currentLevels' => [
'pro',
'elite'
],
'requiresUpgrade' => true,
'description' => '<p>Super-charge your SEO with Link Assistant! Get relevant suggestions for adding internal links to older content as well as finding any orphaned posts that have no internal links. Use our reporting feature to see all link suggestions or add them directly from any page or post.</p>', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
'descriptionVersion' => 0,
'productUrl' => 'https://aioseo.com/feature/internal-link-assistant/',
'learnMoreUrl' => 'https://aioseo.com/feature/internal-link-assistant/',
'manageUrl' => 'https://route#aioseo-link-assistant:overview',
'basename' => 'aioseo-link-assistant/aioseo-link-assistant.php',
'installed' => false,
'isActive' => false,
'canInstall' => false,
'canActivate' => false,
'canUpdate' => false,
'capability' => $this->getManageCapability( 'aioseo-link-assistant' ),
'minimumVersion' => '0.0.0',
'hasMinimumVersion' => false,
'featured' => 100
],
[
'sku' => 'aioseo-video-sitemap',
'name' => 'Video Sitemap',
'version' => '1.0.0',
'image' => null,
'icon' => 'svg-sitemaps-pro',
'levels' => [
'individual',
'business',
'agency',
'pro',
'elite'
],
'currentLevels' => [
'pro',
'elite'
],
'requiresUpgrade' => true,
'description' => '<p>The Video Sitemap works in much the same way as the XML Sitemap module, it generates an XML Sitemap specifically for video content on your site. Search engines use this information to display rich snippet information in search results.</p>', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
'descriptionVersion' => 0,
'productUrl' => 'https://aioseo.com/video-sitemap',
'learnMoreUrl' => 'https://aioseo.com/video-sitemap',
'manageUrl' => 'https://route#aioseo-sitemaps:video-sitemap',
'basename' => 'aioseo-video-sitemap/aioseo-video-sitemap.php',
'installed' => false,
'isActive' => false,
'canInstall' => false,
'canActivate' => false,
'canUpdate' => false,
'capability' => $this->getManageCapability( 'aioseo-video-sitemap' ),
'minimumVersion' => '0.0.0',
'hasMinimumVersion' => false
],
[
'sku' => 'aioseo-local-business',
'name' => 'Local Business SEO',
'version' => '1.0.0',
'image' => null,
'icon' => 'svg-local-business',
'levels' => [
'business',
'agency',
'plus',
'pro',
'elite'
],
'currentLevels' => [
'plus',
'pro',
'elite'
],
'requiresUpgrade' => true,
'description' => '<p>Local Business schema markup enables you to tell Google about your business, including your business name, address and phone number, opening hours and price range. This information may be displayed as a Knowledge Graph card or business carousel.</p>', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
'descriptionVersion' => 0,
'productUrl' => 'https://aioseo.com/local-business',
'learnMoreUrl' => 'https://aioseo.com/local-business',
'manageUrl' => 'https://route#aioseo-local-seo:locations',
'basename' => 'aioseo-local-business/aioseo-local-business.php',
'installed' => false,
'isActive' => false,
'canInstall' => false,
'canActivate' => false,
'canUpdate' => false,
'capability' => $this->getManageCapability( 'aioseo-local-business' ),
'minimumVersion' => '0.0.0',
'hasMinimumVersion' => false
],
[
'sku' => 'aioseo-news-sitemap',
'name' => 'News Sitemap',
'version' => '1.0.0',
'image' => null,
'icon' => 'svg-sitemaps-pro',
'levels' => [
'business',
'agency',
'pro',
'elite'
],
'currentLevels' => [
'pro',
'elite'
],
'requiresUpgrade' => true,
'description' => '<p>Our Google News Sitemap lets you control which content you submit to Google News and only contains articles that were published in the last 48 hours. In order to submit a News Sitemap to Google, you must have added your site to Google’s Publisher Center and had it approved.</p>', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
'descriptionVersion' => 0,
'productUrl' => 'https://aioseo.com/news-sitemap',
'learnMoreUrl' => 'https://aioseo.com/news-sitemap',
'manageUrl' => 'https://route#aioseo-sitemaps:news-sitemap',
'basename' => 'aioseo-news-sitemap/aioseo-news-sitemap.php',
'installed' => false,
'isActive' => false,
'canInstall' => false,
'canActivate' => false,
'canUpdate' => false,
'capability' => $this->getManageCapability( 'aioseo-news-sitemap' ),
'minimumVersion' => '0.0.0',
'hasMinimumVersion' => false
],
[
'sku' => 'aioseo-index-now',
'name' => 'IndexNow',
'version' => '1.0.0',
'image' => null,
'icon' => 'svg-sitemaps-pro',
'levels' => [
'agency',
'business',
'basic',
'plus',
'pro',
'elite'
],
'currentLevels' => [
'basic',
'plus',
'pro',
'elite'
],
'requiresUpgrade' => true,
'description' => '<p>Add IndexNow support to instantly notify search engines when your content has changed. This helps the search engines to prioritize the changes on your website and helps you rank faster.</p>', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
'descriptionVersion' => 0,
'downloadUrl' => '',
'productUrl' => 'https://aioseo.com/index-now/',
'learnMoreUrl' => 'https://aioseo.com/index-now/',
'manageUrl' => 'https://route#aioseo-settings:webmaster-tools',
'basename' => 'aioseo-index-now/aioseo-index-now.php',
'installed' => false,
'isActive' => false,
'canInstall' => false,
'canActivate' => false,
'canUpdate' => false,
'capability' => $this->getManageCapability( 'aioseo-index-now' ),
'minimumVersion' => '0.0.0',
'hasMinimumVersion' => false
],
[
'sku' => 'aioseo-rest-api',
'name' => 'REST API',
'version' => '1.0.0',
'image' => null,
'icon' => 'svg-code',
'levels' => [
'plus',
'pro',
'elite'
],
'currentLevels' => [
'plus',
'pro',
'elite'
],
'requiresUpgrade' => true,
'description' => '<p>Manage your post and term SEO meta via the WordPress REST API. This addon also works seamlessly with headless WordPress installs.</p>', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
'descriptionVersion' => 0,
'downloadUrl' => '',
'productUrl' => 'https://aioseo.com/feature/rest-api/',
'learnMoreUrl' => 'https://aioseo.com/feature/rest-api/',
'manageUrl' => null,
'basename' => 'aioseo-rest-api/aioseo-rest-api.php',
'installed' => false,
'isActive' => false,
'canInstall' => false,
'canActivate' => false,
'canUpdate' => false,
'capability' => null,
'minimumVersion' => '0.0.0',
'hasMinimumVersion' => false
],
[
'sku' => 'aioseo-image-seo',
'name' => 'Image SEO',
'version' => '1.0.0',
'image' => null,
'icon' => 'svg-image-seo',
'levels' => [
'individual',
'business',
'agency',
'plus',
'pro',
'elite',
],
'currentLevels' => [
'plus',
'pro',
'elite'
],
'requiresUpgrade' => true,
'description' => '<p>Globally control the Title attribute and Alt text for images in your content. These attributes are essential for both accessibility and SEO.</p>',
'descriptionVersion' => 0,
'productUrl' => 'https://aioseo.com/image-seo',
'learnMoreUrl' => 'https://aioseo.com/image-seo',
'manageUrl' => 'https://route#aioseo-search-appearance:media',
'basename' => 'aioseo-image-seo/aioseo-image-seo.php',
'installed' => false,
'isActive' => false,
'canInstall' => false,
'canActivate' => false,
'canUpdate' => false,
'capability' => $this->getManageCapability( 'aioseo-image-seo' ),
'minimumVersion' => '0.0.0',
'hasMinimumVersion' => false
]
] ), true );
}
/**
* Check for updates for all addons.
*
* @since 4.2.4
*
* @return void
*/
public function registerUpdateCheck() {}
/**
* Updates a given addon or plugin.
*
* @since 4.4.3
*
* @param string $name The addon name/sku.
* @param bool $network Whether we are in a network environment.
* @return bool Whether the installation was succesful.
*/
public function upgradeAddon( $name, $network ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return false;
}
/**
* Get the download URL for the given addon.
*
* @since 4.4.3
*
* @param string $sku The addon sku.
* @return string The download url for the addon.
*/
public function getDownloadUrl( $sku ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return '';
}
} Assets.php 0000644 00000004125 15154302615 0006523 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use AIOSEO\Plugin\Common\Traits;
/**
* Load file assets.
*
* @since 4.1.9
*/
class Assets {
use Traits\Assets;
/**
* Get the script handle to use for asset enqueuing.
*
* @since 4.1.9
*
* @var string
*/
private $scriptHandle = 'aioseo';
/**
* Class constructor.
*
* @since 4.1.9
*
* @param \AIOSEO\Plugin\Common\Core\Core $core The AIOSEO Core class.
*/
public function __construct( $core ) {
$this->core = $core;
$this->version = aioseo()->version;
$this->manifestFile = AIOSEO_DIR . '/dist/' . aioseo()->versionPath . '/manifest.php';
$this->isDev = aioseo()->isDev;
if ( $this->isDev ) {
$this->domain = getenv( 'VITE_AIOSEO_DOMAIN' );
$this->port = getenv( 'VITE_AIOSEO_DEV_PORT' );
}
add_filter( 'script_loader_tag', [ $this, 'scriptLoaderTag' ], 10, 3 );
add_action( 'admin_head', [ $this, 'devRefreshRuntime' ] );
add_action( 'wp_head', [ $this, 'devRefreshRuntime' ] );
}
/**
* Get the public URL base.
*
* @since 4.1.9
*
* @return string The URL base.
*/
private function getPublicUrlBase() {
return $this->shouldLoadDev() ? $this->getDevUrl() . 'dist/' . aioseo()->versionPath . '/assets/' : $this->basePath();
}
/**
* Get the base path URL.
*
* @since 4.1.9
*
* @return string The base path URL.
*/
private function basePath() {
return $this->normalizeAssetsHost( plugins_url( 'dist/' . aioseo()->versionPath . '/assets/', AIOSEO_FILE ) );
}
/**
* Adds the RefreshRuntime.
*
* @since 4.1.9
*
* @return void
*/
public function devRefreshRuntime() {
if ( $this->shouldLoadDev() ) {
echo sprintf( '<script type="module">
import RefreshRuntime from "%1$s@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>', $this->getDevUrl() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
} Backup.php 0000644 00000004413 15154302615 0006466 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Backup for AIOSEO Settings.
*
* @since 4.0.0
*/
class Backup {
/**
* A the name of the option to save backups with.
*
* @since 4.00
*
* @var string
*/
private $optionsName = 'aioseo_settings_backup';
/**
* Get all backups.
*
* @return array An array of backups.
*/
public function all() {
$backups = json_decode( get_option( $this->optionsName ), true );
if ( empty( $backups ) ) {
$backups = [];
}
return $backups;
}
/**
* Creates a backup of the settings state.
*
* @since 4.0.0
*
* @return void
*/
public function create() {
$backupTime = time();
$options = $this->getOptions();
update_option( $this->optionsName . '_' . $backupTime, wp_json_encode( $options ), 'no' );
$backups = $this->all();
$backups[] = $backupTime;
update_option( $this->optionsName, wp_json_encode( $backups ), 'no' );
}
/**
* Deletes a backup of the settings.
*
* @since 4.0.0
*
* @return void
*/
public function delete( $backupTime ) {
delete_option( $this->optionsName . '_' . $backupTime );
$backups = $this->all();
foreach ( $backups as $key => $backup ) {
if ( $backup === $backupTime ) {
unset( $backups[ $key ] );
}
}
update_option( $this->optionsName, wp_json_encode( array_values( $backups ) ), 'no' );
}
/**
* Restores a backup of the settings.
*
* @since 4.0.0
*
* @return void
*/
public function restore( $backupTime ) {
$backup = json_decode( get_option( $this->optionsName . '_' . $backupTime ), true );
if ( ! empty( $backup['options']['tools']['robots']['rules'] ) ) {
$backup['options']['tools']['robots']['rules'] = array_merge(
aioseo()->robotsTxt->extractSearchAppearanceRules(),
$backup['options']['tools']['robots']['rules']
);
}
aioseo()->options->sanitizeAndSave( $backup['options'] );
aioseo()->internalOptions->sanitizeAndSave( $backup['internalOptions'] );
}
/**
* Get the options to save.
*
* @since 4.0.0
*
* @return array An array of options to save.
*/
private function getOptions() {
return [
'options' => aioseo()->options->all(),
'internalOptions' => aioseo()->internalOptions->all()
];
}
} Blocks.php 0000644 00000010366 15154302615 0006502 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Block helpers.
*
* @since 4.1.1
*/
class Blocks {
/**
* Class constructor.
*
* @since 4.1.1
*/
public function __construct() {
add_action( 'init', [ $this, 'init' ] );
}
/**
* Initializes our blocks.
*
* @since 4.1.1
*
* @return void
*/
public function init() {
add_action( 'enqueue_block_editor_assets', [ $this, 'registerBlockEditorAssets' ] );
}
/**
* Registers the block type with WordPress.
*
* @since 4.2.1
*
* @param string $slug Block type name including namespace.
* @param array $args Array of block type arguments with additional 'wp_min_version' arg.
* @return \WP_Block_Type|false The registered block type on success, or false on failure.
*/
public function registerBlock( $slug = '', $args = [] ) {
global $wp_version; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
if ( ! strpos( $slug, '/' ) ) {
$slug = 'aioseo/' . $slug;
}
if ( ! $this->isBlockEditorActive() ) {
return false;
}
// Check if the block requires a minimum WP version.
if ( ! empty( $args['wp_min_version'] ) && version_compare( $wp_version, $args['wp_min_version'], '>' ) ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
return false;
}
// Checking whether block is registered to ensure it isn't registered twice.
if ( $this->isRegistered( $slug ) ) {
return false;
}
$defaults = [
'render_callback' => null,
'editor_script' => aioseo()->core->assets->jsHandle( 'src/vue/standalone/blocks/main.js' ),
'editor_style' => aioseo()->core->assets->cssHandle( 'src/vue/assets/scss/blocks-editor.scss' ),
'attributes' => null,
'supports' => null
];
$args = wp_parse_args( $args, $defaults );
return register_block_type( $slug, $args );
}
/**
* Registers Gutenberg editor assets.
*
* @since 4.2.1
*
* @return void
*/
public function registerBlockEditorAssets() {
$postSettingJsAsset = 'src/vue/standalone/post-settings/main.js';
if (
aioseo()->helpers->isScreenBase( 'widgets' ) ||
aioseo()->helpers->isScreenBase( 'customize' )
) {
/**
* Make sure the post settings JS asset is registered before adding it as a dependency below.
* This is needed because this asset is not loaded on widgets and customizer screens,
* {@see \AIOSEO\Plugin\Common\Admin\PostSettings::enqueuePostSettingsAssets}.
*
*/
aioseo()->core->assets->load( $postSettingJsAsset, [], aioseo()->helpers->getVueData() );
}
aioseo()->core->assets->loadCss( 'src/vue/standalone/blocks/main.js' );
$dependencies = [
'wp-annotations',
'wp-block-editor',
'wp-blocks',
'wp-components',
'wp-element',
'wp-i18n',
'wp-data',
'wp-url',
'wp-polyfill',
aioseo()->core->assets->jsHandle( $postSettingJsAsset )
];
aioseo()->core->assets->enqueueJs( 'src/vue/standalone/blocks/main.js', $dependencies );
aioseo()->core->assets->registerCss( 'src/vue/assets/scss/blocks-editor.scss' );
}
/**
* Check if a block is already registered.
*
* @since 4.2.1
*
* @param string $slug Name of block to check.
*
* @return bool
*/
public function isRegistered( $slug ) {
if ( ! class_exists( 'WP_Block_Type_Registry' ) ) {
return false;
}
return \WP_Block_Type_Registry::get_instance()->is_registered( $slug );
}
/**
* Helper function to determine if we're rendering the block inside Gutenberg.
*
* @since 4.1.1
*
* @return bool In gutenberg.
*/
public function isRenderingBlockInEditor() {
// phpcs:disable HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
return false;
}
$context = isset( $_REQUEST['context'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['context'] ) ) : '';
// phpcs:enable HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
return 'edit' === $context;
}
/**
* Helper function to determine if we can register blocks.
*
* @since 4.1.1
*
* @return bool Can register block.
*/
public function isBlockEditorActive() {
return function_exists( 'register_block_type' );
}
} Cache.php 0000644 00000021126 15154302615 0006264 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles our cache.
*
* @since 4.1.5
*/
class Cache {
/**
* Our cache table.
*
* @since 4.1.5
*
* @var string
*/
private $table = 'aioseo_cache';
/**
* Our cached cache.
*
* @since 4.1.5
*
* @var array
*/
private static $cache = [];
/**
* The Cache Prune class.
*
* @since 4.1.5
*
* @var CachePrune
*/
public $prune;
/**
* Prefix for this cache.
*
* @since 4.1.5
*
* @var string
*/
protected $prefix = '';
/**
* Class constructor.
*
* @since 4.7.7.1
*/
public function __construct() {
add_action( 'init', [ $this, 'checkIfTableExists' ] ); // This needs to run on init because the DB
// class gets instantiated along with the cache class.
}
/**
* Checks if the cache table exists and creates it if it doesn't.
*
* @since 4.7.7.1
*
* @return void
*/
public function checkIfTableExists() {
if ( ! aioseo()->core->db->tableExists( $this->table ) ) {
aioseo()->preUpdates->createCacheTable();
}
}
/**
* Returns the cache value for a key if it exists and is not expired.
*
* @since 4.1.5
*
* @param string $key The cache key name. Use a '%' for a like query.
* @param bool|array $allowedClasses Whether to allow objects to be returned.
* @return mixed The value or null if the cache does not exist.
*/
public function get( $key, $allowedClasses = false ) {
$key = $this->prepareKey( $key );
if ( isset( self::$cache[ $key ] ) ) {
return self::$cache[ $key ];
}
// Are we searching for a group of keys?
$isLikeGet = preg_match( '/%/', (string) $key );
$result = aioseo()->core->db
->start( $this->table )
->select( '`key`, `value`' )
->whereRaw( '( `expiration` IS NULL OR `expiration` > \'' . aioseo()->helpers->timeToMysql( time() ) . '\' )' );
$isLikeGet ?
$result->whereRaw( '`key` LIKE \'' . $key . '\'' ) :
$result->where( 'key', $key );
$result->output( ARRAY_A )->run();
// If we have nothing in the cache let's return a hard null.
$values = $result->nullSet() ? null : $result->result();
// If we have something let's normalize it.
if ( $values ) {
foreach ( $values as &$value ) {
$value['value'] = aioseo()->helpers->maybeUnserialize( $value['value'], $allowedClasses );
}
// Return only the single cache value.
if ( ! $isLikeGet ) {
$values = $values[0]['value'];
}
}
// Return values without a static cache.
// This is here because clearing the like cache is not simple.
if ( $isLikeGet ) {
return $values;
}
self::$cache[ $key ] = $values;
return self::$cache[ $key ];
}
/**
* Updates the given cache or creates it if it doesn't exist.
*
* @since 4.1.5
*
* @param string $key The cache key name.
* @param mixed $value The value.
* @param int $expiration The expiration time in seconds. Defaults to 24 hours. 0 to no expiration.
* @return void
*/
public function update( $key, $value, $expiration = DAY_IN_SECONDS ) {
// If the value is null we'll convert it and give it a shorter expiration.
if ( null === $value ) {
$value = false;
$expiration = 10 * MINUTE_IN_SECONDS;
}
$serializedValue = serialize( $value );
$expiration = 0 < $expiration ? aioseo()->helpers->timeToMysql( time() + $expiration ) : null;
aioseo()->core->db->insert( $this->table )
->set( [
'key' => $this->prepareKey( $key ),
'value' => $serializedValue,
'expiration' => $expiration,
'created' => aioseo()->helpers->timeToMysql( time() ),
'updated' => aioseo()->helpers->timeToMysql( time() )
] )
->onDuplicate( [
'value' => $serializedValue,
'expiration' => $expiration,
'updated' => aioseo()->helpers->timeToMysql( time() )
] )
->run();
$this->updateStatic( $key, $value );
}
/**
* Deletes the given cache key.
*
* @since 4.1.5
*
* @param string $key The cache key.
* @return void
*/
public function delete( $key ) {
$key = $this->prepareKey( $key );
aioseo()->core->db->delete( $this->table )
->where( 'key', $key )
->run();
$this->clearStatic( $key );
}
/**
* Prepares the key before using the cache.
*
* @since 4.1.5
*
* @param string $key The key to prepare.
* @return string The prepared key.
*/
private function prepareKey( $key ) {
$key = trim( $key );
$key = $this->prefix && 0 !== strpos( $key, $this->prefix ) ? $this->prefix . $key : $key;
if ( aioseo()->helpers->isDev() && 80 < mb_strlen( $key, 'UTF-8' ) ) {
throw new \Exception( 'You are using a cache key that is too large, shorten your key and try again: [' . esc_html( $key ) . ']' );
}
return $key;
}
/**
* Clears all of our cache.
*
* @since 4.1.5
*
* @return void
*/
public function clear() {
// Bust the tableExists and columnExists cache.
aioseo()->internalOptions->database->installedTables = '';
if ( $this->prefix ) {
$this->clearPrefix( '' );
return;
}
// Try to acquire the lock.
if ( ! aioseo()->core->db->acquireLock( 'aioseo_cache_clear_lock', 0 ) ) {
// If we couldn't acquire the lock, exit early without doing anything.
// This means another process is already clearing the cache.
return;
}
// If we find the activation redirect, we'll need to reset it after clearing.
$activationRedirect = $this->get( 'activation_redirect' );
// Create a temporary table with the same structure.
$table = aioseo()->core->db->prefix . $this->table;
$newTable = aioseo()->core->db->prefix . $this->table . '_new';
$oldTable = aioseo()->core->db->prefix . $this->table . '_old';
try {
// Drop the temp table if it exists from a previous failed attempt.
if ( false === aioseo()->core->db->execute( "DROP TABLE IF EXISTS {$newTable}" ) ) {
throw new \Exception( 'Failed to drop temporary table' );
}
// Create the new empty table with the same structure.
if ( false === aioseo()->core->db->execute( "CREATE TABLE {$newTable} LIKE {$table}" ) ) {
throw new \Exception( 'Failed to create temporary table' );
}
// Rename tables (atomic operation in MySQL).
if ( false === aioseo()->core->db->execute( "RENAME TABLE {$table} TO {$oldTable}, {$newTable} TO {$table}" ) ) {
throw new \Exception( 'Failed to rename tables' );
}
// Drop the old table.
if ( false === aioseo()->core->db->execute( "DROP TABLE {$oldTable}" ) ) {
throw new \Exception( 'Failed to drop old table' );
}
} catch ( \Exception $e ) {
// If something fails, ensure we clean up any temporary tables.
aioseo()->core->db->execute( "DROP TABLE IF EXISTS {$newTable}" );
aioseo()->core->db->execute( "DROP TABLE IF EXISTS {$oldTable}" );
// Truncate table to clear the cache.
aioseo()->core->db->truncate( $this->table )->run();
}
$this->clearStatic();
if ( $activationRedirect ) {
$this->update( 'activation_redirect', $activationRedirect, 30 );
}
}
/**
* Clears all of our cache under a certain prefix.
*
* @since 4.1.5
*
* @param string $prefix A prefix to clear or empty to clear everything.
* @return void
*/
public function clearPrefix( $prefix ) {
$prefix = $this->prepareKey( $prefix );
aioseo()->core->db->delete( $this->table )
->whereRaw( "`key` LIKE '$prefix%'" )
->run();
$this->clearStaticPrefix( $prefix );
}
/**
* Clears all of our static in-memory cache of a prefix.
*
* @since 4.1.5
*
* @param string $prefix A prefix to clear.
* @return void
*/
private function clearStaticPrefix( $prefix ) {
$prefix = $this->prepareKey( $prefix );
foreach ( array_keys( self::$cache ) as $key ) {
if ( 0 === strpos( $key, $prefix ) ) {
unset( self::$cache[ $key ] );
}
}
}
/**
* Clears all of our static in-memory cache.
*
* @since 4.1.5
*
* @param string $key A key to clear.
* @return void
*/
private function clearStatic( $key = null ) {
if ( empty( $key ) ) {
self::$cache = [];
return;
}
unset( self::$cache[ $this->prepareKey( $key ) ] );
}
/**
* Clears all of our static in-memory cache or the cache for a single given key.
*
* @since 4.7.1
*
* @param string $key A key to clear (optional).
* @param string $value A value to update (optional).
* @return void
*/
private function updateStatic( $key = null, $value = null ) {
if ( empty( $key ) ) {
$this->clearStatic( $key );
return;
}
self::$cache[ $this->prepareKey( $key ) ] = $value;
}
/**
* Returns the cache table name.
*
* @since 4.1.5
*
* @return string
*/
public function getTableName() {
return $this->table;
}
} CachePrune.php 0000644 00000004074 15154302615 0007301 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles our cache pruning.
*
* @since 4.1.5
*/
class CachePrune {
/**
* The action for the scheduled cache prune.
*
* @since 4.1.5
*
* @var string
*/
private $pruneAction = 'aioseo_cache_prune';
/**
* The action for the scheduled old cache clean.
*
* @since 4.1.5
*
* @var string
*/
private $optionCacheCleanAction = 'aioseo_old_cache_clean';
/**
* Class constructor.
*
* @since 4.1.5
*/
public function __construct() {
add_action( 'init', [ $this, 'init' ] );
}
/**
* Inits our class.
*
* @since 4.1.5
*
* @return void
*/
public function init() {
add_action( $this->pruneAction, [ $this, 'prune' ] );
add_action( $this->optionCacheCleanAction, [ $this, 'optionCacheClean' ] );
if ( ! is_admin() ) {
return;
}
if ( ! aioseo()->actionScheduler->isScheduled( $this->pruneAction ) ) {
aioseo()->actionScheduler->scheduleRecurrent( $this->pruneAction, 0, DAY_IN_SECONDS );
}
}
/**
* Prunes our expired cache.
*
* @since 4.1.5
*
* @return void
*/
public function prune() {
aioseo()->core->db->delete( aioseo()->core->cache->getTableName() )
->whereRaw( '( `expiration` IS NOT NULL AND expiration <= \'' . aioseo()->helpers->timeToMysql( time() ) . '\' )' )
->run();
}
/**
* Cleans our old options cache.
*
* @since 4.1.5
*
* @return void
*/
public function optionCacheClean() {
$optionCache = aioseo()->core->db->delete( aioseo()->core->db->db->options, true )
->whereRaw( "option_name LIKE '\_aioseo\_cache\_%'" )
->limit( 10000 )
->run();
// Schedule a new run if we're not done cleaning.
if ( 0 !== $optionCache->db->rows_affected ) {
aioseo()->actionScheduler->scheduleSingle( $this->optionCacheCleanAction, MINUTE_IN_SECONDS, [], true );
}
}
/**
* Returns the action name for the old cache clean.
*
* @since 4.1.5
*
* @return string
*/
public function getOptionCacheCleanAction() {
return $this->optionCacheCleanAction;
}
} Database.php 0000644 00000136636 15154302615 0007002 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Database utility class for AIOSEO.
*
* @since 4.0.0
*/
class Database {
/**
* List of custom tables we support.
*
* @since 4.0.0
*
* @var array
*/
protected $customTables = [
'aioseo_cache',
'aioseo_crawl_cleanup_blocked_args',
'aioseo_crawl_cleanup_logs',
'aioseo_links',
'aioseo_links_suggestions',
'aioseo_notifications',
'aioseo_posts',
'aioseo_redirects',
'aioseo_redirects_404',
'aioseo_redirects_404_logs',
'aioseo_redirects_hits',
'aioseo_redirects_logs',
'aioseo_terms',
'aioseo_search_statistics_objects',
'aioseo_revisions'
];
/**
* Holds $wpdb instance.
*
* @since 4.0.0
*
* @var \wpdb
*/
public $db = null;
/**
* Holds $wpdb prefix.
*
* @since 4.0.0
*
* @var string
*/
public $prefix = '';
/**
* The database table in use by this query.
*
* @since 4.0.0
*
* @var string
*/
public $table = '';
/**
* The sql statement (SELECT, INSERT, UPDATE, DELETE, etc.).
*
* @since 4.0.0
*
* @var string
*/
private $statement = '';
/**
* The limit clause for the SQL query.
*
* @since 4.0.0
*
* @var string|int
*/
private $limit = '';
/**
* The group clause for the SQL query.
*
* @since 4.0.0
*
* @var array
*/
private $group = [];
/**
* The order by clause for the SQL query.
*
* @since 4.0.0
*
* @var array
*/
private $order = [];
/**
* The select clause for the SQL query.
*
* @since 4.0.0
*
* @var array
*/
private $select = [];
/**
* The set clause for the SQL query.
*
* @since 4.0.0
*
* @var array
*/
private $set = [];
/**
* Duplicate clause for the INSERT query.
*
* @since 4.1.5
*
* @var array
*/
private $onDuplicate = [];
/**
* Ignore clause for the INSERT query.
*
* @since 4.1.6
*
* @var array
*/
private $ignore = false;
/**
* The where clause for the SQL query.
*
* @since 4.0.0
*
* @var array
*/
private $where = [];
/**
* The union clause for the SQL query.
*
* @since 4.0.0
*
* @var array
*/
private $union = [];
/**
* The join clause for the SQL query.
*
* @since 4.2.7
*
* @var array
*/
private $join = [];
/**
* Determines whether the select statement should be distinct.
*
* @since 4.0.0
*
* @var bool
*/
private $distinct = false;
/**
* The order by direction for the query.
*
* @since 4.0.0
*
* @var string
*/
private $orderDirection = 'ASC';
/**
* The query string is populated after the __toString function is run.
*
* @since 4.0.0
*
* @var string
*/
private $query = '';
/**
* The sql query results are stored here.
*
* @since 4.0.0
*
* @var mixed
*/
private $result;
/**
* The method in which $wpdb will output results.
*
* @since 4.0.0
*
* @var string
*/
private $output = 'OBJECT';
/**
* Whether or not to strip tags.
*
* @since 4.0.0
*
* @var bool
*/
private $stripTags = false;
/**
* Set which option to use to escape the SQL query.
*
* @since 4.0.0
*
* @var int
*/
protected $escapeOptions = 0;
/**
* A cache of all queries and their results.
*
* @var array
*/
private $cache = [];
/**
* Whether or not to reset the cached results.
*
* @var bool
*/
private $shouldResetCache = false;
/**
* Constant for escape options.
*
* @since 4.0.0
*
* @var int
*/
const ESCAPE_FORCE = 2;
/**
* Constant for escape options.
*
* @since 4.0.0
*
* @var int
*/
const ESCAPE_STRIP_HTML = 4;
/**
* Constant for escape options.
*
* @since 4.0.0
*
* @var int
*/
const ESCAPE_QUOTE = 8;
/**
* List of model class instances.
*
* @since 4.2.7
*
* @var array
*/
private $models = [];
/**
* The last query that ran, stringified.
*
* @since 4.3.0
*/
public $lastQuery = '';
/**
* Prepares the database class for use.
*
* @since 4.0.0
*/
public function __construct() {
$this->init();
}
/**
* Initializes the DB class.
* This needs to be called after the class is instantiated or when switching between sites in a multisite environment.
* The latter is important because the prefix otherwise isn't updated.
*
* @since 4.6.1
*
* @return void
*/
public function init() {
global $wpdb;
$this->db = $wpdb;
$this->prefix = $wpdb->prefix;
$this->escapeOptions = self::ESCAPE_STRIP_HTML | self::ESCAPE_QUOTE;
}
/**
* If this is a clone, lets reset all the data.
*
* @since 4.0.0
*/
public function __clone() {
// We need to reset the result separately as well since it is not in the default array.
$this->reset( [ 'result' ] );
$this->reset();
}
/**
* Gets all AIOSEO installed tables.
*
* @since 4.0.0
*
* @return array An array of custom AIOSEO tables.
*/
public function getInstalledTables() {
$results = $this->db->get_results( 'SHOW TABLES', 'ARRAY_N' );
return ! empty( $results ) ? wp_list_pluck( $results, 0 ) : [];
}
/**
* Get all the database info such as data size, index size, table list.
*
* @since 4.4.5
*
* @return array An array of the database info.
*/
public function getDatabaseInfo() {
$tables = [];
$databaseSize = [];
if ( defined( 'DB_NAME' ) ) {
$databaseTableInformation = $this->db->get_results(
$this->db->prepare(
"SELECT
table_name AS 'name',
table_collation AS 'collation',
engine AS 'engine',
round( ( data_length / 1024 / 1024 ), 2 ) 'data',
round( ( index_length / 1024 / 1024 ), 2 ) 'index'
FROM information_schema.TABLES
WHERE table_schema = %s
ORDER BY name ASC;",
DB_NAME
)
);
$databaseSize = [
'data' => 0,
'index' => 0,
];
$siteTablesPrefix = $this->db->get_blog_prefix( get_current_blog_id() );
$globalTables = $this->db->tables( 'global', true );
foreach ( $databaseTableInformation as $table ) {
// Only include tables matching the prefix of the current site, this is to prevent displaying all tables on a MS install not relating to the current.
if ( is_multisite() && 0 !== strpos( $table->name, $siteTablesPrefix ) && ! in_array( $table->name, $globalTables, true ) ) {
continue;
}
$tableType = ( 0 === strpos( $table->name, aioseo()->core->db->prefix . 'aioseo' ) ) ? 'aioseo' : 'other';
$tables[ $tableType ][ $table->name ] = [
'data' => $table->data,
'index' => $table->index,
'engine' => $table->engine,
'collation' => $table->collation
];
$databaseSize['data'] += $table->data;
$databaseSize['index'] += $table->index;
}
}
return [
'tables' => $tables,
'size' => $databaseSize,
];
}
/**
* Gets all columns from a table.
*
* @since 4.0.0
*
* @param string $table The name of the table to lookup columns for.
* @return array An array of custom AIOSEO tables.
*/
public function getColumns( $table ) {
if ( ! $this->tableExists( $table ) ) {
return [];
}
$table = $this->prefix . $table;
$installedTables = json_decode( aioseo()->internalOptions->database->installedTables, true );
if ( empty( $installedTables[ $table ] ) ) {
$installedTables[ $table ] = $this->db->get_col( 'SHOW COLUMNS FROM `' . $table . '`' );
aioseo()->internalOptions->database->installedTables = wp_json_encode( $installedTables );
}
return $installedTables[ $table ];
}
/**
* Checks if a table exists.
*
* @since 4.0.0
*
* @param string $table The name of the table.
* @return bool Whether or not the table exists.
*/
public function tableExists( $table ) {
$table = $this->prefix . $table;
$installedTables = json_decode( aioseo()->internalOptions->database->installedTables ?? '[]', true ) ?: [];
if ( isset( $installedTables[ $table ] ) ) {
return true;
}
$results = $this->db->get_results( "SHOW TABLES LIKE '" . $table . "'" );
if ( empty( $results ) ) {
return false;
}
$installedTables[ $table ] = [];
aioseo()->internalOptions->database->installedTables = wp_json_encode( $installedTables );
return true;
}
/**
* Checks if a column exists on a given table.
*
* @since 4.0.5
*
* @param string $table The name of the table.
* @param string $column The name of the column.
* @return bool Whether or not the column exists.
*/
public function columnExists( $table, $column ) {
if ( ! $this->tableExists( $table ) ) {
return false;
}
$columns = $this->getColumns( $table );
return in_array( $column, $columns, true );
}
/**
* Gets the size of a table in bytes.
*
* @since 4.1.0
*
* @param string $table The table to check.
* @return int The size of the table in bytes.
*/
public function getTableSize( $table ) {
$this->db->query( 'ANALYZE TABLE ' . $this->prefix . $table );
$results = $this->db->get_results( '
SELECT
TABLE_NAME AS `table`,
ROUND(SUM(DATA_LENGTH + INDEX_LENGTH)) AS `size`
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = "' . $this->db->dbname . '"
AND TABLE_NAME = "' . $this->prefix . $table . '"
ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC;
' );
return ! empty( $results ) ? $results[0]->size : 0;
}
/**
* The query string in all its glory.
*
* @since 4.0.0
*
* @return string The actual query string.
*/
public function __toString() {
switch ( strtoupper( $this->statement ) ) {
case 'INSERT':
$insert = 'INSERT ';
if ( $this->ignore ) {
$insert .= 'IGNORE ';
}
$insert .= 'INTO ' . $this->table;
$clauses = [];
$clauses[] = $insert;
$clauses[] = 'SET ' . implode( ', ', $this->set );
if ( ! empty( $this->onDuplicate ) ) {
$clauses[] = 'ON DUPLICATE KEY UPDATE ' . implode( ', ', $this->onDuplicate );
}
break;
case 'REPLACE':
$clauses = [];
$clauses[] = "REPLACE INTO $this->table";
$clauses[] = 'SET ' . implode( ', ', $this->set );
break;
case 'UPDATE':
$clauses = [];
$clauses[] = "UPDATE $this->table";
if ( count( $this->join ) > 0 ) {
foreach ( (array) $this->join as $join ) {
if ( is_array( $join[1] ) ) {
$join_on = []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
foreach ( (array) $join[1] as $left => $right ) {
$join_on[] = "$this->table.`$left` = `{$join[0]}`.`$right`"; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
}
// phpcs:disable Squiz.NamingConventions.ValidVariableName
$clauses[] = "\t" . ( ( 'LEFT' === $join[2] || 'RIGHT' === $join[2] ) ? $join[2] . ' JOIN ' : 'JOIN ' ) . $join[0] . ' ON ' . implode( ' AND ', $join_on );
// phpcs:enable Squiz.NamingConventions.ValidVariableName
} else {
$clauses[] = "\t" . ( ( 'LEFT' === $join[2] || 'RIGHT' === $join[2] ) ? $join[2] . ' JOIN ' : 'JOIN ' ) . "{$join[0]} ON {$join[1]}";
}
}
}
$clauses[] = 'SET ' . implode( ', ', $this->set );
if ( count( $this->where ) > 0 ) {
$clauses[] = "WHERE 1 = 1 AND\n\t" . implode( "\n\tAND ", $this->where );
}
if ( count( $this->order ) > 0 ) {
$clauses[] = 'ORDER BY ' . implode( ', ', $this->order );
}
if ( $this->limit ) {
$clauses[] = 'LIMIT ' . $this->limit;
}
break;
case 'TRUNCATE':
$clauses = [];
$clauses[] = "TRUNCATE TABLE $this->table";
break;
case 'DELETE':
$clauses = [];
$clauses[] = "DELETE FROM $this->table";
if ( count( $this->where ) > 0 ) {
$clauses[] = "WHERE 1 = 1 AND\n\t" . implode( "\n\tAND ", $this->where );
}
if ( count( $this->order ) > 0 ) {
$clauses[] = 'ORDER BY ' . implode( ', ', $this->order );
}
if ( $this->limit ) {
$clauses[] = 'LIMIT ' . $this->limit;
}
break;
case 'SELECT':
case 'SELECT DISTINCT':
default:
// Select fields.
$clauses = [];
$distinct = ( $this->distinct || stripos( $this->statement, 'DISTINCT' ) !== false ) ? 'DISTINCT ' : '';
$select = ( count( $this->select ) > 0 ) ? implode( ",\n\t", $this->select ) : '*';
$clauses[] = "SELECT {$distinct}\n\t{$select}";
// Select table.
$clauses[] = "FROM $this->table";
// Select joins.
if ( ! empty( $this->join ) && count( $this->join ) > 0 ) {
foreach ( (array) $this->join as $join ) {
if ( is_array( $join[1] ) ) {
$join_on = []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
foreach ( (array) $join[1] as $left => $right ) {
$join_on[] = "$this->table.`$left` = `{$join[0]}`.`$right`"; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
}
// phpcs:disable Squiz.NamingConventions.ValidVariableName
$clauses[] = "\t" . ( ( 'LEFT' === $join[2] || 'RIGHT' === $join[2] ) ? $join[2] . ' JOIN ' : 'JOIN ' ) . $join[0] . ' ON ' . implode( ' AND ', $join_on );
// phpcs:enable Squiz.NamingConventions.ValidVariableName
} else {
$clauses[] = "\t" . ( ( 'LEFT' === $join[2] || 'RIGHT' === $join[2] ) ? $join[2] . ' JOIN ' : 'JOIN ' ) . "{$join[0]} ON {$join[1]}";
}
}
}
// Select conditions.
if ( count( $this->where ) > 0 ) {
$clauses[] = "WHERE 1 = 1 AND\n\t" . implode( "\n\tAND ", $this->where );
}
// Union queries.
if ( count( $this->union ) > 0 ) {
foreach ( $this->union as $union ) {
$keyword = ( $union[1] ) ? 'UNION' : 'UNION ALL';
$clauses[] = "\n$keyword\n\n$union[0]";
}
$clauses[] = '';
}
// Select groups.
if ( count( $this->group ) > 0 ) {
$clauses[] = 'GROUP BY ' . implode( ', ', $this->escapeColNames( $this->group ) );
}
// Select order.
if ( count( $this->order ) > 0 ) {
$orderFragments = [];
foreach ( $this->escapeColNames( $this->order ) as $col ) {
$orderFragments[] = ( preg_match( '/ (ASC|DESC|RAND\(\))$/i', (string) $col ) ) ? $col : "$col $this->orderDirection";
}
$clauses[] = 'ORDER BY ' . implode( ', ', $orderFragments );
}
// Select limit.
if ( $this->limit ) {
$clauses[] = 'LIMIT ' . $this->limit;
}
break;
}
// @HACK for wpdb::prepare.
$clauses[] = '/* %d = %d */';
$this->query = str_replace( '%%d = %%d', '%d = %d', str_replace( '%', '%%', implode( "\n", $clauses ) ) );
// Flag queries with double quotes down, but not if the double quotes are contained within a string value (like JSON).
if ( aioseo()->isDev && preg_match( '/\{[^}]*\}(*SKIP)(*FAIL)|\[[^]]*\](*SKIP)(*FAIL)|\'[^\']*\'(*SKIP)(*FAIL)|\\"(*SKIP)(*FAIL)|"/i', (string) $this->query ) ) {
// phpcs:disable WordPress.PHP.DevelopmentFunctions
error_log(
"Query with double quotes detected - this may cause isues when ANSI_QUOTES is enabled:\r\n" .
$this->query . "\r\n" . wp_debug_backtrace_summary()
);
// phpcs:enable WordPress.PHP.DevelopmentFunctions
}
$this->lastQuery = $this->query;
return $this->query;
}
/**
* Shortcut method to return the query string.
*
* @since 4.0.0
*
* @return string The query string.
*/
public function query() {
return $this->__toString();
}
/**
* Start a new Database Query.
*
* @since 4.0.0
*
* @param string $table The name of the table without the WordPress prefix unless includes_prefix is true.
* @param bool $includesPrefix This determines if the table name includes the WordPress prefix or not.
* @param string $statement The MySQL statement for the query.
* @return Database Returns the Database class which can then be method chained for building the query.
*/
public function start( $table = '', $includesPrefix = false, $statement = 'SELECT' ) {
// Always reset everything when starting a new query.
$this->reset();
$this->table = $includesPrefix ? $table : $this->prefix . $table;
$this->statement = $statement;
return $this;
}
/**
* Shortcut method for start with INSERT as the statement.
*
* @since 4.0.0
*
* @param string $table The name of the table without the WordPress prefix unless includes_prefix is true.
* @param bool $includesPrefix This determines if the table name includes the WordPress prefix or not.
* @return Database Returns the Database class which can then be method chained for building the query.
*/
public function insert( $table = '', $includesPrefix = false ) {
return $this->start( $table, $includesPrefix, 'INSERT' );
}
/**
* Shortcut method for start with INSERT IGNORE as the statement.
*
* @since 4.1.6
*
* @param string $table The name of the table without the WordPress prefix unless includes_prefix is true.
* @param bool $includesPrefix This determines if the table name includes the WordPress prefix or not.
* @return Database Returns the Database class which can then be method chained for building the query.
*/
public function insertIgnore( $table = '', $includesPrefix = false ) {
$this->ignore = true;
return $this->start( $table, $includesPrefix, 'INSERT' );
}
/**
* Shortcut method for start with UPDATE as the statement.
*
* @since 4.0.0
*
* @param string $table The name of the table without the WordPress prefix unless includes_prefix is true.
* @param bool $includesPrefix This determines if the table name includes the WordPress prefix or not.
* @return Database Returns the Database class which can then be method chained for building the query.
*/
public function update( $table = '', $includesPrefix = false ) {
return $this->start( $table, $includesPrefix, 'UPDATE' );
}
/**
* Shortcut method for start with REPLACE as the statement.
*
* @since 4.0.0
*
* @param string $table The name of the table without the WordPress prefix unless includes_prefix is true.
* @param bool $includesPrefix This determines if the table name includes the WordPress prefix or not.
* @return Database Returns the Database class which can then be method chained for building the query.
*/
public function replace( $table = '', $includesPrefix = false ) {
return $this->start( $table, $includesPrefix, 'REPLACE' );
}
/**
* Shortcut method for start with TRUNCATE as the statement.
*
* @since 4.0.0
*
* @param string $table The name of the table without the WordPress prefix unless includes_prefix is true.
* @param bool $includesPrefix This determines if the table name includes the WordPress prefix or not.
* @return Database Returns the Database class which can then be method chained for building the query.
*/
public function truncate( $table = '', $includesPrefix = false ) {
return $this->start( $table, $includesPrefix, 'TRUNCATE' );
}
/**
* Shortcut method for start with DELETE as the statement.
*
* @since 4.0.0
*
* @param string $table The name of the table without the WordPress prefix unless includes_prefix is true.
* @param bool $includesPrefix This determines if the table name includes the WordPress prefix or not.
* @return Database Returns the Database class which can then be method chained for building the query.
*/
public function delete( $table = '', $includesPrefix = false ) {
return $this->start( $table, $includesPrefix, 'DELETE' );
}
/**
* Adds a SELECT clause.
*
* @since 4.0.0
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function select() {
$args = (array) func_get_args();
if ( count( $args ) === 1 && is_array( $args[0] ) ) {
$args = $args[0];
}
$this->select = array_merge( $this->select, $this->escapeColNames( $args ) );
return $this;
}
/**
* Adds a WHERE clause.
*
* @since 4.0.0
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function where() {
$criteria = $this->prepArgs( func_get_args() );
foreach ( (array) $criteria as $field => $value ) {
if ( ! preg_match( '/[\(\)<=>!]+/', (string) $field ) && false === stripos( $field, ' IS ' ) ) {
$operator = ( is_null( $value ) ) ? 'IS' : '=';
$escaped = $this->escapeColNames( $field );
$field = array_pop( $escaped ) . ' ' . $operator;
}
if ( is_null( $value ) && false !== stripos( $field, ' IS ' ) ) {
// WHERE `field` IS NOT NULL.
$this->where[] = "$field NULL";
continue;
}
if ( is_null( $value ) ) {
// WHERE `field` IS NULL.
$this->where[] = "$field NULL";
continue;
}
if ( is_array( $value ) ) {
$wheres = [];
foreach ( (array) $value as $val ) {
$wheres[] = sprintf( "$field %s", $this->escape( $val, $this->getEscapeOptions() | self::ESCAPE_QUOTE ) );
}
$this->where[] = '(' . implode( ' OR ', $wheres ) . ')';
continue;
}
$this->where[] = sprintf( "$field %s", $this->escape( $value, $this->getEscapeOptions() | self::ESCAPE_QUOTE ) );
}
return $this;
}
/**
* Adds a complex WHERE clause.
*
* @since 4.0.0
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function whereRaw() {
$criteria = $this->prepArgs( func_get_args() );
foreach ( (array) $criteria as $clause ) {
$this->where[] = $clause;
}
return $this;
}
/**
* Adds a WHERE clause with all arguments sent separated by OR instead of AND inside a subclause.
* @example [ 'a' => 1, 'b' => 2 ] becomes "AND (a = 1 OR b = 2)"
*
* @since 4.0.0
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function whereOr() {
$criteria = $this->prepArgs( func_get_args() );
$or = [];
foreach ( (array) $criteria as $field => $value ) {
if ( ! preg_match( '/[\(\)<=>!]+/', (string) $field ) && false === stripos( $field, ' IS ' ) ) {
$operator = ( is_null( $value ) ) ? 'IS' : '=';
$field = $this->escapeColNames( $field );
$field = array_pop( $field ) . ' ' . $operator;
}
if ( is_null( $value ) && false !== stripos( $field, ' IS ' ) ) {
// WHERE `field` IS NOT NULL.
$or[] = "$field NULL";
continue;
}
if ( is_null( $value ) ) {
// WHERE `field` IS NULL.
$or[] = "$field NULL";
}
$or[] = sprintf( "$field %s", $this->escape( $value, $this->getEscapeOptions() | self::ESCAPE_QUOTE ) );
}
// Create our subclause, and add it to the WHERE array.
$this->where[] = '(' . implode( ' OR ', $or ) . ')';
return $this;
}
/**
* Adds a WHERE IN() clause.
*
* @since 4.0.0
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function whereIn() {
$criteria = $this->prepArgs( func_get_args() );
foreach ( (array) $criteria as $field => $values ) {
if ( ! is_array( $values ) ) {
$values = [ $values ];
}
if ( count( $values ) === 0 ) {
continue;
}
foreach ( $values as &$value ) {
// Note: We can no longer check for `is_numeric` because a value like `61021e6242255` returns true and breaks the query.
if ( is_int( $value ) || is_float( $value ) ) {
// No change.
continue;
}
if ( is_null( $value ) || 'null' === strtolower( $value ) ) {
// Change to a true NULL value.
$value = null;
continue;
}
$value = sprintf( '%s', $this->escape( $value, $this->getEscapeOptions() | self::ESCAPE_QUOTE ) );
}
$values = implode( ',', $values );
$this->whereRaw( "$field IN ($values)" );
}
return $this;
}
/**
* Adds a WHERE NOT IN() clause.
*
* @since 4.0.0
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function whereNotIn() {
$criteria = $this->prepArgs( func_get_args() );
foreach ( (array) $criteria as $field => $values ) {
if ( ! is_array( $values ) ) {
$values = [ $values ];
}
if ( count( $values ) === 0 ) {
continue;
}
foreach ( $values as &$value ) {
if ( is_numeric( $value ) ) {
// No change.
continue;
}
if ( is_null( $value ) || false !== stristr( $value, 'NULL' ) ) {
// Change to a true NULL value.
$value = null;
continue;
}
$value = sprintf( '%s', $this->escape( $value, $this->getEscapeOptions() | self::ESCAPE_QUOTE ) );
}
$values = implode( ',', $values );
$this->whereRaw( "$field NOT IN($values)" );
}
return $this;
}
/**
* Adds a WHERE BETWEEN clause.
*
* @since 4.3.0
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function whereBetween() {
$criteria = $this->prepArgs( func_get_args() );
foreach ( (array) $criteria as $field => $values ) {
if ( ! is_array( $values ) ) {
$values = [ $values ];
}
if ( count( $values ) === 0 ) {
continue;
}
foreach ( $values as &$value ) {
// Note: We can no longer check for `is_numeric` because a value like `61021e6242255` returns true and breaks the query.
if ( is_int( $value ) || is_float( $value ) ) {
// No change.
continue;
}
if ( is_null( $value ) || false !== stristr( $value, 'NULL' ) ) {
// Change to a true NULL value.
$value = null;
continue;
}
$value = sprintf( '%s', $this->escape( $value, $this->getEscapeOptions() | self::ESCAPE_QUOTE ) );
}
$values = implode( ' AND ', $values );
$this->whereRaw( "$field BETWEEN $values" );
}
return $this;
}
/**
* Adds a LEFT JOIN clause.
*
* @since 4.0.0
*
* @param string $table The name of the table to join to this query.
* @param string|array $conditions The conditions of the join clause.
* @param bool $includesPrefix This determines if the table name includes the WordPress prefix or not.
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function leftJoin( $table = '', $conditions = '', $includesPrefix = false ) {
return $this->join( $table, $conditions, 'LEFT', $includesPrefix );
}
/**
* Adds a JOIN clause.
*
* @since 4.0.0
*
* @param string $table The name of the table to join to this query.
* @param string|array $conditions The conditions of the join clause.
* @param string $direction This can take 'LEFT' or 'RIGHT' as arguments.
* @param bool $includesPrefix This determines if the table name includes the WordPress prefix or not.
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function join( $table = '', $conditions = '', $direction = '', $includesPrefix = false ) {
$this->join[] = [ $includesPrefix ? $table : $this->prefix . $table, $conditions, $direction ];
return $this;
}
/**
* Add a UNION query.
*
* @since 4.0.0
*
* @param Database|string $query The query (Database object or query string) to be joined with.
* @param bool $distinct Set whether this union should be distinct or not.
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function union( $query, $distinct = true ) {
$this->union[] = [ $query, $distinct ];
return $this;
}
/**
* Adds am GROUP BY clause.
*
* @since 4.0.0
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function groupBy() {
$args = (array) func_get_args();
if ( count( $args ) === 1 && is_array( $args[0] ) ) {
$args = $args[0];
}
$this->group = array_merge( $this->group, $args );
return $this;
}
/**
* Adds am ORDER BY clause.
*
* @since 4.0.0
* @version 4.8.2 Hardened against SQL injection.
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function orderBy() {
// Normalize arguments.
$args = (array) func_get_args();
if ( count( $args ) === 1 && is_array( $args[0] ) ) {
$args = $args[0];
}
$orderBy = [];
// Separate commas to account for multiple orders.
foreach ( $args as $argComma ) {
$orderBy = array_map( 'trim', array_merge( $orderBy, explode( ',', $argComma ) ) );
}
// Validate and sanitize column names and sort directions.
$sanitizedOrderBy = [];
foreach ( $orderBy as $ordBy ) {
$parts = explode( ' ', $ordBy );
$column = str_replace( '`', '', $parts[0] ); // Strip existing ticks first.
$column = preg_replace( '/[^a-zA-Z0-9_.]/', '', $column ); // Strip invalid characters from the column name.
$column = $this->escapeColNames( $column )[0];
$direction = isset( $parts[1] ) ? strtoupper( $parts[1] ) : 'ASC';
// Validate the order direction.
if ( ! in_array( $direction, [ 'ASC', 'DESC' ], true ) ) {
$direction = 'ASC';
}
$sanitizedOrderBy[] = "$column $direction";
}
if ( ! empty( $sanitizedOrderBy ) ) {
if ( ! empty( $args[0] ) && true !== $args[0] ) {
$this->order = array_merge( $this->order, $sanitizedOrderBy );
} else {
// This allows for overwriting a preexisting order-by setting.
array_shift( $sanitizedOrderBy );
$this->order = $sanitizedOrderBy;
}
}
return $this;
}
/**
* Adds a raw ORDER BY clause.
*
* @since 4.8.2
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function orderByRaw() {
$args = (array) func_get_args();
if ( count( $args ) === 1 && is_array( $args[0] ) ) {
$args = $args[0];
}
$this->order = array_merge( $this->order, $args );
return $this;
}
/**
* Sets the sort direction for ORDER BY clauses.
*
* @since 4.0.0
*
* @param string $direction This sets the direction of the order by clause, default is 'ASC'.
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function orderDirection( $direction = 'ASC' ) {
$this->orderDirection = $direction;
return $this;
}
/**
* Adds a LIMIT clause.
*
* @since 4.0.0
*
* @param int $limit The amount of rows to limit the query to.
* @param int $offset The amount of rows the result of the query should be ofset with.
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function limit( $limit, $offset = -1 ) {
if ( ! is_numeric( $limit ) || $limit <= 0 ) {
return $this;
}
if ( ! is_numeric( $offset ) ) {
$offset = -1;
}
$this->limit = ( -1 === $offset )
? intval( $limit )
: intval( $offset ) . ', ' . intval( $limit );
return $this;
}
/**
* Converts associative arrays to a SET argument.
*
* @since 4.1.5
*
* @param array $args The arguments.
* @return array The prepared arguments.
*/
private function prepareSet( $args ) {
$args = $this->prepArgs( $args );
$preparedSet = [];
foreach ( (array) $args as $field => $value ) {
if ( is_null( $value ) ) {
$preparedSet[] = "`$field` = NULL";
continue;
}
if ( is_array( $value ) ) {
throw new \Exception( 'Cannot save an unserialized array in the database. Data passed was: ' . wp_json_encode( $value ) );
}
if ( is_object( $value ) ) {
throw new \Exception( 'Cannot save an unserialized object in the database. Data passed was: ' . esc_html( $value ) );
}
$preparedSet[] = sprintf( "`$field` = %s", $this->escape( $value, $this->getEscapeOptions() | self::ESCAPE_QUOTE ) );
}
return $preparedSet;
}
/**
* Adds a SET clause.
*
* @since 4.0.0
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function set() {
$this->set = array_merge( $this->set, $this->prepareSet( func_get_args() ) );
return $this;
}
/**
* Adds an ON DUPLICATE clause.
*
* @since 4.1.5
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function onDuplicate() {
$this->onDuplicate = array_merge( $this->onDuplicate, $this->prepareSet( func_get_args() ) );
return $this;
}
/**
* Set the output for the query.
*
* @since 4.0.0
*
* @param string $output This can be one of the following: ARRAY_A | ARRAY_N | OBJECT | OBJECT_K.
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function output( $output = 'OBJECT' ) {
if ( ! $output ) {
$output = 'OBJECT';
}
$this->output = $output;
return $this;
}
/**
* Reset the cache so we make sure the query gets to the DB.
*
* @since 4.1.6
*
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function resetCache() {
$this->shouldResetCache = true;
return $this;
}
/**
* Run this query.
*
* @since 4.0.0
*
* @param bool $reset Whether to reset the results/query.
* @param string $return Determine which method to call on the $wpdb object
* @param array $params Optional extra parameters to pass to the db method call
* @return Database Returns the Database class which can be method chained for more query building.
*/
public function run( $reset = true, $return = 'results', $params = [] ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( ! in_array( $return, [ 'results', 'col', 'var', 'row' ], true ) ) {
$return = 'results';
}
$prepare = $this->db->prepare( $this->query(), 1, 1 );
$queryHash = sha1( $this->query() );
$cacheTableName = $this->getCacheTableName();
// Pull the result from the in-memory cache if everything checks out.
if (
! $this->shouldResetCache &&
! in_array( $this->statement, [ 'INSERT', 'REPLACE', 'UPDATE', 'DELETE' ], true ) &&
isset( $this->cache[ $cacheTableName ][ $queryHash ][ $return ] ) &&
empty( $this->join )
) {
$this->result = $this->cache[ $cacheTableName ][ $queryHash ][ $return ];
return $this;
}
switch ( $return ) {
case 'col':
$this->result = $this->db->get_col( $prepare );
break;
case 'var':
$this->result = $this->db->get_var( $prepare );
break;
case 'row':
$this->result = $this->db->get_row( $prepare );
break;
default:
$this->result = $this->db->get_results( $prepare, $this->output );
}
if ( $reset ) {
$this->reset();
}
$this->cache[ $cacheTableName ][ $queryHash ][ $return ] = $this->result;
// Reset the cache trigger for the next run.
$this->shouldResetCache = false;
return $this;
}
/**
* Inject a count select statement and return the result.
*
* @since 4.1.0
*
* @param string $countColumn The column to count with. Defaults to '*' all.
* @return int The number of rows that were found.
*/
public function count( $countColumn = '*' ) {
$usingGroup = ! empty( $this->group );
$results = $this->reset( [ 'select', 'order', 'limit' ] )
->select( 'count(' . $countColumn . ') as count' )
->run()
->result();
return 1 === $this->numRows() && ! $usingGroup
? (int) $results[0]->count
: $this->numRows();
}
/**
* Inject a count group select statement and return the result.
*
* @since 4.6.1
*
* @param string $countDistinctColumn The column to count with. Defaults to '*' all.
* @return int The number of rows that were found.
*/
public function countDistinct( $countDistinctColumn = '*' ) {
$countDistinctColumn = '*' !== $countDistinctColumn ? 'distinct( ' . $countDistinctColumn . ' )' : $countDistinctColumn;
return $this->reset( [ 'select', 'order', 'limit' ] )
->select( 'count(' . $countDistinctColumn . ') as count' )
->run( true, 'var' )
->result();
}
/**
* Returns the query results based on the value of the output property.
*
* @since 4.0.0
*
* @return mixed This depends on what was set in the output property.
*/
public function result() {
return $this->result;
}
/**
* Return a model model from a row.
*
* @since 4.0.0
*
* @param string $class The name of the model class to call.
* @return object The model class instance.
*/
public function model( $class ) {
$result = $this->result();
return ! empty( $result )
? ( is_array( $result )
? new $class( (array) current( $result ) )
: $result )
: new $class();
}
/**
* Return an array of model class instancnes from the result.
*
* @since 4.0.0
*
* @param string $class The name of the model class to call.
* @param string $id The ID of the index to use.
* @param bool $toJson The index if necessary.
* @return array An array of model class instances.
*/
public function models( $class, $id = null, $toJson = false ) {
if ( ! empty( $this->models ) ) {
return $this->models;
}
$i = 0;
$models = [];
foreach ( $this->result() as $row ) {
$var = ( null === $id ) ? $row : $row[ $id ];
$class = new $class( $var );
// Lets add the class to the array using the class ID.
$models[ $class->id ] = $toJson ? $class->jsonSerialize() : $class;
$i++;
}
$this->models = $models;
return $this->models;
}
/**
* Returns the last error reported by MySQL.
*
* @since 4.0.0
*
* @return string The last error message.
*/
public function lastError() {
return $this->db->last_error;
}
/**
* Return the $wpdb insert_id from the last query.
*
* @since 4.0.0
*
* @return int The ID of the most recent INSERT query.
*/
public function insertId() {
return $this->db->insert_id;
}
/**
* Return the $wpdb rows_affected from the last query.
*
* @since 4.0.0
*
* @return int The number of rows affected.
*/
public function rowsAffected() {
return $this->db->rows_affected;
}
/**
* Return the $wpdb num_rows from the last query.
*
* @since 4.0.0
*
* @return int The count for the number of rows in the last query.
*/
public function numRows() {
return $this->db->num_rows;
}
/**
* Check if the last query had any rows.
*
* @since 4.0.0
*
* @return bool Whether there were any rows retrived by the last query.
*/
public function nullSet() {
return ( $this->numRows() < 1 );
}
/**
* This will start a MySQL transaction. Be sure to commit or rollback!
*
* @since 4.0.0
*/
public function startTransaction() {
$this->db->query( 'START TRANSACTION' );
}
/**
* This will commit a MySQL transaction. Used in conjunction with startTransaction.
*
* @since 4.0.0
*/
public function commit() {
$this->db->query( 'COMMIT' );
}
/**
* This will rollback a MySQL transaction. Used in conjunction with startTransaction.
*
* @since 4.0.0
*/
public function rollback() {
$this->db->query( 'ROLLBACK' );
}
/**
* Fast way to execute raw queries.
* NOTE: When using this method, all arguments must be sanitized manually!
*
* @since 4.0.0
*
* @param string $sql The sql query to execute.
* @param bool $results Whether to return the results or not.
* @param bool $useCache Whether to use the cache or not.
* @return mixed Could be an array or object depending on the result set.
*/
public function execute( $sql, $results = false, $useCache = false ) {
$this->lastQuery = $sql;
$queryHash = sha1( $sql );
$cacheTableName = $this->getCacheTableName();
// Pull the result from the in-memory cache if everything checks out.
if (
$useCache &&
! $this->shouldResetCache &&
isset( $this->cache[ $cacheTableName ][ $queryHash ] )
) {
if ( $results ) {
$this->result = $this->cache[ $cacheTableName ][ $queryHash ];
}
return $this;
}
if ( $results ) {
$this->result = $this->db->get_results( $sql, $this->output );
if ( $useCache ) {
$this->cache[ $cacheTableName ][ $queryHash ] = $this->result;
// Reset the cache trigger for the next run.
$this->shouldResetCache = false;
}
return $this;
}
return $this->db->query( $sql );
}
/**
* Escape a value for safe use in SQL queries.
*
* @param string $value The value to be escaped.
* @param int|null $options The escape options.
* @return string The escaped SQL value.
*/
public function escape( $value, $options = null ) {
if ( is_array( $value ) ) {
foreach ( $value as &$val ) {
$val = $this->escape( $val, $options );
}
return $value;
}
$options = ( is_null( $options ) ) ? $this->getEscapeOptions() : $options;
if ( ( $options & self::ESCAPE_STRIP_HTML ) !== 0 && isset( $this->stripTags ) && true === $this->stripTags ) {
$value = wp_strip_all_tags( $value );
}
if (
( ( $options & self::ESCAPE_FORCE ) !== 0 || php_sapi_name() === 'cli' ) ||
( ( $options & self::ESCAPE_QUOTE ) !== 0 && ! is_int( $value ) )
) {
$value = esc_sql( $value );
if ( ! is_int( $value ) ) {
$value = "'$value'";
}
}
return $value;
}
/**
* Returns the current escape options value.
*
* @since 4.0.0
*
* @return int The current escape options value.
*/
public function getEscapeOptions() {
return $this->escapeOptions;
}
/**
* Sets the current escape options value.
*
* @since 4.0.0
*
* @param int $options The escape options value.
*/
public function setEscapeOptions( $options ) {
$this->escapeOptions = $options;
}
/**
* Backtick-escapes an array of column and/or table names.
*
* @since 4.0.0
*
* @param array $cols An array of column names to be escaped.
* @return array An array of escaped column names.
*/
private function escapeColNames( $cols ) {
if ( ! is_array( $cols ) ) {
$cols = [ $cols ];
}
foreach ( $cols as &$col ) {
if ( false === stripos( $col, '(' ) && false === stripos( $col, ' ' ) && false === stripos( $col, '*' ) ) {
if ( stripos( $col, '.' ) ) {
list( $table, $c ) = explode( '.', $col );
$col = "`$table`.`$c`";
continue;
}
$col = "`$col`";
}
}
return $cols;
}
/**
* Gets a variable list of function arguments and reformats them as needed for many of the functions of this class.
*
* @since 4.0.0
*
* @param mixed $values This could be anything, but if used properly it usually is a string or an array.
* @return mixed If the preparation was successful, it will return an array of arguments. Otherwise it could be anything.
*/
private function prepArgs( $values ) {
$values = (array) $values;
if ( ! is_array( $values[0] ) && count( $values ) === 2 ) {
$values = [ $values[0] => $values[1] ];
} elseif ( is_array( $values[0] ) && count( $values ) === 1 ) {
$values = $values[0];
}
return $values;
}
/**
* Resets all the variables that make up the query.
*
* @since 4.0.0
*
* @param array $what Set which properties you want to reset. All are selected by default.
* @return Database Returns the Database instance.
*/
public function reset(
$what = [
'table',
'statement',
'limit',
'group',
'order',
'select',
'set',
'onDuplicate',
'ignore',
'where',
'union',
'distinct',
'orderDirection',
'query',
'output',
'stripTags',
'models',
'join'
]
) {
// If we are not running a select query, let's bust the cache for this table.
$selectStatements = [ 'SELECT', 'SELECT DISTINCT' ];
if (
! empty( $this->statement ) &&
! in_array( $this->statement, $selectStatements, true )
) {
$this->bustCache( $this->getCacheTableName() );
}
foreach ( (array) $what as $var ) {
switch ( $var ) {
case 'group':
case 'order':
case 'select':
case 'set':
case 'onDuplicate':
case 'where':
case 'union':
case 'join':
$this->$var = [];
break;
case 'orderDirection':
$this->$var = 'ASC';
break;
case 'ignore':
case 'stripTags':
$this->$var = false;
break;
case 'output':
$this->$var = 'OBJECT';
break;
default:
if ( isset( $this->$var ) ) {
$this->$var = null;
}
break;
}
}
return $this;
}
/**
* Returns the current value of one or more query properties.
*
* @since 4.0.0
*
* @param string|array $what You can pass in an array of options to retrieve. By default it selects all if them.
* @return string|array Returns the value of whichever variables are passed in.
*/
public function getQueryProperty(
$what = [
'table',
'statement',
'limit',
'group',
'order',
'select',
'set',
'onDuplicate',
'where',
'union',
'distinct',
'orderDirection',
'query',
'output',
'result'
]
) {
if ( is_array( $what ) ) {
$return = [];
foreach ( (array) $what as $which ) {
$return[ $which ] = $this->$which;
}
return $return;
}
return $this->$what;
}
/**
* Get a table name for the cache key.
*
* @since 4.1.6
*
* @param string $cacheTableName The table name to check against.
* @return string The cache key table name.
*/
private function getCacheTableName( $cacheTableName = '' ) {
$cacheTableName = empty( $cacheTableName ) ? $this->table : $cacheTableName;
foreach ( $this->customTables as $tableName ) {
if ( false !== stripos( (string) $cacheTableName, $this->prefix . $tableName ) ) {
$cacheTableName = $tableName;
break;
}
}
return $cacheTableName;
}
/**
* Busts the cache for the given table name.
*
* @since 4.1.6
*
* @param string $tableName The table name.
* @return void
*/
public function bustCache( $tableName = '' ) {
if ( ! $tableName ) {
// Bust all the cache.
$this->cache = [];
return;
}
unset( $this->cache[ $tableName ] );
}
/**
* In order to not have a conflict, we need to return a clone.
*
* @since 4.1.0
*
* @return Database The cloned Database instance.
*/
public function noConflict() {
return clone $this;
}
/**
* Checks whether the given index exists on the given table.
*
* @since 4.4.8
*
* @param string $tableName The table name.
* @param string $indexName The index name.
* @param bool $includesPrefix Whether the table name includes the WordPress prefix or not.
* @return bool Whether the index exists or not.
*/
public function indexExists( $tableName, $indexName, $includesPrefix = false ) {
$prefix = $includesPrefix ? '' : $this->prefix;
$tableName = strtolower( $prefix . $tableName );
$indexName = strtolower( $indexName );
$indexes = $this->db->get_results( "SHOW INDEX FROM `$tableName`" );
foreach ( $indexes as $index ) {
if ( empty( $index->Key_name ) ) {
continue;
}
if ( strtolower( $index->Key_name ) === $indexName ) {
return true;
}
}
return false;
}
/**
* Acquires a database lock with the given name.
*
* @since 4.8.3
*
* @param string $lockName The name of the lock to acquire.
* @param integer $timeout The timeout in seconds. Default is 0 which means it will return immediately if the lock cannot be acquired.
* @return boolean Whether the lock was acquired.
*/
public function acquireLock( $lockName, $timeout = 0 ) {
$lockResult = $this->db->get_var( $this->db->prepare( 'SELECT GET_LOCK(%s, %d)', $lockName, $timeout ) );
$acquired = '1' === $lockResult;
if ( $acquired ) {
// Register a shutdown function to always release the lock even if a fatal error occurs.
register_shutdown_function( function () use ( $lockName ) {
$this->releaseLock( $lockName );
} );
}
return $acquired;
}
/**
* Releases a database lock with the given name.
*
* @since 4.8.3
*
* @param string $lockName The name of the lock to release.
* @return boolean Whether the lock was released.
*/
public function releaseLock( $lockName ) {
$releaseResult = $this->db->query( $this->db->prepare( 'SELECT RELEASE_LOCK(%s)', $lockName ) );
return false !== $releaseResult;
}
} Features.php 0000644 00000012002 15154302615 0007030 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Contains helper methods specific to the addons.
*
* @since 4.3.0
*/
class Features {
/**
* The features URL.
*
* @since 4.3.0
*
* @var string
*/
protected $featuresUrl = 'https://licensing-cdn.aioseo.com/keys/lite/all-in-one-seo-pack-pro-features.json';
/**
* Returns our features.
*
* @since 4.3.0
*
* @param boolean $flushCache Whether or not to flush the cache.
* @return array An array of addon data.
*/
public function getFeatures( $flushCache = false ) {
$features = aioseo()->core->networkCache->get( 'license_features' );
if ( null === $features || $flushCache ) {
$response = aioseo()->helpers->wpRemoteGet( $this->getFeaturesUrl() );
if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
$features = json_decode( wp_remote_retrieve_body( $response ), true );
}
if ( ! $features || ! empty( $features->error ) ) {
$features = $this->getDefaultFeatures();
}
aioseo()->core->networkCache->update( 'license_features', $features );
}
// Convert the features array to objects using JSON. This is essential because we have lots of features that rely on this to be an object, and changing it to an array would break them.
$features = json_decode( wp_json_encode( $features ) );
return $features;
}
/**
* Get the URL to get features.
*
* @since 4.1.8
*
* @return string The URL.
*/
protected function getFeaturesUrl() {
$url = $this->featuresUrl;
if ( defined( 'AIOSEO_FEATURES_URL' ) ) {
$url = AIOSEO_FEATURES_URL;
}
return $url;
}
/**
* Retrieves a default list of all external saas features available for the current user if the API cannot be reached.
*
* @since 4.3.0
*
* @return array An array of features.
*/
protected function getDefaultFeatures() {
return json_decode( wp_json_encode( [
[
'license_level' => 'pro',
'section' => 'schema',
'feature' => 'event'
],
[
'license_level' => 'elite',
'section' => 'schema',
'feature' => 'event'
],
[
'license_level' => 'elite',
'section' => 'schema',
'feature' => 'job-posting'
],
[
'license_level' => 'elite',
'section' => 'tools',
'feature' => 'network-tools-site-activation'
],
[
'license_level' => 'elite',
'section' => 'tools',
'feature' => 'network-tools-database'
],
[
'license_level' => 'elite',
'section' => 'tools',
'feature' => 'network-tools-import-export'
],
[
'license_level' => 'elite',
'section' => 'tools',
'feature' => 'network-tools-robots'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'seo-statistics'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'keyword-rankings'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'keyword-rankings-pages'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'content-rankings'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'post-detail'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'post-detail-page-speed'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'post-detail-seo-statistics'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'post-detail-keywords'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'post-detail-focus-keyword-trend'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'keyword-tracking'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'post-detail-keyword-tracking'
],
[
'license_level' => 'elite',
'section' => 'search-statistics',
'feature' => 'index-status'
]
] ), true );
}
/**
* Get the plans for a given feature.
*
* @since 4.3.0
*
* @param string $sectionSlug The section name.
* @param string $feature The feature name.
* @return array The plans for the feature.
*/
public function getPlansForFeature( $sectionSlug, $feature = '' ) {
$plans = [];
// Loop through all the features and find the plans that have access to the feature.
foreach ( $this->getFeatures() as $featureArray ) {
if ( $featureArray->section !== $sectionSlug ) {
continue;
}
if ( ! empty( $feature ) && $featureArray->feature !== $feature ) {
continue;
}
$plans[] = ucfirst( $featureArray->license_level );
}
return array_unique( $plans );
}
} Filesystem.php 0000644 00000014234 15154302615 0007407 0 ustar 00 <?php
// phpcs:disable WordPress.WP.AlternativeFunctions
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Load our manifest to use throughout the app.
*
* @since 4.1.9
*/
class Filesystem {
/**
* Holds the WordPress filesystem object.
*
* @since 4.1.9
*
* @var \WP_Filesystem_Base
*/
public $fs = null;
/**
* Core class instance.
*
* @since 4.2.7
*
* @var \AIOSEO\Plugin\Common\Core\Core
*/
private $core = null;
/**
* Class constructor.
*
* @since 4.1.9
*
* @param \AIOSEO\Plugin\Common\Core\Core $core The AIOSEO Core class.
* @param array $args Any arguments needed to construct the class with.
*/
public function __construct( $core, $args = [] ) {
$this->core = $core;
$this->init( $args );
}
/**
* Initialize the filesystem.
*
* @since 4.1.9
*
* @param array $args An array of arguments for the WP_Filesystem
* @return void
*/
public function init( $args = [] ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
WP_Filesystem( $args );
global $wp_filesystem; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
if ( is_object( $wp_filesystem ) ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
$this->fs = $wp_filesystem; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
}
}
/**
* Wrapper method to check if a file exists.
*
* @since 4.1.9
*
* @param string $filename The filename to check if it exists.
* @return bool Returns true if the file or directory specified by filename exists; false otherwise.
*/
public function exists( $filename ) {
if ( ! $this->isWpfsValid() ) {
return @file_exists( $filename );
}
return $this->fs->exists( $filename );
}
/**
* Retrieve the contents of a file.
*
* @since 4.1.9
*
* @param string $filename The filename to get the contents for.
* @return string|bool The function returns the read data or false on failure.
*/
public function getContents( $filename ) {
if ( ! $this->exists( $filename ) ) {
return false;
}
if ( ! $this->isWpfsValid() ) {
return @file_get_contents( $filename );
}
return $this->fs->get_contents( $filename );
}
/**
* Reads entire file into an array.
*
* @since 4.1.9
*
* @param string $file Path to the file.
* @return array|bool File contents in an array on success, false on failure.
*/
public function getContentsArray( $file ) {
if ( ! $this->exists( $file ) ) {
return false;
}
if ( ! $this->isWpfsValid() ) {
return @file( $file );
}
return $this->fs->get_contents_array( $file );
}
/**
* Sets the access and modification times of a file.
* Note: If $file doesn't exist, it will be created.
*
* @since 4.1.9
*
* @param string $file Path to file.
* @param int $time Optional. Modified time to set for file. Default 0.
* @param int $atime Optional. Access time to set for file. Default 0.
* @return bool True on success, false on failure.
*/
public function touch( $file, $time = 0, $atime = 0 ) {
if ( 0 === $time ) {
$time = time();
}
if ( 0 === $atime ) {
$atime = time();
}
if ( ! $this->isWpfsValid() ) {
return @touch( $file, $time, $atime );
}
return $this->fs->touch( $file, $time, $atime );
}
/**
* Writes a string to a file.
*
* @since 4.1.9
*
* @param string $file Remote path to the file where to write the data.
* @param string $contents The data to write.
* @param int|false $mode Optional. The file permissions as octal number, usually 0644. Default false.
* @return int|bool True on success, false on failure.
*/
public function putContents( $file, $contents, $mode = false ) {
if ( ! $this->isWpfsValid() ) {
return @file_put_contents( $file, $contents );
}
return $this->fs->put_contents( $file, $contents, $mode );
}
/**
* Checks if a file or directory is writable.
*
* @since 4.1.9
*
* @param string $file Path to file or directory.
* @return bool Whether $file is writable.
*/
public function isWritable( $file ) {
if ( ! $this->isWpfsValid() ) {
return @is_writable( $file );
}
return $this->fs->is_writable( $file );
}
/**
* Checks if a file is readable.
*
* @since 4.1.9
*
* @param string $file Path to file.
* @return bool Whether $file is readable.
*/
public function isReadable( $file ) {
if ( ! $this->isWpfsValid() ) {
return @is_readable( $file );
}
return $this->fs->is_readable( $file );
}
/**
* Gets the file size (in bytes).
*
* @since 4.1.9
*
* @param string $file Path to file.
* @return int|bool Size of the file in bytes on success, false on failure.
*/
public function size( $file ) {
if ( ! $this->isWpfsValid() ) {
return @filesize( $file );
}
return $this->fs->size( $file );
}
/**
* Checks if resource is a file.
*
* @since 4.1.9
*
* @param string $file File path.
* @return bool Whether $file is a file.
*/
public function isFile( $file ) {
if ( ! $this->isWpfsValid() ) {
return @is_file( $file );
}
return $this->fs->is_file( $file );
}
/**
* Checks if resource is a directory.
*
* @since 4.1.9
*
* @param string $path Directory path.
* @return bool Whether $path is a directory.
*/
public function isDir( $path ) {
if ( ! $this->isWpfsValid() ) {
return @is_dir( $path );
}
return $this->fs->is_dir( $path );
}
/**
* A simple check to ensure that the WP_Filesystem is valid.
*
* @since 4.1.9
*
* @return bool True if valid, false if not.
*/
public function isWpfsValid() {
if (
! is_a( $this->fs, 'WP_Filesystem_Base' ) ||
(
// Errors is a WP_Error object.
! empty( $this->fs->errors ) &&
// We directly check if the errors array is empty for compatibility with WP < 5.1.
! empty( $this->fs->errors->errors )
)
) {
return false;
}
return true;
}
/**
* In order to not have a conflict, we need to return a clone.
*
* @since 4.1.9
*
* @return Filesystem The cloned Filesystem object.
*/
public function noConflict() {
return clone $this;
}
} NetworkCache.php 0000644 00000005416 15154302615 0007642 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles our network cache.
*
* @since 4.2.5
*/
class NetworkCache extends Cache {
/**
* Returns the cache value for a key if it exists and is not expired.
*
* @since 4.2.5
*
* @param string $key The cache key name. Use a '%' for a like query.
* @param bool|array $allowedClasses Whether to allow objects to be returned.
* @return mixed The value or null if the cache does not exist.
*/
public function get( $key, $allowedClasses = false ) {
if ( ! aioseo()->helpers->isPluginNetworkActivated() ) {
return parent::get( $key, $allowedClasses );
}
aioseo()->helpers->switchToBlog( aioseo()->helpers->getNetworkId() );
$value = parent::get( $key, $allowedClasses );
aioseo()->helpers->restoreCurrentBlog();
return $value;
}
/**
* Updates the given cache or creates it if it doesn't exist.
*
* @since 4.2.5
*
* @param string $key The cache key name.
* @param mixed $value The value.
* @param int $expiration The expiration time in seconds. Defaults to 24 hours. 0 to no expiration.
* @return void
*/
public function update( $key, $value, $expiration = DAY_IN_SECONDS ) {
if ( ! aioseo()->helpers->isPluginNetworkActivated() ) {
parent::update( $key, $value, $expiration );
return;
}
aioseo()->helpers->switchToBlog( aioseo()->helpers->getNetworkId() );
parent::update( $key, $value, $expiration );
aioseo()->helpers->restoreCurrentBlog();
}
/**
* Deletes the given cache key.
*
* @since 4.2.5
*
* @param string $key The cache key.
* @return void
*/
public function delete( $key ) {
if ( ! aioseo()->helpers->isPluginNetworkActivated() ) {
parent::delete( $key );
return;
}
aioseo()->helpers->switchToBlog( aioseo()->helpers->getNetworkId() );
parent::delete( $key );
aioseo()->helpers->restoreCurrentBlog();
}
/**
* Clears all of our cache.
*
* @since 4.2.5
*
* @return void
*/
public function clear() {
if ( ! aioseo()->helpers->isPluginNetworkActivated() ) {
parent::clear();
return;
}
aioseo()->helpers->switchToBlog( aioseo()->helpers->getNetworkId() );
parent::clear();
aioseo()->helpers->restoreCurrentBlog();
}
/**
* Clears all of our cache under a certain prefix.
*
* @since 4.2.5
*
* @param string $prefix A prefix to clear or empty to clear everything.
* @return void
*/
public function clearPrefix( $prefix ) {
if ( ! aioseo()->helpers->isPluginNetworkActivated() ) {
parent::clearPrefix( $prefix );
return;
}
aioseo()->helpers->switchToBlog( aioseo()->helpers->getNetworkId() );
parent::clearPrefix( $prefix );
aioseo()->helpers->restoreCurrentBlog();
}
} Tags.php 0000644 00000124307 15154302615 0006164 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to replace tag values with their data counterparts.
*
* @since 4.0.0
*/
class Tags {
/**
* An array of tag values that we support.
*
* @since 4.0.0
*
* @var array
*/
public $tags = [];
/**
* Specifies the denotation character for the tags.
*
* @since 4.0.0
*
* @var string
*/
public $denotationChar = '#';
/**
* An array of contexts to separate tags.
*
* @since 4.0.0
*
* @var array
*/
private $context = [
'authorDescription' => [
'author_bio',
'author_first_name',
'author_last_name',
'author_name',
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'separator_sa',
'site_title',
'tagline'
],
'authorTitle' => [
'author_first_name',
'author_last_name',
'author_name',
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'separator_sa',
'site_title',
'tagline'
],
'descriptionFormat' => [
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'description',
'post_date',
'post_month',
'post_title',
'post_year',
'separator_sa',
'site_title',
'tagline'
],
'dateDescription' => [
'archive_date',
'archive_title',
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'post_day',
'post_month',
'post_year',
'separator_sa',
'site_title',
'tagline'
],
'dateTitle' => [
'archive_date',
'archive_title',
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'post_day',
'post_month',
'post_year',
'separator_sa',
'site_title',
'tagline'
],
'homePage' => [
'author_first_name',
'author_last_name',
'author_name',
'current_date',
'current_day',
'current_month',
'current_year',
'post_date',
'post_day',
'post_excerpt_only',
'post_excerpt',
'post_month',
'post_title',
'post_year',
'separator_sa',
'site_title',
'tagline'
],
'knowledgeGraph' => [
'separator_sa',
'site_title',
'tagline'
],
'pagedFormat' => [
'page_number',
'separator_sa'
],
'postDescription' => [
'author_first_name',
'author_last_name',
'author_name',
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'permalink',
'post_content',
'post_date',
'post_day',
'post_excerpt_only',
'post_excerpt',
'post_month',
'post_title',
'post_year',
'separator_sa',
'site_title',
'tagline',
'tax_name',
'taxonomy_title'
],
'postTitle' => [
'author_first_name',
'author_last_name',
'author_name',
'categories',
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'permalink',
'post_content',
'post_date',
'post_day',
'post_excerpt_only',
'post_excerpt',
'post_month',
'post_title',
'post_year',
'separator_sa',
'site_title',
'tagline',
'tax_name',
'taxonomy_title'
],
'rss' => [
'author_link',
'author_link_alt',
'author_name',
'featured_image',
'post_date',
'post_link',
'post_link_alt',
'post_title',
'site_link',
'site_link_alt',
'site_title',
'taxonomy_title'
],
'schema' => [
'author_first_name',
'author_last_name',
'author_name',
'author_url',
'categories',
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'permalink',
'post_content',
'post_date',
'post_day',
'post_excerpt_only',
'post_excerpt',
'post_month',
'post_title',
'post_year',
'separator_sa',
'site_title',
'tagline',
'tax_name',
'taxonomy_title'
],
'searchDescription' => [
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'search_term',
'separator_sa',
'site_title',
'tagline'
],
'searchTitle' => [
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'search_term',
'separator_sa',
'site_title',
'tagline'
],
'siteDescription' => [
'current_date',
'current_day',
'current_month',
'current_year',
'permalink',
'post_date',
'post_day',
'post_month',
'post_year',
'search_term',
'separator_sa',
'tagline'
],
'siteTitle' => [
'current_date',
'current_day',
'current_month',
'current_year',
'permalink',
'post_date',
'post_day',
'post_month',
'post_year',
'search_term',
'separator_sa',
'tagline'
],
'taxonomyDescription' => [
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'permalink',
'separator_sa',
'site_title',
'tagline',
'taxonomy_description',
'taxonomy_title'
],
'taxonomyTitle' => [
'current_date',
'current_day',
'current_month',
'current_year',
'custom_field',
'permalink',
'separator_sa',
'site_title',
'tagline',
'tax_parent_name',
'taxonomy_description',
'taxonomy_title'
]
];
/**
* Class constructor.
*
* @since 4.0.0
*/
public function __construct() {
// Tags need to be registered on wp_loaded instead of init to ensure these are available during block rendering.
add_action( 'wp_loaded', [ $this, 'registerTags' ] );
}
/**
* Register the tags.
*
* @since 4.7.6
*
* @return void
*/
public function registerTags() {
$this->tags = array_merge( $this->tags, [
[
'id' => 'alt_tag',
'name' => __( 'Image Alt Tag', 'all-in-one-seo-pack' ),
'description' => __( 'Your image\'s alt tag attribute.', 'all-in-one-seo-pack' )
],
[
'id' => 'attachment_caption',
'name' => __( 'Media Caption', 'all-in-one-seo-pack' ),
'description' => __( 'Caption for the current media file.', 'all-in-one-seo-pack' )
],
[
'id' => 'attachment_description',
'name' => __( 'Media Description', 'all-in-one-seo-pack' ),
'description' => __( 'Description for the current media file.', 'all-in-one-seo-pack' )
],
[
'id' => 'archive_date',
'name' => __( 'Archive Date', 'all-in-one-seo-pack' ),
'description' => __( 'The date of the current archive, localized.', 'all-in-one-seo-pack' )
],
[
'id' => 'author_link',
'name' => __( 'Author Link', 'all-in-one-seo-pack' ),
'description' => __( 'Author archive link (name as text).', 'all-in-one-seo-pack' )
],
[
'id' => 'author_link_alt',
'name' => __( 'Author Link (Alt)', 'all-in-one-seo-pack' ),
'description' => __( 'Author archive link (link as text).', 'all-in-one-seo-pack' )
],
[
'id' => 'author_bio',
'name' => __( 'Author Biography', 'all-in-one-seo-pack' ),
'description' => __( 'The biography of the author.', 'all-in-one-seo-pack' )
],
[
'id' => 'author_name',
'name' => __( 'Author Name', 'all-in-one-seo-pack' ),
'description' => __( 'The display name of the post author.', 'all-in-one-seo-pack' )
],
[
'id' => 'author_first_name',
'name' => __( 'Author First Name', 'all-in-one-seo-pack' ),
'description' => __( 'The first name of the post author.', 'all-in-one-seo-pack' )
],
[
'id' => 'author_last_name',
'name' => __( 'Author Last Name', 'all-in-one-seo-pack' ),
'description' => __( 'The last name of the post author.', 'all-in-one-seo-pack' )
],
[
'id' => 'author_url',
'name' => __( 'Author URL', 'all-in-one-seo-pack' ),
'description' => __( 'The URL of the author page.', 'all-in-one-seo-pack' )
],
[
'id' => 'archive_title',
'name' => __( 'Archive Title', 'all-in-one-seo-pack' ),
'description' => __( 'The title of the current archive.', 'all-in-one-seo-pack' )
],
[
'id' => 'blog_link',
'name' => __( 'Site Link', 'all-in-one-seo-pack' ),
'description' => __( 'Site link (link as text).', 'all-in-one-seo-pack' ),
'html' => true
],
[
'id' => 'blog_title',
'name' => __( 'Site Title', 'all-in-one-seo-pack' ),
'description' => __( 'Your site title.', 'all-in-one-seo-pack' ),
'deprecated' => true
],
[
'id' => 'category',
'name' => __( 'Category', 'all-in-one-seo-pack' ),
'description' => __( 'Current or first category title.', 'all-in-one-seo-pack' ),
'deprecated' => true
],
[
'id' => 'categories',
'name' => __( 'Categories', 'all-in-one-seo-pack' ),
'description' => __( 'All categories that are assigned to the current post, comma-separated.', 'all-in-one-seo-pack' )
],
[
'id' => 'category_link',
// Translators: 1 - The type of page (Post, Page, Category, Tag, etc.).
'name' => sprintf( __( '%1$s Link', 'all-in-one-seo-pack' ), 'Category' ),
'description' => __( 'Current or first term link (name as text).', 'all-in-one-seo-pack' ),
'html' => true
],
[
'id' => 'category_link_alt',
// Translators: 1 - The type of page (Post, Page, Category, Tag, etc.).
'name' => sprintf( __( '%1$s Link (Alt)', 'all-in-one-seo-pack' ), 'Category' ),
'description' => __( 'Current or first term link (link as text).', 'all-in-one-seo-pack' ),
'html' => true
],
[
'id' => 'current_date',
'name' => __( 'Current Date', 'all-in-one-seo-pack' ),
'description' => __( 'The current date, localized.', 'all-in-one-seo-pack' )
],
[
'id' => 'current_day',
'name' => __( 'Current Day', 'all-in-one-seo-pack' ),
'description' => __( 'The current day of the month, localized.', 'all-in-one-seo-pack' )
],
[
'id' => 'current_month',
'name' => __( 'Current Month', 'all-in-one-seo-pack' ),
'description' => __( 'The current month, localized.', 'all-in-one-seo-pack' )
],
[
'id' => 'current_year',
'name' => __( 'Current Year', 'all-in-one-seo-pack' ),
'description' => __( 'The current year, localized.', 'all-in-one-seo-pack' )
],
[
'id' => 'custom_field',
'name' => __( 'Custom Field', 'all-in-one-seo-pack' ),
'description' => __( 'A custom field from the current page/post.', 'all-in-one-seo-pack' ),
'custom' => true
],
[
'id' => 'description',
'name' => __( 'Description', 'all-in-one-seo-pack' ),
'description' => __( 'The meta description for the current page/post.', 'all-in-one-seo-pack' )
],
[
'id' => 'featured_image',
'name' => __( 'Featured Image', 'all-in-one-seo-pack' ),
'description' => __( 'The featured image of the current page/post.', 'all-in-one-seo-pack' )
],
[
'id' => 'page_number',
'name' => __( 'Page Number', 'all-in-one-seo-pack' ),
'description' => __( 'The page number for the current paginated page.', 'all-in-one-seo-pack' )
],
[
'id' => 'parent_title',
'name' => __( 'Parent Title', 'all-in-one-seo-pack' ),
'description' => __( 'The title of the parent post of the current page/post.', 'all-in-one-seo-pack' )
],
[
'id' => 'permalink',
'name' => __( 'Permalink', 'all-in-one-seo-pack' ),
'description' => __( 'The permalink for the current page/post.', 'all-in-one-seo-pack' )
],
[
'id' => 'post_content',
// Translators: 1 - The singular name of the post type.
'name' => sprintf( __( '%1$s Content', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'The content of your page/post.', 'all-in-one-seo-pack' )
],
[
'id' => 'post_date',
// Translators: 1 - The singular name of the post type.
'name' => sprintf( __( '%1$s Date', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'The date when the page/post was published, localized.', 'all-in-one-seo-pack' )
],
[
'id' => 'post_day',
// Translators: 1 - The singular name of the post type.
'name' => sprintf( __( '%1$s Day', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'The day of the month when the page/post was published, localized.', 'all-in-one-seo-pack' )
],
[
'id' => 'post_excerpt',
// Translators: 1 - The singular name of the post type.
'name' => sprintf( __( '%1$s Excerpt', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'The excerpt defined on your page/post.', 'all-in-one-seo-pack' )
],
[
'id' => 'post_excerpt_only',
// Translators: 1 - The singular name of the post type.
'name' => sprintf( __( '%1$s Excerpt Only', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'The excerpt defined on your page/post. Will not fall back to the post content.', 'all-in-one-seo-pack' )
],
[
'id' => 'post_month',
// Translators: 1 - The singular name of the post type.
'name' => sprintf( __( '%1$s Month', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'The month when the page/post was published, localized.', 'all-in-one-seo-pack' )
],
[
'id' => 'post_year',
// Translators: 1 - The singular name of the post type.
'name' => sprintf( __( '%1$s Year', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'The year when the page/post was published, localized.', 'all-in-one-seo-pack' )
],
[
'id' => 'post_link',
// Translators: 1 - The type of page (Post, Page, Category, Tag, etc.).
'name' => sprintf( __( '%1$s Link', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'Post link (name as anchor text).', 'all-in-one-seo-pack' ),
'html' => true
],
[
'id' => 'post_link_alt',
// Translators: 1 - The type of page (Post, Page, Category, Tag, etc.).
'name' => sprintf( __( '%1$s Link (Alt)', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'Post link (link as anchor text).', 'all-in-one-seo-pack' ),
'html' => true
],
[
'id' => 'post_title',
// Translators: 1 - The type of page (Post, Page, Category, Tag, etc.).
'name' => sprintf( __( '%1$s Title', 'all-in-one-seo-pack' ), 'Post' ),
'description' => __( 'The original title of the current post.', 'all-in-one-seo-pack' )
],
[
'id' => 'search_term',
'name' => __( 'Search Term', 'all-in-one-seo-pack' ),
'description' => __( 'The term the user is searching for.', 'all-in-one-seo-pack' )
],
[
'id' => 'separator_sa',
'name' => __( 'Separator', 'all-in-one-seo-pack' ),
'description' => __( 'The separator defined in the search appearance settings.', 'all-in-one-seo-pack' )
],
[
'id' => 'site_description',
'name' => __( 'Site Description', 'all-in-one-seo-pack' ),
'description' => __( 'The description for your site.', 'all-in-one-seo-pack' ),
'deprecated' => true
],
[
'id' => 'site_link',
'name' => __( 'Site Link', 'all-in-one-seo-pack' ),
'description' => __( 'Site link (name as text).', 'all-in-one-seo-pack' ),
'html' => true
],
[
'id' => 'site_link_alt',
'name' => __( 'Site Link (Alt)', 'all-in-one-seo-pack' ),
'description' => __( 'Site link (link as text).', 'all-in-one-seo-pack' ),
'html' => true
],
[
'id' => 'site_title',
'name' => __( 'Site Title', 'all-in-one-seo-pack' ),
'description' => __( 'Your site title.', 'all-in-one-seo-pack' ),
'html' => true
],
[
'id' => 'tagline',
'name' => __( 'Tagline', 'all-in-one-seo-pack' ),
'description' => __( 'The tagline for your site, set in the general settings.', 'all-in-one-seo-pack' )
],
[
'id' => 'tax_name',
'name' => __( 'Taxonomy Name', 'all-in-one-seo-pack' ),
'description' => __( 'The name of the first term of a given taxonomy that is assigned to the current page/post.', 'all-in-one-seo-pack' ),
'custom' => true
],
[
'id' => 'tax_parent_name',
'name' => __( 'Parent Term', 'all-in-one-seo-pack' ),
'description' => __( 'The name of the parent term of the current term.', 'all-in-one-seo-pack' ),
],
[
'id' => 'taxonomy_description',
// Translators: 1 - The singular name of the current taxonomy.
'name' => sprintf( __( '%1$s Description', 'all-in-one-seo-pack' ), 'Category' ),
'description' => __( 'The description of the primary term, first assigned term or the current term.', 'all-in-one-seo-pack' )
],
[
'id' => 'taxonomy_title',
// Translators: 1 - The type of page (Post, Page, Category, Tag, etc.).
'name' => sprintf( __( '%1$s Title', 'all-in-one-seo-pack' ), 'Category' ),
'description' => __( 'The title of the primary term, first assigned term or the current term.', 'all-in-one-seo-pack' )
]
] );
}
/**
* Returns all the tags.
*
* @since 4.0.0
*
* @param bool $sampleData Whether or not to fill empty values with sample data.
* @return array An array of tags.
*/
public function all( $sampleData = false ) {
$tags = $this->tags;
foreach ( $tags as $key => $tag ) {
$tags[ $key ]['value'] = ( $tag['instance'] ?? null )
? $tag['instance']->getTagValue( $tag, null, $sampleData )
: $this->getTagValue( $tag, null, $sampleData );
}
usort( $tags, function ( $a, $b ) {
return $a['name'] < $b['name']
? -1
: ( $a['name'] > $b['name'] ? 1 : 0 );
} );
return [
'tags' => $tags,
'context' => $this->getContext()
];
}
/**
* Add the context for all the post/page types.
*
* @since 4.0.0
*
* @return array An array of contextual data.
*/
public function getContext() {
$context = $this->context;
// Post types including CPT's.
foreach ( aioseo()->helpers->getPublicPostTypes() as $postType ) {
if (
'post' === $postType['name'] ||
! empty( $postType['buddyPress'] )
) {
continue;
}
if ( $postType['hasArchive'] ) {
$context[ $postType['name'] . 'ArchiveTitle' ] = $context['dateTitle'];
$context[ $postType['name'] . 'ArchiveDescription' ] = $context['dateDescription'];
}
$context[ $postType['name'] . 'Title' ] = $context['postTitle'];
$context[ $postType['name'] . 'Description' ] = $context['postDescription'];
// Check if the post type has an excerpt.
if ( empty( $postType['supports']['excerpt'] ) ) {
$phpTitleKey = array_search( 'post_excerpt', $context[ $postType['name'] . 'Title' ], true );
if ( false !== $phpTitleKey ) {
unset( $context[ $postType['name'] . 'Title' ][ $phpTitleKey ] );
}
$phpTitleKey = array_search( 'post_excerpt_only', $context[ $postType['name'] . 'Title' ], true );
if ( false !== $phpTitleKey ) {
unset( $context[ $postType['name'] . 'Title' ][ $phpTitleKey ] );
}
$phpDescriptionKey = array_search( 'post_excerpt', $context[ $postType['name'] . 'Description' ], true );
if ( false !== $phpDescriptionKey ) {
unset( $context[ $postType['name'] . 'Description' ][ $phpDescriptionKey ] );
}
$phpDescriptionKey = array_search( 'post_excerpt_only', $context[ $postType['name'] . 'Description' ], true );
if ( false !== $phpDescriptionKey ) {
unset( $context[ $postType['name'] . 'Description' ][ $phpDescriptionKey ] );
}
asort( $context[ $postType['name'] . 'Title' ] );
$context[ $postType['name'] . 'Title' ] = array_values( $context[ $postType['name'] . 'Title' ] );
asort( $context[ $postType['name'] . 'Description' ] );
$context[ $postType['name'] . 'Description' ] = array_values( $context[ $postType['name'] . 'Description' ] );
}
if ( 'page' === $postType['name'] ) {
$phpTitleKey = array_search( 'taxonomy_title', $context['pageTitle'], true );
if ( false !== $phpTitleKey ) {
unset( $context['pageTitle'][ $phpTitleKey ] );
}
$phpTitleKey = array_search( 'category', $context['pageTitle'], true );
if ( false !== $phpTitleKey ) {
unset( $context['pageTitle'][ $phpTitleKey ] );
}
$phpDescriptionKey = array_search( 'taxonomy_title', $context['pageDescription'], true );
if ( false !== $phpDescriptionKey ) {
unset( $context['pageDescription'][ $phpDescriptionKey ] );
}
$phpDescriptionKey = array_search( 'category', $context['pageDescription'], true );
if ( false !== $phpDescriptionKey ) {
unset( $context['pageDescription'][ $phpDescriptionKey ] );
}
$context['pageTitle'] = array_values( $context['pageTitle'] );
$context['pageDescription'] = array_values( $context['pageDescription'] );
asort( $context['pageTitle'] );
$context['pageTitle'] = array_values( $context['pageTitle'] );
asort( $context['pageDescription'] );
$context['pageDescription'] = array_values( $context['pageDescription'] );
}
if ( 'attachment' === $postType['name'] ) {
$context['attachmentTitle'][] = 'alt_tag';
asort( $context['attachmentTitle'] );
$context['attachmentTitle'] = array_values( $context['attachmentTitle'] );
$context['attachmentDescription'][] = 'alt_tag';
asort( $context['attachmentDescription'] );
$context['attachmentDescription'] = array_values( $context['attachmentDescription'] );
$phpTitleKey = array_search( 'taxonomy_title', $context['attachmentTitle'], true );
if ( false !== $phpTitleKey ) {
unset( $context['attachmentTitle'][ $phpTitleKey ] );
}
$phpTitleKey = array_search( 'post_content', $context['attachmentTitle'], true );
if ( false !== $phpTitleKey ) {
unset( $context['attachmentTitle'][ $phpTitleKey ] );
}
$phpTitleKey = array_search( 'post_excerpt', $context['attachmentTitle'], true );
if ( false !== $phpTitleKey ) {
unset( $context['attachmentTitle'][ $phpTitleKey ] );
}
$phpTitleKey = array_search( 'post_excerpt_only', $context['attachmentTitle'], true );
if ( false !== $phpTitleKey ) {
unset( $context['attachmentTitle'][ $phpTitleKey ] );
}
$phpDescriptionKey = array_search( 'taxonomy_title', $context['attachmentDescription'], true );
if ( false !== $phpDescriptionKey ) {
unset( $context['attachmentDescription'][ $phpDescriptionKey ] );
}
$phpDescriptionKey = array_search( 'post_content', $context['attachmentDescription'], true );
if ( false !== $phpDescriptionKey ) {
unset( $context['attachmentDescription'][ $phpDescriptionKey ] );
}
$phpDescriptionKey = array_search( 'post_excerpt', $context['attachmentDescription'], true );
if ( false !== $phpDescriptionKey ) {
unset( $context['attachmentDescription'][ $phpDescriptionKey ] );
}
$phpDescriptionKey = array_search( 'post_excerpt_only', $context['attachmentDescription'], true );
if ( false !== $phpDescriptionKey ) {
unset( $context['attachmentDescription'][ $phpDescriptionKey ] );
}
$context['attachmentTitle'] = array_merge( $context['attachmentTitle'], [ 'attachment_caption', 'attachment_description' ] );
$context['attachmentDescription'] = array_merge( $context['attachmentDescription'], [ 'attachment_caption', 'attachment_description' ] );
asort( $context['attachmentTitle'] );
$context['attachmentTitle'] = array_values( $context['attachmentTitle'] );
asort( $context['attachmentDescription'] );
$context['attachmentDescription'] = array_values( $context['attachmentDescription'] );
}
if ( ! in_array( 'category', get_object_taxonomies( $postType['name'] ), true ) ) {
$phpTitleKey = array_search( 'categories', $context[ $postType['name'] . 'Title' ], true );
if ( false !== $phpTitleKey ) {
unset( $context[ $postType['name'] . 'Title' ][ $phpTitleKey ] );
}
$phpTitleKey = array_search( 'categories', $context[ $postType['name'] . 'Description' ], true );
if ( false !== $phpTitleKey ) {
unset( $context[ $postType['name'] . 'Description' ][ $phpTitleKey ] );
}
asort( $context[ $postType['name'] . 'Title' ] );
$context[ $postType['name'] . 'Title' ] = array_values( $context[ $postType['name'] . 'Title' ] );
asort( $context[ $postType['name'] . 'Description' ] );
$context[ $postType['name'] . 'Description' ] = array_values( $context[ $postType['name'] . 'Description' ] );
}
if ( $postType['hierarchical'] ) {
$context[ $postType['name'] . 'Title' ][] = 'parent_title';
}
}
// Taxonomies including from CPT's.
foreach ( aioseo()->helpers->getPublicTaxonomies() as $taxonomy ) {
$context[ $taxonomy['name'] . 'Title' ] = $context['taxonomyTitle'];
$context[ $taxonomy['name'] . 'Description' ] = $context['taxonomyDescription'];
}
return $context;
}
/**
* Replace the tags in the string provided.
*
* @since 4.0.0
*
* @param string $string The string to look for tags in.
* @param int $id The page or post ID.
* @return string The string with tags replaced.
*/
public function replaceTags( $string, $id = 0 ) {
if ( ! $string || ! preg_match( '/' . $this->denotationChar . '/', (string) $string ) ) {
return $string;
}
foreach ( $this->tags as $tag ) {
if ( 'custom_field' === $tag['id'] || 'tax_name' === $tag['id'] ) {
continue;
}
$tagId = $this->denotationChar . $tag['id'];
// Pattern explained: Exact match of tag, not followed by any additional letter, number or underscore.
// This allows us to have tags like: #post_link and #post_link_alt
// and it will always replace the correct one.
$pattern = "/$tagId(?![a-zA-Z0-9_])/im";
if ( preg_match( $pattern, (string) $string ) ) {
$tagValue = $this->getTagValue( $tag, $id );
$string = preg_replace( $pattern, '%|%' . aioseo()->helpers->escapeRegexReplacement( $tagValue ), (string) $string );
}
}
$string = $this->parseTaxonomyNames( $string, $id );
// Custom fields are parsed separately.
$string = $this->parseCustomFields( $string, $id );
return preg_replace( '/%\|%/im', '', (string) $string );
}
/**
* Get the value of the tag to replace.
*
* @since 4.0.0
*
* @param array $tag The tag to look for.
* @param int|null $id The post ID.
* @param bool $sampleData Whether or not to fill empty values with sample data.
* @return mixed The value of the tag.
*/
public function getTagValue( $tag, $id, $sampleData = false ) {
$author = new \WP_User();
$post = aioseo()->helpers->getPost( $id );
$postId = null;
$category = null;
if ( $post ) {
$author = new \WP_User( $post->post_author );
$postId = empty( $id ) ? $post->ID : $id;
$category = get_the_category( $postId );
} elseif ( is_author() && is_a( get_queried_object(), 'WP_User' ) ) {
$author = get_queried_object();
}
switch ( $tag['id'] ) {
case 'alt_tag':
return empty( $id )
? ( $sampleData ? __( 'A sample alt tag for your image', 'all-in-one-seo-pack' ) : '' )
: get_post_meta( $id, '_wp_attachment_image_alt', true );
case 'archive_date':
$date = null;
if ( is_year() ) {
$date = get_the_date( 'Y' );
}
if ( is_month() ) {
$date = get_the_date( 'F, Y' );
}
if ( is_day() ) {
$date = get_the_date();
}
if ( $sampleData ) {
$date = $this->formatDateAsI18n( date_i18n( 'U' ) );
}
if ( ! empty( $date ) ) {
return $date;
}
break;
case 'archive_title':
$title = is_post_type_archive() ? post_type_archive_title( '', false ) : get_the_archive_title();
return $sampleData ? __( 'Sample Archive Title', 'all-in-one-seo-pack' ) : wp_strip_all_tags( $title );
case 'author_bio':
$bio = get_the_author_meta( 'description', $author->ID );
return empty( $bio ) && $sampleData ? __( 'Sample author biography', 'all-in-one-seo-pack' ) : $bio;
case 'author_first_name':
$name = $author->first_name;
return empty( $name ) && $sampleData ? wp_get_current_user()->first_name : $author->first_name;
case 'author_last_name':
$name = $author->last_name;
return empty( $name ) && $sampleData ? wp_get_current_user()->last_name : $author->last_name;
case 'author_link':
return '<a href="' . esc_url( get_author_posts_url( $author->ID ) ) . '">' . esc_html( $author->display_name ) . '</a>';
case 'author_link_alt':
return '<a href="' . esc_url( get_author_posts_url( $author->ID ) ) . '">' . esc_url( get_author_posts_url( $author->ID ) ) . '</a>';
case 'author_name':
$name = $author->display_name;
return empty( $name ) && $sampleData ? wp_get_current_user()->display_name : $author->display_name;
case 'author_url':
$authorUrl = get_author_posts_url( $author->ID );
return ! empty( $authorUrl ) ? $authorUrl : '';
case 'attachment_caption':
$caption = wp_get_attachment_caption( $postId );
return empty( $caption ) && $sampleData ? __( 'Sample caption for media.', 'all-in-one-seo-pack' ) : $caption;
case 'attachment_description':
$description = ! empty( $post->post_content ) ? $post->post_content : '';
return empty( $description ) && $sampleData ? __( 'Sample description for media.', 'all-in-one-seo-pack' ) : $description;
case 'categories':
if ( ! is_object( $post ) || 'post' !== $post->post_type ) {
return ! is_object( $post ) && $sampleData ? __( 'Sample Category 1, Sample Category 2', 'all-in-one-seo-pack' ) : '';
}
$categories = get_the_terms( $post->ID, 'category' );
$names = [];
if ( ! is_array( $categories ) ) {
return '';
}
foreach ( $categories as $category ) {
$names[] = $category->name;
}
return implode( ', ', $names );
case 'category_link':
return '<a href="' . esc_url( get_category_link( $category ) ) . '">' . ( $category ? $category[0]->name : '' ) . '</a>';
case 'category_link_alt':
return '<a href="' . esc_url( get_category_link( $category ) ) . '">' . esc_url( get_category_link( $category ) ) . '</a>';
case 'current_date':
return $this->formatDateAsI18n( date_i18n( 'U' ) );
case 'current_day':
return date_i18n( 'd' );
case 'current_month':
return date_i18n( 'F' );
case 'current_year':
return date_i18n( 'Y' );
case 'custom_field':
return $sampleData ? __( 'Sample Custom Field Value', 'all-in-one-seo-pack' ) : '';
case 'featured_image':
if ( ! has_post_thumbnail( $postId ) ) {
return $sampleData ? __( 'Sample featured image', 'all-in-one-seo-pack' ) : '';
}
$imageId = get_post_thumbnail_id( $postId );
$image = (array) wp_get_attachment_image_src( $imageId, 'full' );
$image = isset( $image[0] ) ? '<img src="' . $image[0] . '" style="display: block; margin: 1em auto">' : ''; // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
return $sampleData ? __( 'Sample featured image', 'all-in-one-seo-pack' ) : $image;
case 'page_number':
return aioseo()->helpers->getPageNumber();
case 'parent_title':
if ( ! is_object( $post ) || ! $post->post_parent ) {
return ! is_object( $post ) && $sampleData ? __( 'Sample Parent', 'all-in-one-seo-pack' ) : '';
}
$parent = get_post( $post->post_parent );
return $parent ? $parent->post_title : '';
case 'permalink':
return aioseo()->helpers->getUrl();
case 'post_date':
$date = $this->formatDateAsI18n( get_the_date( 'U' ) );
return empty( $date ) && $sampleData ? $this->formatDateAsI18n( date_i18n( 'U' ) ) : $date;
case 'post_day':
$day = get_the_date( 'd', $post );
return empty( $day ) && $sampleData ? date_i18n( 'd' ) : $day;
case 'post_excerpt_only':
return empty( $postId ) ? ( $sampleData ? __( 'Sample excerpt from a page/post.', 'all-in-one-seo-pack' ) : '' ) : $post->post_excerpt;
case 'post_excerpt':
if ( empty( $postId ) ) {
return $sampleData ? __( 'Sample excerpt from a page/post.', 'all-in-one-seo-pack' ) : '';
}
if ( $post->post_excerpt ) {
return $post->post_excerpt;
}
// Fall through if the post doesn't have an excerpt set. In that case getDescriptionFromContent() will generate it for us.
case 'post_content':
return empty( $postId ) ? ( $sampleData ? __( 'An example of content from your page/post.', 'all-in-one-seo-pack' ) : '' ) : aioseo()->helpers->getDescriptionFromContent( $post );
case 'post_link':
return '<a href="' . esc_url( get_permalink( $post ) ) . '">' . esc_html( get_the_title( $post ) ) . '</a>';
case 'post_link_alt':
return '<a href="' . esc_url( get_permalink( $post ) ) . '">' . esc_url( get_permalink( $post ) ) . '</a>';
case 'post_month':
$month = get_the_date( 'F', $post );
return empty( $month ) && $sampleData ? date_i18n( 'F' ) : $month;
case 'post_title':
$title = esc_html( get_the_title( $post ) );
return empty( $title ) && $sampleData ? __( 'Sample Post', 'all-in-one-seo-pack' ) : $title;
case 'post_year':
$year = get_the_date( 'Y', $post );
return empty( $year ) && $sampleData ? date_i18n( 'Y' ) : $year;
case 'search_term':
$search = get_search_query();
return empty( $search ) && $sampleData ? __( 'Example search string', 'all-in-one-seo-pack' ) : esc_attr( stripslashes( $search ) );
case 'separator_sa':
return aioseo()->helpers->decodeHtmlEntities( aioseo()->options->searchAppearance->global->separator );
case 'site_link':
case 'blog_link':
return '<a href="' . esc_url( get_bloginfo( 'url' ) ) . '">' . esc_html( get_bloginfo( 'name' ) ) . '</a>';
case 'site_link_alt':
return '<a href="' . esc_url( get_bloginfo( 'url' ) ) . '">' . esc_url( get_bloginfo( 'url' ) ) . '</a>';
case 'tag':
return single_term_title( '', false );
case 'tax_name':
return $sampleData ? __( 'Sample Taxonomy Name Value', 'all-in-one-seo-pack' ) : '';
case 'tax_parent_name':
$termObject = get_term( $id ); // Don't use the getTerm() helper here. We need the actual Product Attribute tax.
$parentTermObject = ! empty( $termObject->parent ) ? aioseo()->helpers->getTerm( $termObject->parent ) : '';
$name = $parentTermObject->name ?? '';
if (
is_a( $termObject, 'WP_Term' ) &&
empty( $parentTermObject ) &&
aioseo()->helpers->isWooCommerceProductAttribute( $termObject->taxonomy )
) {
$wcAttributeTaxonomiesTable = aioseo()->core->db->prefix . 'woocommerce_attribute_taxonomies';
$attributeName = str_replace( 'pa_', '', $termObject->taxonomy );
$result = aioseo()->core->db->db->get_row(
aioseo()->core->db->db->prepare(
"SELECT attribute_label FROM $wcAttributeTaxonomiesTable WHERE attribute_name = %s",
$attributeName
)
);
return $result->attribute_label ?? '';
}
return $sampleData ? __( 'Sample Parent Term Name', 'all-in-one-seo-pack' ) : $name;
case 'taxonomy_description':
$description = term_description();
return empty( $description ) && $sampleData ? __( 'Sample taxonomy description', 'all-in-one-seo-pack' ) : $description;
case 'taxonomy_title':
case 'category':
$title = $this->getTaxonomyTitle( $postId );
return ! $title && $sampleData ? __( 'Sample Taxonomy Title', 'all-in-one-seo-pack' ) : $title;
case 'site_description':
case 'blog_description':
case 'tagline':
return aioseo()->helpers->decodeHtmlEntities( get_bloginfo( 'description' ) );
case 'site_title':
case 'blog_title':
return aioseo()->helpers->decodeHtmlEntities( get_bloginfo( 'name' ) );
default:
return '';
}
}
/**
* Get the category title.
*
* @since 4.0.0
*
* @param integer $postId The post ID if set.
* @return string The category title.
*/
private function getTaxonomyTitle( $postId = null ) {
$isWcActive = aioseo()->helpers->isWooCommerceActive();
$title = '';
if ( $isWcActive && is_product_category() ) {
$title = single_cat_title( '', false );
} elseif ( is_category() ) {
$title = single_cat_title( '', false );
} elseif ( is_tag() ) {
$title = single_tag_title( '', false );
} elseif ( is_author() ) {
$title = get_the_author();
} elseif ( is_tax() ) {
$title = single_term_title( '', false );
} elseif ( is_post_type_archive() ) {
$title = post_type_archive_title( '', false );
} elseif ( is_archive() ) {
$title = get_the_archive_title();
}
if ( $postId ) {
$currentScreen = aioseo()->helpers->getCurrentScreen();
$isProduct = $isWcActive && ( is_product() || 'product' === ( $currentScreen->post_type ?? '' ) );
$post = aioseo()->helpers->getPost( $postId );
$postTaxonomies = get_object_taxonomies( $post, 'objects' );
$postTerms = [];
foreach ( $postTaxonomies as $taxonomySlug => $taxonomy ) {
if ( ! $taxonomy->hierarchical ) {
continue;
}
$taxonomySlug = $isProduct ? 'product_cat' : $taxonomySlug;
$primaryTerm = aioseo()->standalone->primaryTerm->getPrimaryTerm( $postId, $taxonomySlug );
if ( $primaryTerm ) {
$postTerms[] = aioseo()->helpers->getTerm( $primaryTerm, $taxonomySlug );
break;
}
$postTaxonomyTerms = get_the_terms( $postId, $taxonomySlug );
if ( is_array( $postTaxonomyTerms ) ) {
$postTerms = array_merge( $postTerms, $postTaxonomyTerms );
break;
}
}
$title = $postTerms ? $postTerms[0]->name : '';
}
return wp_strip_all_tags( (string) $title );
}
/**
* Formatted Date
*
* Get formatted date based on WP options.
*
* @since 4.0.0
*
* @param null|int $date Date in UNIX timestamp format. Otherwise, current time.
* @return string Date internationalized.
*/
public function formatDateAsI18n( $date = null ) {
if ( ! $date ) {
$date = time();
}
$format = get_option( 'date_format' );
$formattedDate = date_i18n( $format, $date );
return apply_filters(
'aioseo_format_date',
$formattedDate,
[
$date,
$format
]
);
}
/**
* Parses custom taxonomy tags by replacing them with the name of the first assigned term of the given taxonomy.
*
* @since 4.0.0
*
* @param string $string The string to parse.
* @return mixed The new title.
*/
private function parseTaxonomyNames( $string, $id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$pattern = '/' . $this->denotationChar . 'tax_name-([a-zA-Z0-9_-]+)/im';
$string = preg_replace_callback( $pattern, [ $this, 'replaceTaxonomyName' ], $string );
$pattern = '/' . $this->denotationChar . 'tax_name(?![a-zA-Z0-9_-])/im';
return preg_replace( $pattern, '', (string) $string );
}
/**
* Adds support for using #custom_field-[custom_field_title] for using
* custom fields / Advanced Custom Fields in titles / descriptions etc.
*
* @since 4.0.0
*
* @param string $string The string to parse customs fields out of.
* @param int $postId The page or post ID.
* @return string The new title.
*/
public function parseCustomFields( $string, $postId = 0 ) {
$pattern = '/' . $this->denotationChar . 'custom_field-([a-zA-Z0-9_-]+)/im';
$matches = [];
preg_match_all( $pattern, (string) $string, $matches, PREG_SET_ORDER );
$string = $this->replaceCustomField( $string, $matches, $postId );
$pattern = '/' . $this->denotationChar . 'custom_field(?![a-zA-Z0-9_-])/im';
return preg_replace( $pattern, '', (string) $string );
}
/**
* Add context to our internal context.
*
* @since 4.0.0
*
* @param array $context A context array to append.
* @return void
*/
public function addContext( $context ) {
$this->context = array_merge( $this->context, $context );
}
/**
* Add tags to our internal tags.
*
* @since 4.0.0
*
* @param array $tags A tags array to append.
* @return void
*/
public function addTags( $tags ) {
$this->tags = array_merge( $this->tags, $tags );
}
/**
* Replaces a taxonomy name tag with its respective value.
*
* @since 4.0.0
*
* @param array $matches The matches.
* @return string The replaced matches.
*/
private function replaceTaxonomyName( $matches ) {
$termName = '';
$post = aioseo()->helpers->getPost();
if ( ! empty( $matches[1] ) && $post ) {
$taxonomy = get_taxonomy( $matches[1] );
if ( ! $taxonomy ) {
return '';
}
$term = aioseo()->standalone->primaryTerm->getPrimaryTerm( $post->ID, $taxonomy->name );
if ( ! $term ) {
$terms = get_the_terms( $post->ID, $taxonomy->name );
if ( ! $terms || is_wp_error( $terms ) ) {
return '';
}
$term = array_shift( $terms );
}
$termName = $term->name;
}
return '%|%' . $termName;
}
/**
* (ACF) Custom Field Replace.
*
* @since 4.0.0
*
* @param string $string The string to parse customs fields out of.
* @param array $matches Array of matched values.
* @param int $postId The page or post ID.
* @return bool|string New title/text.
*/
private function replaceCustomField( $string, $matches, $postId ) {
if ( empty( $matches ) ) {
return $string;
}
$postId = get_queried_object() ?? $postId;
foreach ( $matches as $match ) {
$value = '';
if ( ! empty( $match[1] ) ) {
if ( function_exists( 'get_field' ) ) {
$value = get_field( $match[1], $postId );
if ( ! empty( $value['url'] ) && ! empty( $value['title'] ) ) {
$value = "<a href='{$value['url']}'>{$value['title']}</a>";
}
if ( empty( $value ) ) {
$value = aioseo()->helpers->getAcfFlexibleContentField( $match[1], $postId );
}
}
if ( empty( $value ) ) {
global $post;
if ( ! empty( $post ) ) {
$value = get_post_meta( $post->ID, $match[1], true );
}
}
}
$value = is_scalar( $value ) ? wp_strip_all_tags( $value ) : '';
$string = str_replace( $match[0], '%|%' . $value, $string );
}
return $string;
}
/**
* Get the default tags for the current post.
*
* @since 4.0.0
*
* @param integer $postId The Post ID.
* @return array An array of tags.
*/
public function getDefaultPostTags( $postId ) {
$post = get_post( $postId );
$title = aioseo()->meta->title->getTitle( $post, true );
$description = aioseo()->meta->description->getDescription( $post, true );
return [
'title' => empty( $title ) ? '' : $title,
'description' => empty( $description ) ? '' : $description
];
}
} Templates.php 0000644 00000005504 15154302615 0007221 0 ustar 00 <?php
namespace AIOSEO\Plugin\Common\Utils;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Templates
*
* @since 4.0.17
*
* @package AIOSEO\Plugin\Common\Utils
*/
class Templates {
/**
* This plugin absolute path.
*
* @since 4.0.17
*
* @var string
*/
protected $pluginPath = AIOSEO_DIR;
/**
* Paths were our template files are located.
*
* @since 4.0.17
*
* @var string Array of paths.
*/
protected $paths = [
'app/Common/Views'
];
/**
*
* The theme folder.
*
* @since 4.0.17
*
* @var string
*/
private $themeTemplatePath = 'aioseo/';
/**
*
* A theme subfolder.
*
* @since 4.0.17
*
* @var string
*/
protected $themeTemplateSubpath = '';
/**
* Locate a template file in the theme or our plugin paths.
*
* @since 4.0.17
*
* @param string $templateName The template name.
* @return string The template absolute path.
*/
public function locateTemplate( $templateName ) {
// Try to find template file in the theme.
$template = locate_template(
[
trailingslashit( $this->getThemeTemplatePath() ) . trailingslashit( $this->getThemeTemplateSubpath() ) . $templateName
]
);
if ( ! $template ) {
// Try paths, in order.
foreach ( $this->paths as $path ) {
$template = trailingslashit( $this->addPluginPath( $path ) ) . $templateName;
if ( aioseo()->core->fs->exists( $template ) ) {
break;
}
}
}
return apply_filters( 'aioseo_locate_template', $template, $templateName );
}
/**
* Includes a template if the file exists.
*
* @param string $templateName The template path/name.php to be included.
* @param null $data Data passed down to the template.
* @return void
*/
public function getTemplate( $templateName, $data = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$template = $this->locateTemplate( $templateName );
if ( ! empty( $template ) and aioseo()->core->fs->exists( $template ) ) {
include $template;
}
}
/**
* Add this plugin path when trying the paths.
*
* @since 4.0.17
*
* @param string $path A path.
* @return string A path with the plugin absolute path.
*/
protected function addPluginPath( $path ) {
return trailingslashit( $this->pluginPath ) . $path;
}
/**
* Returns the theme folder for templates.
*
* @since 4.0.17
*
* @return string The theme folder for templates.
*/
public function getThemeTemplatePath() {
return apply_filters( 'aioseo_template_path', $this->themeTemplatePath );
}
/**
*
* Returns the theme subfolder for templates.
*
* @since 4.0.17
*
* @return string The theme subfolder for templates.
*/
public function getThemeTemplateSubpath() {
return apply_filters( 'aioseo_template_subpath', $this->themeTemplateSubpath );
}
} campaigns-block-settings.js 0000644 00000002234 15155033364 0012000 0 ustar 00 import ArchieIcon from '../Components/Icons/Archie';
import CampaignSelector from '../Components/Blocks/CampaignSelector';
const { __ } = wp.i18n;
/**
* Get the settings to register the campaign selector block.
*
* Because we need to support older versions (<=5.7) this returns the
* proper settings. Versions >=5.8 use the block.json approach.
*
* @since 2.6.10
*
* @returns {Object} The settings for the campaign selector block.
*/
export const getBlockSettings = () => {
const wpVersion = parseFloat(OMAPI.wpVersion);
const baseSettings = {
icon: ArchieIcon,
edit: CampaignSelector,
save() {
return null;
},
};
const legacySettings = {
title: OMAPI.i18n.title,
description: OMAPI.i18n.description,
category: 'embed',
keywords: [
__('Popup', 'optin-monster-api'),
__('Form', 'optin-monster-api'),
__('Campaign', 'optin-monster-api'),
__('Email', 'optin-monster-api'),
__('Conversion', 'optin-monster-api'),
],
attributes: {
slug: {
type: 'string',
},
followrules: {
type: 'boolean',
default: true,
},
},
};
return wpVersion >= 5.8 ? baseSettings : Object.assign(baseSettings, legacySettings);
};
campaigns.js 0000644 00000003136 15155033364 0007054 0 ustar 00 import get from 'lodash/get';
import { hasSites } from './sites';
/**
* Get available inline campaign select options.
*
* @since [since]
*
* @param {string} type The campaign generic type (inline or other).
* @param {string} slug The campaign slug.
* @param {boolean} checkSites Whether to check for sites (return empty result if no sites connected).
*
* @returns {Array} Array of campaign options for select elements.
* Includes value, label, and selected/disabled properties.
*/
export const getOptions = (type, slug = null, checkSites = true) => {
if (checkSites && !hasSites()) {
return [];
}
const campaigns = get(OMAPI, `campaigns.${type}`, {});
if (Object.keys(campaigns).length < 1 || !OMAPI.omUserId) {
return [];
}
let available = Object.keys(campaigns).map((value) => {
let label = get(campaigns, `${value}.title`, '');
if (get(campaigns, `${value}.pending`)) {
label += ' [Pending]';
}
const selected = null !== slug && slug === value;
const disabled = null !== slug && get(OMAPI, `_usedSlugs.${value}`) && value !== slug;
return { value, label, selected, disabled };
});
if (available.length > 0) {
available.unshift({ value: '', label: OMAPI.i18n.campaign_select });
}
return available;
};
/**
* Get the global campaign object from OM API for given slug.
*
* @since 2.3.0
*
* @param {string} slug The campaign slug.
*
* @returns {Object|null} The global campaign object or null.
*/
export const getCampaign = (slug = '') => {
const key = `om${slug}`;
return window[key] ? window[key] : null;
};
monsterlink.js 0000644 00000000334 15155033364 0007454 0 ustar 00 /**
* Get a monsterlink from an id.
*
* @since 2.3.0
*
* @param {string} id Campaign Id.
*
* @returns {string} Campaign monsterlink url.
*/
export const getMonsterlink = (id) => OMAPI.monsterlink + id + '/';
sites.js 0000644 00000000113 15155033364 0006231 0 ustar 00 export const hasSites = () => OMAPI.site_ids && OMAPI.site_ids.length > 0;
BlockTemplateMigrationUtils.php 0000644 00000012145 15155074006 0012704 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\Utils;
/**
* Utility methods used for migrating pages to block templates.
* {@internal This class and its methods should only be used within the BlockTemplateController.php and is not intended for public use.}
*/
class BlockTemplateMigrationUtils {
/**
* Check if a page has been migrated to a template.
*
* @param string $page_id Page ID.
* @return boolean
*/
public static function has_migrated_page( $page_id ) {
return (bool) get_option( 'has_migrated_' . $page_id, false );
}
/**
* Stores an option to indicate that a template has been migrated.
*
* @param string $page_id Page ID.
* @param string $status Status of the migration.
*/
public static function set_has_migrated_page( $page_id, $status = 'success' ) {
update_option( 'has_migrated_' . $page_id, $status );
}
/**
* Migrates a page to a template if needed.
*
* @param string $template_slug Template slug.
* @param \WP_Post $page Page object.
*/
public static function migrate_page( $template_slug, $page ) {
// Get the block template for this page. If it exists, we won't migrate because the user already has custom content.
$block_template = BlockTemplateUtils::get_block_template( 'woocommerce/woocommerce//' . $template_slug, 'wp_template' );
// If we were unable to get the block template, bail. Try again later.
if ( ! $block_template ) {
return;
}
// If a custom template is present already, no need to migrate.
if ( $block_template->wp_id ) {
return self::set_has_migrated_page( $template_slug, 'custom-template-exists' );
}
// Use the page template if it exists, which we'll use over our default template if found.
$page_template = self::get_page_template( $page );
$default_template = self::get_default_template( $page );
$template_content = $page_template ?: $default_template;
// If at this point we have no content to migrate, bail.
if ( ! $template_content ) {
return self::set_has_migrated_page( $template_slug, 'no-content' );
}
if ( self::create_custom_template( $block_template, $template_content ) ) {
return self::set_has_migrated_page( $template_slug );
}
}
/**
* Get template for a page following the page hierarchy.
*
* @param \WP_Post|null $page Page object.
* @return string
*/
protected static function get_page_template( $page ) {
$templates = array();
if ( $page && $page->ID ) {
$template = get_page_template_slug( $page->ID );
if ( $template && 0 === validate_file( $template ) ) {
$templates[] = $template;
}
$pagename = $page->post_name;
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) {
$templates[] = "page-{$pagename_decoded}";
}
$templates[] = "page-{$pagename}";
}
}
$block_template = false;
foreach ( $templates as $template ) {
$block_template = BlockTemplateUtils::get_block_template( get_stylesheet() . '//' . $template, 'wp_template' );
if ( $block_template && ! empty( $block_template->content ) ) {
break;
}
}
return $block_template ? $block_template->content : '';
}
/**
* Prepare default page template.
*
* @param \WP_Post $page Page object.
* @return string
*/
protected static function get_default_template( $page ) {
if ( ! $page || empty( $page->post_content ) ) {
return '';
}
$default_template_content = '
<!-- wp:group {"layout":{"inherit":true}} -->
<div class="wp-block-group">
<!-- wp:heading {"level":1} -->
<h1 class="wp-block-heading">' . wp_kses_post( $page->post_title ) . '</h1>
<!-- /wp:heading -->
' . wp_kses_post( $page->post_content ) . '
</div>
<!-- /wp:group -->
';
return self::get_block_template_part( 'header' ) . $default_template_content . self::get_block_template_part( 'footer' );
}
/**
* Create a custom template with given content.
*
* @param \WP_Block_Template|null $template Template object.
* @param string $content Template content.
* @return boolean Success.
*/
protected static function create_custom_template( $template, $content ) {
$term = get_term_by( 'slug', $template->theme, 'wp_theme', ARRAY_A );
if ( ! $term ) {
$term = wp_insert_term( $template->theme, 'wp_theme' );
}
$template_id = wp_insert_post(
[
'post_name' => $template->slug,
'post_type' => 'wp_template',
'post_status' => 'publish',
'tax_input' => array(
'wp_theme' => $template->theme,
),
'meta_input' => array(
'origin' => $template->source,
),
'post_content' => $content,
],
true
);
wp_set_post_terms( $template_id, array( $term['term_id'] ), 'wp_theme' );
return $template_id && ! is_wp_error( $template_id );
}
/**
* Returns the requested template part.
*
* @param string $part The part to return.
* @return string
*/
protected static function get_block_template_part( $part ) {
$template_part = BlockTemplateUtils::get_block_template( get_stylesheet() . '//' . $part, 'wp_template_part' );
if ( ! $template_part || empty( $template_part->content ) ) {
return '';
}
return $template_part->content;
}
}
BlockTemplateUtils.php 0000644 00000070626 15155074006 0011042 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\Utils;
use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating;
use Automattic\WooCommerce\Blocks\Options;
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutHeaderTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
use Automattic\WooCommerce\Blocks\Templates\MiniCartTemplate;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
/**
* Utility methods used for serving block templates from WooCommerce Blocks.
* {@internal This class and its methods should only be used within the BlockTemplateController.php and is not intended for public use.}
*/
class BlockTemplateUtils {
const ELIGIBLE_FOR_ARCHIVE_PRODUCT_FALLBACK = array( 'taxonomy-product_cat', 'taxonomy-product_tag', ProductAttributeTemplate::SLUG );
/**
* Directory names for block templates
*
* Directory names conventions for block templates have changed with Gutenberg 12.1.0,
* however, for backwards-compatibility, we also keep the older conventions, prefixed
* with `DEPRECATED_`.
*
* @var array {
* @var string DEPRECATED_TEMPLATES Old directory name of the block templates directory.
* @var string DEPRECATED_TEMPLATE_PARTS Old directory name of the block template parts directory.
* @var string TEMPLATES_DIR_NAME Directory name of the block templates directory.
* @var string TEMPLATE_PARTS_DIR_NAME Directory name of the block template parts directory.
* }
*/
const DIRECTORY_NAMES = array(
'DEPRECATED_TEMPLATES' => 'block-templates',
'DEPRECATED_TEMPLATE_PARTS' => 'block-template-parts',
'TEMPLATES' => 'templates',
'TEMPLATE_PARTS' => 'parts',
);
/**
* WooCommerce plugin slug
*
* This is used to save templates to the DB which are stored against this value in the wp_terms table.
*
* @var string
*/
const PLUGIN_SLUG = 'woocommerce/woocommerce';
/**
* Deprecated WooCommerce plugin slug
*
* For supporting users who have customized templates under the incorrect plugin slug during the first release.
* More context found here: https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5423.
*
* @var string
*/
const DEPRECATED_PLUGIN_SLUG = 'woocommerce';
/**
* Returns an array containing the references of
* the passed blocks and their inner blocks.
*
* @param array $blocks array of blocks.
*
* @return array block references to the passed blocks and their inner blocks.
*/
public static function flatten_blocks( &$blocks ) {
$all_blocks = array();
$queue = array();
foreach ( $blocks as &$block ) {
$queue[] = &$block;
}
$queue_count = count( $queue );
while ( $queue_count > 0 ) {
$block = &$queue[0];
array_shift( $queue );
$all_blocks[] = &$block;
if ( ! empty( $block['innerBlocks'] ) ) {
foreach ( $block['innerBlocks'] as &$inner_block ) {
$queue[] = &$inner_block;
}
}
$queue_count = count( $queue );
}
return $all_blocks;
}
/**
* Parses wp_template content and injects the current theme's
* stylesheet as a theme attribute into each wp_template_part
*
* @param string $template_content serialized wp_template content.
*
* @return string Updated wp_template content.
*/
public static function inject_theme_attribute_in_content( $template_content ) {
$has_updated_content = false;
$new_content = '';
$template_blocks = parse_blocks( $template_content );
$blocks = self::flatten_blocks( $template_blocks );
foreach ( $blocks as &$block ) {
if (
'core/template-part' === $block['blockName'] &&
! isset( $block['attrs']['theme'] )
) {
$block['attrs']['theme'] = wp_get_theme()->get_stylesheet();
$has_updated_content = true;
}
}
if ( $has_updated_content ) {
foreach ( $template_blocks as &$block ) {
$new_content .= serialize_block( $block );
}
return $new_content;
}
return $template_content;
}
/**
* Build a unified template object based a post Object.
* Important: This method is an almost identical duplicate from wp-includes/block-template-utils.php as it was not intended for public use. It has been modified to build templates from plugins rather than themes.
*
* @param \WP_Post $post Template post.
*
* @return \WP_Block_Template|\WP_Error Template.
*/
public static function build_template_result_from_post( $post ) {
$terms = get_the_terms( $post, 'wp_theme' );
if ( is_wp_error( $terms ) ) {
return $terms;
}
if ( ! $terms ) {
return new \WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'woocommerce' ) );
}
$theme = $terms[0]->name;
$has_theme_file = true;
$template = new \WP_Block_Template();
$template->wp_id = $post->ID;
$template->id = $theme . '//' . $post->post_name;
$template->theme = $theme;
$template->content = $post->post_content;
$template->slug = $post->post_name;
$template->source = 'custom';
$template->type = $post->post_type;
$template->description = $post->post_excerpt;
$template->title = $post->post_title;
$template->status = $post->post_status;
$template->has_theme_file = $has_theme_file;
$template->is_custom = false;
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
if ( 'wp_template_part' === $post->post_type ) {
$type_terms = get_the_terms( $post, 'wp_template_part_area' );
if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
$template->area = $type_terms[0]->name;
}
}
// We are checking 'woocommerce' to maintain classic templates which are saved to the DB,
// prior to updating to use the correct slug.
// More information found here: https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5423.
if ( self::PLUGIN_SLUG === $theme || self::DEPRECATED_PLUGIN_SLUG === strtolower( $theme ) ) {
$template->origin = 'plugin';
}
return $template;
}
/**
* Build a unified template object based on a theme file.
* Important: This method is an almost identical duplicate from wp-includes/block-template-utils.php as it was not intended for public use. It has been modified to build templates from plugins rather than themes.
*
* @param array|object $template_file Theme file.
* @param string $template_type wp_template or wp_template_part.
*
* @return \WP_Block_Template Template.
*/
public static function build_template_result_from_file( $template_file, $template_type ) {
$template_file = (object) $template_file;
// If the theme has an archive-products.html template but does not have product taxonomy templates
// then we will load in the archive-product.html template from the theme to use for product taxonomies on the frontend.
$template_is_from_theme = 'theme' === $template_file->source;
$theme_name = wp_get_theme()->get( 'TextDomain' );
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$template_content = file_get_contents( $template_file->path );
$template = new \WP_Block_Template();
$template->id = $template_is_from_theme ? $theme_name . '//' . $template_file->slug : self::PLUGIN_SLUG . '//' . $template_file->slug;
$template->theme = $template_is_from_theme ? $theme_name : self::PLUGIN_SLUG;
$template->content = self::inject_theme_attribute_in_content( $template_content );
// Remove the term description block from the archive-product template
// as the Product Catalog/Shop page doesn't have a description.
if ( 'archive-product' === $template_file->slug ) {
$template->content = str_replace( '<!-- wp:term-description {"align":"wide"} /-->', '', $template->content );
}
// Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909.
$template->source = $template_file->source ? $template_file->source : 'plugin';
$template->slug = $template_file->slug;
$template->type = $template_type;
$template->title = ! empty( $template_file->title ) ? $template_file->title : self::get_block_template_title( $template_file->slug );
$template->description = ! empty( $template_file->description ) ? $template_file->description : self::get_block_template_description( $template_file->slug );
$template->status = 'publish';
$template->has_theme_file = true;
$template->origin = $template_file->source;
$template->is_custom = false; // Templates loaded from the filesystem aren't custom, ones that have been edited and loaded from the DB are.
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
$template->area = 'uncategorized';
// Force the Mini-Cart template part to be in the Mini-Cart template part area.
if ( 'wp_template_part' === $template_type && 'mini-cart' === $template_file->slug ) {
$template->area = 'mini-cart';
}
return $template;
}
/**
* Build a new template object so that we can make Woo Blocks default templates available in the current theme should they not have any.
*
* @param string $template_file Block template file path.
* @param string $template_type wp_template or wp_template_part.
* @param string $template_slug Block template slug e.g. single-product.
* @param bool $template_is_from_theme If the block template file is being loaded from the current theme instead of Woo Blocks.
*
* @return object Block template object.
*/
public static function create_new_block_template_object( $template_file, $template_type, $template_slug, $template_is_from_theme = false ) {
$theme_name = wp_get_theme()->get( 'TextDomain' );
$new_template_item = array(
'slug' => $template_slug,
'id' => $template_is_from_theme ? $theme_name . '//' . $template_slug : self::PLUGIN_SLUG . '//' . $template_slug,
'path' => $template_file,
'type' => $template_type,
'theme' => $template_is_from_theme ? $theme_name : self::PLUGIN_SLUG,
// Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909.
'source' => $template_is_from_theme ? 'theme' : 'plugin',
'title' => self::get_block_template_title( $template_slug ),
'description' => self::get_block_template_description( $template_slug ),
'post_types' => array(), // Don't appear in any Edit Post template selector dropdown.
);
return (object) $new_template_item;
}
/**
* Finds all nested template part file paths in a theme's directory.
*
* @param string $base_directory The theme's file path.
* @return array $path_list A list of paths to all template part files.
*/
public static function get_template_paths( $base_directory ) {
$path_list = array();
if ( file_exists( $base_directory ) ) {
$nested_files = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $base_directory ) );
$nested_html_files = new \RegexIterator( $nested_files, '/^.+\.html$/i', \RecursiveRegexIterator::GET_MATCH );
foreach ( $nested_html_files as $path => $file ) {
$path_list[] = $path;
}
}
return $path_list;
}
/**
* Returns template titles.
*
* @param string $template_slug The templates slug (e.g. single-product).
* @return string Human friendly title.
*/
public static function get_block_template_title( $template_slug ) {
$plugin_template_types = self::get_plugin_block_template_types();
if ( isset( $plugin_template_types[ $template_slug ] ) ) {
return $plugin_template_types[ $template_slug ]['title'];
} else {
// Human friendly title converted from the slug.
return ucwords( preg_replace( '/[\-_]/', ' ', $template_slug ) );
}
}
/**
* Returns template descriptions.
*
* @param string $template_slug The templates slug (e.g. single-product).
* @return string Template description.
*/
public static function get_block_template_description( $template_slug ) {
$plugin_template_types = self::get_plugin_block_template_types();
if ( isset( $plugin_template_types[ $template_slug ] ) ) {
return $plugin_template_types[ $template_slug ]['description'];
}
return '';
}
/**
* Returns a filtered list of plugin template types, containing their
* localized titles and descriptions.
*
* @return array The plugin template types.
*/
public static function get_plugin_block_template_types() {
return array(
'single-product' => array(
'title' => _x( 'Single Product', 'Template name', 'woocommerce' ),
'description' => __( 'Displays a single product.', 'woocommerce' ),
),
'archive-product' => array(
'title' => _x( 'Product Catalog', 'Template name', 'woocommerce' ),
'description' => __( 'Displays your products.', 'woocommerce' ),
),
'taxonomy-product_cat' => array(
'title' => _x( 'Products by Category', 'Template name', 'woocommerce' ),
'description' => __( 'Displays products filtered by a category.', 'woocommerce' ),
),
'taxonomy-product_tag' => array(
'title' => _x( 'Products by Tag', 'Template name', 'woocommerce' ),
'description' => __( 'Displays products filtered by a tag.', 'woocommerce' ),
),
ProductAttributeTemplate::SLUG => array(
'title' => _x( 'Products by Attribute', 'Template name', 'woocommerce' ),
'description' => __( 'Displays products filtered by an attribute.', 'woocommerce' ),
),
ProductSearchResultsTemplate::SLUG => array(
'title' => _x( 'Product Search Results', 'Template name', 'woocommerce' ),
'description' => __( 'Displays search results for your store.', 'woocommerce' ),
),
MiniCartTemplate::SLUG => array(
'title' => _x( 'Mini-Cart', 'Template name', 'woocommerce' ),
'description' => __( 'Template used to display the Mini-Cart drawer.', 'woocommerce' ),
),
CartTemplate::get_slug() => array(
'title' => _x( 'Cart', 'Template name', 'woocommerce' ),
'description' => __( 'The Cart template displays the items selected by the user for purchase, including quantities, prices, and discounts. It allows users to review their choices before proceeding to checkout.', 'woocommerce' ),
),
CheckoutTemplate::get_slug() => array(
'title' => _x( 'Checkout', 'Template name', 'woocommerce' ),
'description' => __( 'The Checkout template guides users through the final steps of the purchase process. It enables users to enter shipping and billing information, select a payment method, and review order details.', 'woocommerce' ),
),
CheckoutHeaderTemplate::SLUG => array(
'title' => _x( 'Checkout Header', 'Template name', 'woocommerce' ),
'description' => __( 'Template used to display the simplified Checkout header.', 'woocommerce' ),
),
OrderConfirmationTemplate::get_slug() => array(
'title' => _x( 'Order Confirmation', 'Template name', 'woocommerce' ),
'description' => __( 'The Order Confirmation template provides customers with a summary of their completed purchase, including ordered items, shipping details, and order total. It serves as a receipt and confirmation of the successful transaction.', 'woocommerce' ),
),
);
}
/**
* Converts template paths into a slug
*
* @param string $path The template's path.
* @return string slug
*/
public static function generate_template_slug_from_path( $path ) {
$template_extension = '.html';
return basename( $path, $template_extension );
}
/**
* Gets the first matching template part within themes directories
*
* Since [Gutenberg 12.1.0](https://github.com/WordPress/gutenberg/releases/tag/v12.1.0), the conventions for
* block templates and parts directory has changed from `block-templates` and `block-templates-parts`
* to `templates` and `parts` respectively.
*
* This function traverses all possible combinations of directory paths where a template or part
* could be located and returns the first one which is readable, prioritizing the new convention
* over the deprecated one, but maintaining that one for backwards compatibility.
*
* @param string $template_slug The slug of the template (i.e. without the file extension).
* @param string $template_type Either `wp_template` or `wp_template_part`.
*
* @return string|null The matched path or `null` if no match was found.
*/
public static function get_theme_template_path( $template_slug, $template_type = 'wp_template' ) {
$template_filename = $template_slug . '.html';
$possible_templates_dir = 'wp_template' === $template_type ? array(
self::DIRECTORY_NAMES['TEMPLATES'],
self::DIRECTORY_NAMES['DEPRECATED_TEMPLATES'],
) : array(
self::DIRECTORY_NAMES['TEMPLATE_PARTS'],
self::DIRECTORY_NAMES['DEPRECATED_TEMPLATE_PARTS'],
);
// Combine the possible root directory names with either the template directory
// or the stylesheet directory for child themes.
$possible_paths = array_reduce(
$possible_templates_dir,
function( $carry, $item ) use ( $template_filename ) {
$filepath = DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . $template_filename;
$carry[] = get_stylesheet_directory() . $filepath;
$carry[] = get_template_directory() . $filepath;
return $carry;
},
array()
);
// Return the first matching.
foreach ( $possible_paths as $path ) {
if ( is_readable( $path ) ) {
return $path;
}
}
return null;
}
/**
* Check if the theme has a template. So we know if to load our own in or not.
*
* @param string $template_name name of the template file without .html extension e.g. 'single-product'.
* @return boolean
*/
public static function theme_has_template( $template_name ) {
return ! ! self::get_theme_template_path( $template_name, 'wp_template' );
}
/**
* Check if the theme has a template. So we know if to load our own in or not.
*
* @param string $template_name name of the template file without .html extension e.g. 'single-product'.
* @return boolean
*/
public static function theme_has_template_part( $template_name ) {
return ! ! self::get_theme_template_path( $template_name, 'wp_template_part' );
}
/**
* Checks to see if they are using a compatible version of WP, or if not they have a compatible version of the Gutenberg plugin installed.
*
* @param string $template_type Optional. Template type: `wp_template` or `wp_template_part`.
* Default `wp_template`.
* @return boolean
*/
public static function supports_block_templates( $template_type = 'wp_template' ) {
if ( 'wp_template_part' === $template_type && ( wc_current_theme_is_fse_theme() || current_theme_supports( 'block-template-parts' ) ) ) {
return true;
} elseif ( 'wp_template' === $template_type && wc_current_theme_is_fse_theme() ) {
return true;
}
return false;
}
/**
* Retrieves a single unified template object using its id.
*
* @param string $id Template unique identifier (example: theme_slug//template_slug).
* @param string $template_type Optional. Template type: `wp_template` or 'wp_template_part`.
* Default `wp_template`.
*
* @return WP_Block_Template|null Template.
*/
public static function get_block_template( $id, $template_type ) {
if ( function_exists( 'get_block_template' ) ) {
return get_block_template( $id, $template_type );
}
if ( function_exists( 'gutenberg_get_block_template' ) ) {
return gutenberg_get_block_template( $id, $template_type );
}
return null;
}
/**
* Checks if we can fall back to the `archive-product` template for a given slug.
*
* `taxonomy-product_cat`, `taxonomy-product_tag`, `taxonomy-product_attribute` templates can
* generally use the `archive-product` as a fallback if there are no specific overrides.
*
* @param string $template_slug Slug to check for fallbacks.
* @return boolean
*/
public static function template_is_eligible_for_product_archive_fallback( $template_slug ) {
return in_array( $template_slug, self::ELIGIBLE_FOR_ARCHIVE_PRODUCT_FALLBACK, true );
}
/**
* Checks if we can fall back to an `archive-product` template stored on the db for a given slug.
*
* @param string $template_slug Slug to check for fallbacks.
* @param array $db_templates Templates that have already been found on the db.
* @return boolean
*/
public static function template_is_eligible_for_product_archive_fallback_from_db( $template_slug, $db_templates ) {
$eligible_for_fallback = self::template_is_eligible_for_product_archive_fallback( $template_slug );
if ( ! $eligible_for_fallback ) {
return false;
}
$array_filter = array_filter(
$db_templates,
function ( $template ) use ( $template_slug ) {
return 'archive-product' === $template->slug;
}
);
return count( $array_filter ) > 0;
}
/**
* Gets the `archive-product` fallback template stored on the db for a given slug.
*
* @param string $template_slug Slug to check for fallbacks.
* @param array $db_templates Templates that have already been found on the db.
* @return boolean|object
*/
public static function get_fallback_template_from_db( $template_slug, $db_templates ) {
$eligible_for_fallback = self::template_is_eligible_for_product_archive_fallback( $template_slug );
if ( ! $eligible_for_fallback ) {
return false;
}
foreach ( $db_templates as $template ) {
if ( 'archive-product' === $template->slug ) {
return $template;
}
}
return false;
}
/**
* Checks if we can fall back to the `archive-product` file template for a given slug in the current theme.
*
* `taxonomy-product_cat`, `taxonomy-product_tag`, `taxonomy-attribute` templates can
* generally use the `archive-product` as a fallback if there are no specific overrides.
*
* @param string $template_slug Slug to check for fallbacks.
* @return boolean
*/
public static function template_is_eligible_for_product_archive_fallback_from_theme( $template_slug ) {
return self::template_is_eligible_for_product_archive_fallback( $template_slug )
&& ! self::theme_has_template( $template_slug )
&& self::theme_has_template( 'archive-product' );
}
/**
* Sets the `has_theme_file` to `true` for templates with fallbacks
*
* There are cases (such as tags, categories and attributes) in which fallback templates
* can be used; so, while *technically* the theme doesn't have a specific file
* for them, it is important that we tell Gutenberg that we do, in fact,
* have a theme file (i.e. the fallback one).
*
* **Note:** this function changes the array that has been passed.
*
* It returns `true` if anything was changed, `false` otherwise.
*
* @param array $query_result Array of template objects.
* @param object $template A specific template object which could have a fallback.
*
* @return boolean
*/
public static function set_has_theme_file_if_fallback_is_available( $query_result, $template ) {
foreach ( $query_result as &$query_result_template ) {
if (
$query_result_template->slug === $template->slug
&& $query_result_template->theme === $template->theme
) {
if ( self::template_is_eligible_for_product_archive_fallback_from_theme( $template->slug ) ) {
$query_result_template->has_theme_file = true;
}
return true;
}
}
return false;
}
/**
* Filter block templates by feature flag.
*
* @param WP_Block_Template[] $block_templates An array of block template objects.
*
* @return WP_Block_Template[] An array of block template objects.
*/
public static function filter_block_templates_by_feature_flag( $block_templates ) {
$feature_gating = new FeatureGating();
$flag = $feature_gating->get_flag();
/**
* An array of block templates with slug as key and flag as value.
*
* @var array
*/
$block_templates_with_feature_gate = array();
return array_filter(
$block_templates,
function( $block_template ) use ( $flag, $block_templates_with_feature_gate ) {
if ( isset( $block_templates_with_feature_gate[ $block_template->slug ] ) ) {
return $block_templates_with_feature_gate[ $block_template->slug ] <= $flag;
}
return true;
}
);
}
/**
* Removes templates that were added to a theme's block-templates directory, but already had a customised version saved in the database.
*
* @param \WP_Block_Template[]|\stdClass[] $templates List of templates to run the filter on.
*
* @return array List of templates with duplicates removed. The customised alternative is preferred over the theme default.
*/
public static function remove_theme_templates_with_custom_alternative( $templates ) {
// Get the slugs of all templates that have been customised and saved in the database.
$customised_template_slugs = array_map(
function( $template ) {
return $template->slug;
},
array_values(
array_filter(
$templates,
function( $template ) {
// This template has been customised and saved as a post.
return 'custom' === $template->source;
}
)
)
);
// Remove theme (i.e. filesystem) templates that have the same slug as a customised one. We don't need to check
// for `woocommerce` in $template->source here because woocommerce templates won't have been added to $templates
// if a saved version was found in the db. This only affects saved templates that were saved BEFORE a theme
// template with the same slug was added.
return array_values(
array_filter(
$templates,
function( $template ) use ( $customised_template_slugs ) {
// This template has been customised and saved as a post, so return it.
return ! ( 'theme' === $template->source && in_array( $template->slug, $customised_template_slugs, true ) );
}
)
);
}
/**
* Returns whether the blockified templates should be used or not.
* First, we need to make sure WordPress version is higher than 6.1 (lowest that supports Products block).
* Then, if the option is not stored on the db, we need to check if the current theme is a block one or not.
*
* @return boolean
*/
public static function should_use_blockified_product_grid_templates() {
$minimum_wp_version = '6.1';
if ( version_compare( $GLOBALS['wp_version'], $minimum_wp_version, '<' ) ) {
return false;
}
$use_blockified_templates = get_option( Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE );
if ( false === $use_blockified_templates ) {
return wc_current_theme_is_fse_theme();
}
return wc_string_to_bool( $use_blockified_templates );
}
/**
* Returns whether the passed `$template` has a title, and it's different from the slug.
*
* @param object $template The template object.
* @return boolean
*/
public static function template_has_title( $template ) {
return ! empty( $template->title ) && $template->title !== $template->slug;
}
/**
* Returns whether the passed `$template` has the legacy template block.
*
* @param object $template The template object.
* @return boolean
*/
public static function template_has_legacy_template_block( $template ) {
return has_block( 'woocommerce/legacy-template', $template->content );
}
/**
* Gets the templates saved in the database.
*
* @param array $slugs An array of slugs to retrieve templates for.
* @param string $template_type wp_template or wp_template_part.
*
* @return int[]|\WP_Post[] An array of found templates.
*/
public static function get_block_templates_from_db( $slugs = array(), $template_type = 'wp_template' ) {
$check_query_args = array(
'post_type' => $template_type,
'posts_per_page' => -1,
'no_found_rows' => true,
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
array(
'taxonomy' => 'wp_theme',
'field' => 'name',
'terms' => array( self::DEPRECATED_PLUGIN_SLUG, self::PLUGIN_SLUG, get_stylesheet() ),
),
),
);
if ( is_array( $slugs ) && count( $slugs ) > 0 ) {
$check_query_args['post_name__in'] = $slugs;
}
$check_query = new \WP_Query( $check_query_args );
$saved_woo_templates = $check_query->posts;
return array_map(
function( $saved_woo_template ) {
return self::build_template_result_from_post( $saved_woo_template );
},
$saved_woo_templates
);
}
}
BlocksWpQuery.php 0000644 00000004125 15155074006 0010034 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\Utils;
use WP_Query;
/**
* BlocksWpQuery query.
*
* Wrapper for WP Query with additional helper methods.
* Allows query args to be set and parsed without doing running it, so that a cache can be used.
*
* @deprecated 2.5.0
*/
class BlocksWpQuery extends WP_Query {
/**
* Constructor.
*
* Sets up the WordPress query, if parameter is not empty.
*
* Unlike the constructor in WP_Query, this does not RUN the query.
*
* @param string|array $query URL query string or array of vars.
*/
public function __construct( $query = '' ) {
if ( ! empty( $query ) ) {
$this->init();
$this->query = wp_parse_args( $query );
$this->query_vars = $this->query;
$this->parse_query_vars();
}
}
/**
* Get cached posts, if a cache exists.
*
* A hash is generated using the array of query_vars. If doing custom queries via filters such as posts_where
* (where the SQL query is manipulated directly) you can still ensure there is a unique hash by injecting custom
* query vars via the parse_query filter. For example:
*
* add_filter( 'parse_query', function( $wp_query ) {
* $wp_query->query_vars['my_custom_query_var'] = true;
* } );
*
* Doing so won't have any negative effect on the query itself, and it will cause the hash to change.
*
* @param string $transient_version Transient version to allow for invalidation.
* @return WP_Post[]|int[] Array of post objects or post IDs.
*/
public function get_cached_posts( $transient_version = '' ) {
$hash = md5( wp_json_encode( $this->query_vars ) );
$transient_name = 'wc_blocks_query_' . $hash;
$transient_value = get_transient( $transient_name );
if ( isset( $transient_value, $transient_value['version'], $transient_value['value'] ) && $transient_value['version'] === $transient_version ) {
return $transient_value['value'];
}
$results = $this->get_posts();
set_transient(
$transient_name,
array(
'version' => $transient_version,
'value' => $results,
),
DAY_IN_SECONDS * 30
);
return $results;
}
}
CartCheckoutUtils.php 0000644 00000006634 15155074006 0010671 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\Utils;
/**
* Class containing utility methods for dealing with the Cart and Checkout blocks.
*/
class CartCheckoutUtils {
/**
* Checks if the default cart page is using the Cart block.
*
* @return bool true if the WC cart page is using the Cart block.
*/
public static function is_cart_block_default() {
if ( wc_current_theme_is_fse_theme() ) {
// Ignore the pages and check the templates.
$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'cart' ), 'wp_template' );
// If there is no template file, we're using default which does use the block.
if ( empty( $templates_from_db ) ) {
return true;
}
foreach ( $templates_from_db as $template ) {
if ( has_block( 'woocommerce/cart', $template->content ) ) {
return true;
}
}
}
$cart_page_id = wc_get_page_id( 'cart' );
return $cart_page_id && has_block( 'woocommerce/cart', $cart_page_id );
}
/**
* Checks if the default checkout page is using the Checkout block.
*
* @return bool true if the WC checkout page is using the Checkout block.
*/
public static function is_checkout_block_default() {
if ( wc_current_theme_is_fse_theme() ) {
// Ignore the pages and check the templates.
$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'checkout' ), 'wp_template' );
// If there is no template file, we're using default which does use the block.
if ( empty( $templates_from_db ) ) {
return true;
}
foreach ( $templates_from_db as $template ) {
if ( has_block( 'woocommerce/checkout', $template->content ) ) {
return true;
}
}
}
$checkout_page_id = wc_get_page_id( 'checkout' );
return $checkout_page_id && has_block( 'woocommerce/checkout', $checkout_page_id );
}
/**
* Gets country codes, names, states, and locale information.
*
* @return array
*/
public static function get_country_data() {
$billing_countries = WC()->countries->get_allowed_countries();
$shipping_countries = WC()->countries->get_shipping_countries();
$country_locales = wc()->countries->get_country_locale();
$country_states = wc()->countries->get_states();
$all_countries = self::deep_sort_with_accents( array_unique( array_merge( $billing_countries, $shipping_countries ) ) );
$country_data = [];
foreach ( array_keys( $all_countries ) as $country_code ) {
$country_data[ $country_code ] = [
'allowBilling' => isset( $billing_countries[ $country_code ] ),
'allowShipping' => isset( $shipping_countries[ $country_code ] ),
'states' => self::deep_sort_with_accents( $country_states[ $country_code ] ?? [] ),
'locale' => $country_locales[ $country_code ] ?? [],
];
}
return $country_data;
}
/**
* Removes accents from an array of values, sorts by the values, then returns the original array values sorted.
*
* @param array $array Array of values to sort.
* @return array Sorted array.
*/
protected static function deep_sort_with_accents( $array ) {
if ( ! is_array( $array ) || empty( $array ) ) {
return $array;
}
$array_without_accents = array_map(
function( $value ) {
return is_array( $value )
? self::deep_sort_with_accents( $value )
: remove_accents( wc_strtolower( html_entity_decode( $value ) ) );
},
$array
);
asort( $array_without_accents );
return array_replace( $array_without_accents, $array );
}
}
MiniCartUtils.php 0000644 00000002273 15155074006 0010013 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\Utils;
/**
* Utility methods used for the Mini Cart block.
*/
class MiniCartUtils {
/**
* Migrate attributes to color panel component format.
*
* @param array $attributes Any attributes that currently are available from the block.
* @return array Reformatted attributes that are compatible with the color panel component.
*/
public static function migrate_attributes_to_color_panel( $attributes ) {
if ( isset( $attributes['priceColorValue'] ) && ! isset( $attributes['priceColor'] ) ) {
$attributes['priceColor'] = array(
'color' => $attributes['priceColorValue'],
);
unset( $attributes['priceColorValue'] );
}
if ( isset( $attributes['iconColorValue'] ) && ! isset( $attributes['iconColor'] ) ) {
$attributes['iconColor'] = array(
'color' => $attributes['iconColorValue'],
);
unset( $attributes['iconColorValue'] );
}
if ( isset( $attributes['productCountColorValue'] ) && ! isset( $attributes['productCountColor'] ) ) {
$attributes['productCountColor'] = array(
'color' => $attributes['productCountColorValue'],
);
unset( $attributes['productCountColorValue'] );
}
return $attributes;
}
}
SettingsUtils.php 0000644 00000002726 15155074006 0010110 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\Utils;
use WC_Admin_Settings;
/**
* WooSettingsUtils class
*/
class SettingsUtils {
/**
* Input field for permalink settings/pages.
*
* @param array $value Input value.
*/
public static function permalink_input_field( $value ) {
$field_description = WC_Admin_Settings::get_field_description( $value );
$description = $field_description['description'];
$tooltip_html = $field_description['tooltip_html'];
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo wp_kses_post( $tooltip_html ); ?></label>
</th>
<td class="forminp forminp-text">
<span class="code" style="width: 400px; display:flex; align-items:center; gap:5px;">
<code class="permalink-custom" style="vertical-align: middle;">
<?php echo esc_html( get_site_url( null, '/' ) ); ?>
</code>
<input
name="<?php echo esc_attr( $value['field_name'] ); ?>"
id="<?php echo esc_attr( $value['id'] ); ?>"
type="text"
required
style="vertical-align: middle;"
value="<?php echo esc_attr( $value['value'] ); ?>"
class="<?php echo esc_attr( $value['class'] ); ?>"
placeholder="<?php echo esc_attr( $value['placeholder'] ); ?>"
/><?php echo esc_html( $value['suffix'] ); ?>
</span>
<?php echo wp_kses_post( $description ); ?>
</td>
</tr>
<?php
}
}
StyleAttributesUtils.php 0000644 00000044461 15155074006 0011461 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\Utils;
/**
* StyleAttributesUtils class used for getting class and style from attributes.
*/
class StyleAttributesUtils {
/**
* If color value is in preset format, convert it to a CSS var. Else return same value
* For example:
* "var:preset|color|pale-pink" -> "var(--wp--preset--color--pale-pink)"
* "#98b66e" -> "#98b66e"
*
* @param string $color_value value to be processed.
*
* @return (string)
*/
public static function get_color_value( $color_value ) {
if ( is_string( $color_value ) && str_contains( $color_value, 'var:preset|color|' ) ) {
$color_value = str_replace( 'var:preset|color|', '', $color_value );
return sprintf( 'var(--wp--preset--color--%s)', $color_value );
}
return $color_value;
}
/**
* Get CSS value for color preset.
*
* @param string $preset_name Preset name.
*
* @return string CSS value for color preset.
*/
public static function get_preset_value( $preset_name ) {
return "var(--wp--preset--color--$preset_name)";
}
/**
* If spacing value is in preset format, convert it to a CSS var. Else return same value
* For example:
* "var:preset|spacing|50" -> "var(--wp--preset--spacing--50)"
* "50px" -> "50px"
*
* @param string $spacing_value value to be processed.
*
* @return (string)
*/
public static function get_spacing_value( $spacing_value ) {
// Used following code as reference: https://github.com/WordPress/gutenberg/blob/cff6d70d6ff5a26e212958623dc3130569f95685/lib/block-supports/layout.php/#L219-L225.
if ( is_string( $spacing_value ) && str_contains( $spacing_value, 'var:preset|spacing|' ) ) {
$spacing_value = str_replace( 'var:preset|spacing|', '', $spacing_value );
return sprintf( 'var(--wp--preset--spacing--%s)', $spacing_value );
}
return $spacing_value;
}
/**
* Get class and style for align from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_align_class_and_style( $attributes ) {
$align_attribute = $attributes['align'] ?? null;
if ( ! $align_attribute ) {
return null;
}
if ( 'wide' === $align_attribute ) {
return array(
'class' => 'alignwide',
'style' => null,
);
}
if ( 'full' === $align_attribute ) {
return array(
'class' => 'alignfull',
'style' => null,
);
}
if ( 'left' === $align_attribute ) {
return array(
'class' => 'alignleft',
'style' => null,
);
}
if ( 'right' === $align_attribute ) {
return array(
'class' => 'alignright',
'style' => null,
);
}
if ( 'center' === $align_attribute ) {
return array(
'class' => 'aligncenter',
'style' => null,
);
}
return null;
}
/**
* Get class and style for background-color from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_background_color_class_and_style( $attributes ) {
$background_color = $attributes['backgroundColor'] ?? '';
$custom_background_color = $attributes['style']['color']['background'] ?? '';
if ( ! $background_color && '' === $custom_background_color ) {
return null;
}
if ( $background_color ) {
return array(
'class' => sprintf( 'has-background has-%s-background-color', $background_color ),
'style' => null,
'value' => self::get_preset_value( $background_color ),
);
} elseif ( '' !== $custom_background_color ) {
return array(
'class' => null,
'style' => sprintf( 'background-color: %s;', $custom_background_color ),
'value' => $custom_background_color,
);
}
return null;
}
/**
* Get class and style for border-color from attributes.
*
* Data passed to this function is not always consistent. It can be:
* Linked - preset color: $attributes['borderColor'] => 'luminous-vivid-orange'.
* Linked - custom color: $attributes['style']['border']['color'] => '#681228'.
* Unlinked - preset color: $attributes['style']['border']['top']['color'] => 'var:preset|color|luminous-vivid-orange'
* Unlinked - custom color: $attributes['style']['border']['top']['color'] => '#681228'.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_border_color_class_and_style( $attributes ) {
$border_color_linked_preset = $attributes['borderColor'] ?? '';
$border_color_linked_custom = $attributes['style']['border']['color'] ?? '';
$custom_border = $attributes['style']['border'] ?? '';
$border_color_class = '';
$border_color_css = '';
if ( $border_color_linked_preset ) {
// Linked preset color.
$border_color_class = sprintf( 'has-border-color has-%s-border-color', $border_color_linked_preset );
} elseif ( $border_color_linked_custom ) {
// Linked custom color.
$border_color_css .= 'border-color:' . $border_color_linked_custom . ';';
} else {
// Unlinked.
if ( is_array( $custom_border ) ) {
foreach ( $custom_border as $border_color_key => $border_color_value ) {
if ( is_array( $border_color_value ) && array_key_exists( 'color', ( $border_color_value ) ) ) {
$border_color_css .= 'border-' . $border_color_key . '-color:' . self::get_color_value( $border_color_value['color'] ) . ';';
}
}
}
}
if ( ! $border_color_class && ! $border_color_css ) {
return null;
}
return array(
'class' => $border_color_class,
'style' => $border_color_css,
);
}
/**
* Get class and style for border-radius from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_border_radius_class_and_style( $attributes ) {
$custom_border_radius = $attributes['style']['border']['radius'] ?? '';
if ( '' === $custom_border_radius ) {
return null;
}
$border_radius_css = '';
if ( is_string( $custom_border_radius ) ) {
// Linked sides.
$border_radius_css = 'border-radius:' . $custom_border_radius . ';';
} else {
// Unlinked sides.
$border_radius = array();
$border_radius['border-top-left-radius'] = $custom_border_radius['topLeft'] ?? '';
$border_radius['border-top-right-radius'] = $custom_border_radius['topRight'] ?? '';
$border_radius['border-bottom-right-radius'] = $custom_border_radius['bottomRight'] ?? '';
$border_radius['border-bottom-left-radius'] = $custom_border_radius['bottomLeft'] ?? '';
foreach ( $border_radius as $border_radius_side => $border_radius_value ) {
if ( '' !== $border_radius_value ) {
$border_radius_css .= $border_radius_side . ':' . $border_radius_value . ';';
}
}
}
return array(
'class' => null,
'style' => $border_radius_css,
);
}
/**
* Get class and style for border width from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_border_width_class_and_style( $attributes ) {
$custom_border = $attributes['style']['border'] ?? '';
if ( '' === $custom_border ) {
return null;
}
$border_width_css = '';
if ( array_key_exists( 'width', ( $custom_border ) ) && ! empty( $custom_border['width'] ) ) {
// Linked sides.
$border_width_css = 'border-width:' . $custom_border['width'] . ';';
} else {
// Unlinked sides.
foreach ( $custom_border as $border_width_side => $border_width_value ) {
if ( isset( $border_width_value['width'] ) ) {
$border_width_css .= 'border-' . $border_width_side . '-width:' . $border_width_value['width'] . ';';
}
}
}
return array(
'class' => null,
'style' => $border_width_css,
);
}
/**
* Get space-separated classes from block attributes.
*
* @param array $attributes Block attributes.
* @param array $properties Properties to get classes from.
*
* @return string Space-separated classes.
*/
public static function get_classes_by_attributes( $attributes, $properties = array() ) {
$classes_and_styles = self::get_classes_and_styles_by_attributes( $attributes, $properties );
return $classes_and_styles['classes'];
}
/**
* Get class and style for font-family from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_font_family_class_and_style( $attributes ) {
$font_family = $attributes['fontFamily'] ?? '';
if ( $font_family ) {
return array(
'class' => sprintf( 'has-%s-font-family', $font_family ),
'style' => null,
);
}
return null;
}
/**
* Get class and style for font-size from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_font_size_class_and_style( $attributes ) {
$font_size = $attributes['fontSize'] ?? '';
$custom_font_size = $attributes['style']['typography']['fontSize'] ?? '';
if ( ! $font_size && '' === $custom_font_size ) {
return null;
}
if ( $font_size ) {
return array(
'class' => sprintf( 'has-font-size has-%s-font-size', $font_size ),
'style' => null,
);
} elseif ( '' !== $custom_font_size ) {
return array(
'class' => null,
'style' => sprintf( 'font-size: %s;', $custom_font_size ),
);
}
return null;
}
/**
* Get class and style for font-style from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_font_style_class_and_style( $attributes ) {
$custom_font_style = $attributes['style']['typography']['fontStyle'] ?? '';
if ( '' !== $custom_font_style ) {
return array(
'class' => null,
'style' => sprintf( 'font-style: %s;', $custom_font_style ),
);
}
return null;
}
/**
* Get class and style for font-weight from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_font_weight_class_and_style( $attributes ) {
$custom_font_weight = $attributes['style']['typography']['fontWeight'] ?? '';
if ( '' !== $custom_font_weight ) {
return array(
'class' => null,
'style' => sprintf( 'font-weight: %s;', $custom_font_weight ),
);
}
return null;
}
/**
* Get class and style for letter-spacing from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_letter_spacing_class_and_style( $attributes ) {
$custom_letter_spacing = $attributes['style']['typography']['letterSpacing'] ?? '';
if ( '' !== $custom_letter_spacing ) {
return array(
'class' => null,
'style' => sprintf( 'letter-spacing: %s;', $custom_letter_spacing ),
);
}
return null;
}
/**
* Get class and style for line height from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_line_height_class_and_style( $attributes ) {
$line_height = $attributes['style']['typography']['lineHeight'] ?? '';
if ( ! $line_height ) {
return null;
}
return array(
'class' => null,
'style' => sprintf( 'line-height: %s;', $line_height ),
);
}
/**
* Get class and style for link-color from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_link_color_class_and_style( $attributes ) {
if ( ! isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
return null;
}
$link_color = $attributes['style']['elements']['link']['color']['text'];
// If the link color is selected from the theme color picker, the value of $link_color is var:preset|color|slug.
// If the link color is selected from the core color picker, the value of $link_color is an hex value.
// When the link color is a string var:preset|color|slug we parsed it for get the slug, otherwise we use the hex value.
$index_named_link_color = strrpos( $link_color, '|' );
if ( ! empty( $index_named_link_color ) ) {
$parsed_named_link_color = substr( $link_color, $index_named_link_color + 1 );
return array(
'class' => null,
'style' => sprintf( 'color: %s;', self::get_preset_value( $parsed_named_link_color ) ),
'value' => self::get_preset_value( $parsed_named_link_color ),
);
} else {
return array(
'class' => null,
'style' => sprintf( 'color: %s;', $link_color ),
'value' => $link_color,
);
}
}
/**
* Get class and style for margin from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_margin_class_and_style( $attributes ) {
$margin = $attributes['style']['spacing']['margin'] ?? null;
if ( ! $margin ) {
return null;
}
$spacing_values_css = '';
foreach ( $margin as $margin_side => $margin_value ) {
$spacing_values_css .= 'margin-' . $margin_side . ':' . self::get_spacing_value( $margin_value ) . ';';
}
return array(
'class' => null,
'style' => $spacing_values_css,
);
}
/**
* Get class and style for padding from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_padding_class_and_style( $attributes ) {
$padding = $attributes['style']['spacing']['padding'] ?? null;
if ( ! $padding ) {
return null;
}
$spacing_values_css = '';
foreach ( $padding as $padding_side => $padding_value ) {
$spacing_values_css .= 'padding-' . $padding_side . ':' . self::get_spacing_value( $padding_value ) . ';';
}
return array(
'class' => null,
'style' => $spacing_values_css,
);
}
/**
* Get space-separated style rules from block attributes.
*
* @param array $attributes Block attributes.
* @param array $properties Properties to get styles from.
*
* @return string Space-separated style rules.
*/
public static function get_styles_by_attributes( $attributes, $properties = array() ) {
$classes_and_styles = self::get_classes_and_styles_by_attributes( $attributes, $properties );
return $classes_and_styles['styles'];
}
/**
* Get class and style for text align from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_text_align_class_and_style( $attributes ) {
if ( isset( $attributes['textAlign'] ) ) {
return array(
'class' => 'has-text-align-' . $attributes['textAlign'],
'style' => null,
);
}
return null;
}
/**
* Get class and style for text-color from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_text_color_class_and_style( $attributes ) {
$text_color = $attributes['textColor'] ?? '';
$custom_text_color = $attributes['style']['color']['text'] ?? '';
if ( ! $text_color && ! $custom_text_color ) {
return null;
}
if ( $text_color ) {
return array(
'class' => sprintf( 'has-text-color has-%s-color', $text_color ),
'style' => null,
'value' => self::get_preset_value( $text_color ),
);
} elseif ( $custom_text_color ) {
return array(
'class' => null,
'style' => sprintf( 'color: %s;', $custom_text_color ),
'value' => $custom_text_color,
);
}
return null;
}
/**
* Get class and style for text-decoration from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_text_decoration_class_and_style( $attributes ) {
$custom_text_decoration = $attributes['style']['typography']['textDecoration'] ?? '';
if ( '' !== $custom_text_decoration ) {
return array(
'class' => null,
'style' => sprintf( 'text-decoration: %s;', $custom_text_decoration ),
);
}
return null;
}
/**
* Get class and style for text-transform from attributes.
*
* @param array $attributes Block attributes.
*
* @return (array | null)
*/
public static function get_text_transform_class_and_style( $attributes ) {
$custom_text_transform = $attributes['style']['typography']['textTransform'] ?? '';
if ( '' !== $custom_text_transform ) {
return array(
'class' => null,
'style' => sprintf( 'text-transform: %s;', $custom_text_transform ),
);
}
return null;
}
/**
* Get classes and styles from attributes.
*
* @param array $attributes Block attributes.
* @param array $properties Properties to get classes/styles from.
*
* @return array
*/
public static function get_classes_and_styles_by_attributes( $attributes, $properties = array() ) {
$classes_and_styles = array(
'align' => self::get_align_class_and_style( $attributes ),
'background_color' => self::get_background_color_class_and_style( $attributes ),
'border_color' => self::get_border_color_class_and_style( $attributes ),
'border_radius' => self::get_border_radius_class_and_style( $attributes ),
'border_width' => self::get_border_width_class_and_style( $attributes ),
'font_family' => self::get_font_family_class_and_style( $attributes ),
'font_size' => self::get_font_size_class_and_style( $attributes ),
'font_style' => self::get_font_style_class_and_style( $attributes ),
'font_weight' => self::get_font_weight_class_and_style( $attributes ),
'letter_spacing' => self::get_letter_spacing_class_and_style( $attributes ),
'line_height' => self::get_line_height_class_and_style( $attributes ),
'link_color' => self::get_link_color_class_and_style( $attributes ),
'margin' => self::get_margin_class_and_style( $attributes ),
'padding' => self::get_padding_class_and_style( $attributes ),
'text_align' => self::get_text_align_class_and_style( $attributes ),
'text_color' => self::get_text_color_class_and_style( $attributes ),
'text_decoration' => self::get_text_decoration_class_and_style( $attributes ),
'text_transform' => self::get_text_transform_class_and_style( $attributes ),
);
if ( ! empty( $properties ) ) {
foreach ( $classes_and_styles as $key => $value ) {
if ( ! in_array( $key, $properties, true ) ) {
unset( $classes_and_styles[ $key ] );
}
}
}
$classes_and_styles = array_filter( $classes_and_styles );
$classes = array_map(
function( $item ) {
return $item['class'];
},
$classes_and_styles
);
$styles = array_map(
function( $item ) {
return $item['style'];
},
$classes_and_styles
);
$classes = array_filter( $classes );
$styles = array_filter( $styles );
return array(
'classes' => implode( ' ', $classes ),
'styles' => implode( ' ', $styles ),
);
}
}
Utils.php 0000644 00000001775 15155074006 0006372 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\Utils;
/**
* Utils class
*/
class Utils {
/**
* Compare the current WordPress version with a given version. It's a wrapper around `version-compare`
* that additionally takes into account the suffix (like `-RC1`).
* For example: version 6.3 is considered lower than 6.3-RC2, so you can do
* wp_version_compare( '6.3', '>=' ) and that will return true for 6.3-RC2.
*
* @param string $version The version to compare against.
* @param string|null $operator Optional. The comparison operator. Defaults to null.
* @return bool|int Returns true if the current WordPress version satisfies the comparison, false otherwise.
*/
public static function wp_version_compare( $version, $operator = null ) {
$current_wp_version = get_bloginfo( 'version' );
if ( preg_match( '/^([0-9]+\.[0-9]+)/', $current_wp_version, $matches ) ) {
$current_wp_version = (float) $matches[1];
}
return version_compare( $current_wp_version, $version, $operator );
}
}