Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,387 @@
<?php
/**
* Divi integration.
*
* @since 2.0.8
* @package RankMath
* @subpackage RankMath\Core
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Divi;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Traits\Meta;
defined( 'ABSPATH' ) || exit;
/**
* Elementor class.
*/
class Divi {
use Meta;
use Hooker;
/**
* Holds data of FAQ schema activated accordions.
*
* @var array
*/
private $faq_accordion_data = [];
/**
* Class constructor.
*/
public function __construct() {
$this->filter( 'et_builder_get_parent_modules', 'filter_et_builder_parent_modules' );
$this->filter( 'rank_math/json_ld', 'add_faq_schema', 10 );
$this->action( 'wp_footer', 'add_divi_scripts' );
}
/**
* Get accordion data.
*
* This function is a bit of a reconstruction of WP's `do_shortcode` function
* in order to retreive the setting from Divi's accordion module.
*/
public function get_accordion_data() {
$post_content = get_the_content();
if ( ! has_shortcode( $post_content, 'et_pb_accordion' ) ) {
return [];
}
$accordions = $this->get_shortcode_data( $post_content, 'et_pb_accordion' );
foreach ( $accordions as &$accordion ) {
if ( ! empty( $accordion['content'] ) ) {
$accordion['content'] = $this->get_shortcode_data(
$accordion['content'],
'et_pb_accordion_item',
false
);
}
}
return array_filter( $accordions );
}
/**
* Get shortcode data.
*
* @param string $string The string to search for shortcodes.
* @param string|array $tagname The shortcode name as a string or an array of names.
* @param bool $check_for_schema Whether to only allow truthy schema attr shortcodes.
*
* @return array Array of all found shortcodes.
*/
public function get_shortcode_data( $string, $tagname, $check_for_schema = true ) {
$pattern = get_shortcode_regex( is_array( $tagname ) ? $tagname : [ $tagname ] );
if ( ! preg_match_all( "/$pattern/s", $string, $matches, PREG_SET_ORDER ) ) {
return [];
}
return array_map(
function( $m ) use ( $check_for_schema ) {
global $shortcode_tags;
// Allow [[foo]] syntax for escaping a tag.
if ( '[' === $m[1] && ']' === $m[6] ) {
return [];
}
$attr = shortcode_parse_atts( $m[3] );
if (
$check_for_schema &&
(
! isset( $attr['rank_math_faq_schema'] ) ||
! filter_var( $attr['rank_math_faq_schema'], FILTER_VALIDATE_BOOLEAN )
)
) {
return [];
}
$tag = $m[2];
/**
* Filters whether to call a shortcode callback.
*
* NOTE: This is a WP core filter through which a shortcode can be prevented
* from being rendered.
*
* @param false|string $return Short-circuit return value. Either false or the value to replace the shortcode with.
* @param string $tag Shortcode name.
* @param array|string $attr Shortcode attributes array or empty string.
* @param array $m Regular expression match array.
*/
// phpcs:ignore
if ( apply_filters( 'pre_do_shortcode_tag', false, $tag, $attr, $m ) ) {
return [];
}
$content = isset( $m[5] ) ? $m[5] : '';
if ( has_filter( 'do_shortcode_tag' ) ) {
$output = $m[1] . call_user_func( $shortcode_tags[ $tag ], $attr, $content, $tag ) . $m[6];
/**
* Filters the output created by a shortcode callback.
*
* NOTE: This is a WP core filter through which a shortcode can be prevented
* from being rendered.
*
* @param string $output Shortcode output.
* @param string $tag Shortcode name.
* @param array|string $attr Shortcode attributes array or empty string.
* @param array $m Regular expression match array.
*/
$output = apply_filters( 'do_shortcode_tag', $output, $tag, $attr, $m );
if ( empty( $output ) ) {
return [];
}
}
return [
'tag' => $tag,
'atts' => $attr,
'content' => $content,
];
},
$matches
);
}
/**
* Add FAQ schema using the accordion content.
*
* @param array $data Array of json-ld data.
*
* @return array
*/
public function add_faq_schema( $data ) {
if ( ! is_singular() ) {
return $data;
}
$accordions = $this->get_accordion_data();
if ( empty( $accordions ) ) {
return $data;
}
$data['faq-data'] = [
'@type' => 'FAQPage',
];
foreach ( $accordions as $accordion ) {
if ( empty( $accordion['content'] ) || ! is_array( $accordion['content'] ) ) {
continue;
}
foreach ( $accordion['content'] as $item ) {
if ( empty( $item['atts']['title'] ) ) {
continue;
}
$data['faq-data']['mainEntity'][] = [
'@type' => 'Question',
'name' => $item['atts']['title'],
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => $item['content'],
],
];
}
}
return $data;
}
/**
* Enqueue assets for Divi frontend editor.
*
* @return void
*/
public function add_divi_scripts() {
if ( ! $this->can_add_tab() ) {
return;
}
$this->add_global_json_data();
wp_dequeue_script( 'rank-math-pro-metabox' );
wp_enqueue_style(
'rank-math-pro-editor',
RANK_MATH_PRO_URL . 'assets/admin/css/divi.css',
[],
RANK_MATH_PRO_VERSION
);
wp_enqueue_script(
'rank-math-pro-editor',
RANK_MATH_PRO_URL . 'assets/admin/js/divi.js',
[
'rm-react',
'rm-react-dom',
'jquery-ui-autocomplete',
'moment',
'wp-components',
'wp-compose',
'wp-data',
'wp-element',
'wp-hooks',
'wp-i18n',
'wp-plugins',
],
RANK_MATH_PRO_VERSION,
true
);
}
/**
* Add JSON data to rankMath global variable.
*/
private function add_global_json_data() {
$id = get_the_ID();
$robots = $this->get_meta( 'post', $id, 'rank_math_news_sitemap_robots' );
Helper::add_json(
'newsSitemap',
[
'robots' => $robots ? $robots : 'index',
]
);
}
/**
* Show field check callback.
*
* @return boolean
*/
private function can_add_tab() {
if (
! Helper::is_divi_frontend_editor() ||
! defined( 'ET_BUILDER_PRODUCT_VERSION' ) ||
! version_compare( '4.9.2', ET_BUILDER_PRODUCT_VERSION, 'le' )
) {
return false;
}
return true;
}
/**
* Add custom toggle (options group) and custom field option on all modules.
*
* @param array $modules ET builder modules.
*
* @return array Returns ET builder modules.
*/
public function filter_et_builder_parent_modules( $modules ) {
if ( empty( $modules ) ) {
return $modules;
}
if ( isset( $modules['et_pb_accordion'] ) ) {
$modules['et_pb_accordion'] = $this->filter_module_et_pb_accordion(
$modules['et_pb_accordion']
);
}
return $modules;
}
/**
* Filter ET Accordion module.
*
* @param object $module The Accordion module.
* @return object $module Returns the module.
*/
private function filter_module_et_pb_accordion( $module ) {
static $is_accordion_filtered = false;
if (
$is_accordion_filtered ||
! isset( $module->settings_modal_toggles ) ||
! isset( $module->fields_unprocessed )
) {
return $module;
}
/**
* Toggles list on the module.
*
* @var array
*
* Official tabs list:
* 'general': Content tab.
* 'advanced': Design tab.
* 'custom_css': Advanced tab.
*
* The structures:
* array(
* 'general' => array(),
* 'advanced' => array(),
* 'custom_css' => array(
* 'toggles' => array(
* 'toggle_slug' => $toggle_definition,
* ... Other toggles.
* ),
* ),
* ... Other tabs if they exist.
* )
*/
$toggles_list = $module->settings_modal_toggles;
// Add Rank Math toggle on general tab.
if (
isset( $toggles_list['general'] ) &&
! empty( $toggles_list['general']['toggles'] )
) {
$toggles_list['general']['toggles']['rank_math_faq_schema_toggle'] = [
'title' => wp_strip_all_tags( __( 'Rank Math FAQ Schema', 'rank-math-pro' ) ),
'priority' => 220,
];
$module->settings_modal_toggles = $toggles_list;
}
/**
* Fields list on the module.
*
* @var array
*
* The structures:
* array(
* 'field_slug' => array(
* 'label' => '',
* 'description' => '',
* 'type' => '',
* 'toggle_slug' => '',
* 'tab_slug' => '',
* ),
* ... Other fields.
* )
*/
$fields_list = $module->fields_unprocessed;
// Add 'Member Field' option on 'Member Toggle' options group.
if ( ! empty( $fields_list ) ) {
$fields_list['rank_math_faq_schema'] = [
'label' => wp_strip_all_tags( __( 'Add FAQ Schema Markup', 'rank-math-pro' ) ),
'description' => wp_strip_all_tags( __( 'Added by the Rank Math SEO Plugin.', 'rank-math-pro' ) ),
'toggle_slug' => 'rank_math_faq_schema_toggle',
'tab_slug' => 'general',
'type' => 'yes_no_button',
'default' => 'off',
'options' => [
'on' => wp_strip_all_tags( __( 'Yes', 'rank-math-pro' ) ),
'off' => wp_strip_all_tags( __( 'No', 'rank-math-pro' ) ),
],
];
$module->fields_unprocessed = $fields_list;
}
$is_accordion_filtered = true;
return $module;
}
}

View File

@@ -0,0 +1,161 @@
<?php
/**
* Elementor integration.
*
* @since 2.0.8
* @package RankMath
* @subpackage RankMath\Core
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Elementor;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use Elementor\Controls_Manager;
defined( 'ABSPATH' ) || exit;
/**
* Elementor class.
*/
class Elementor {
use Hooker;
/**
* Class constructor.
*/
public function __construct() {
$this->action( 'elementor/editor/before_enqueue_scripts', 'editor_scripts' );
$this->action( 'elementor/widgets/register', 'add_breadcrumb_widget' );
$this->action( 'elementor/element/accordion/section_title/before_section_end', 'add_faq_setting', 99 );
$this->filter( 'rank_math/json_ld', 'add_faq_schema', 99 );
}
/**
* Enqueue the editor scripts.
*/
public function editor_scripts() {
wp_dequeue_script( 'rank-math-pro-metabox' );
wp_enqueue_style(
'rank-math-pro-editor',
RANK_MATH_PRO_URL . 'assets/admin/css/elementor.css',
[],
RANK_MATH_PRO_VERSION
);
wp_enqueue_script(
'rank-math-pro-editor',
RANK_MATH_PRO_URL . 'assets/admin/js/elementor.js',
[
'rank-math-editor',
],
RANK_MATH_PRO_VERSION,
true
);
}
/**
* Add Breadcrumb Widget in Elementor Editor.
*
* @param Widgets_Manager $widget The widgets manager.
*/
public function add_breadcrumb_widget( $widget ) {
$widget->register( new Widget_Breadcrumbs() );
}
/**
* Add toggle to enable/disable FAQ schema in Accordion Widget.
*
* @param Controls_Stack $widget The control.
*/
public function add_faq_setting( $widget ) {
$widget->add_control(
'rank_math_add_faq_schema',
[
'label' => esc_html__( 'Add FAQ Schema Markup', 'rank-math-pro' ),
'type' => Controls_Manager::SWITCHER,
'separator' => 'before',
'description' => esc_html__( 'Added by the Rank Math SEO Plugin.', 'rank-math-pro' ),
]
);
}
/**
* Add FAQ schema using the accordion content.
*
* @param array $data Array of json-ld data.
*
* @return array
*/
public function add_faq_schema( $data ) {
if ( ! is_singular() ) {
return $data;
}
global $post;
$elementor_document = \Elementor\Plugin::$instance->documents->get( $post->ID );
if ( ! $elementor_document || ! $elementor_document->is_built_with_elementor() ) {
return $data;
}
$elementor_data = get_post_meta( $post->ID, '_elementor_data', true );
if ( ! empty( $elementor_data ) && is_string( $elementor_data ) ) {
$elementor_data = json_decode( $elementor_data, true );
}
$accordion_data = $this->get_accordion_data( $elementor_data );
if ( empty( $accordion_data ) ) {
return $data;
}
$data['faq-data'] = [
'@type' => 'FAQPage',
];
foreach ( $accordion_data as $faqs ) {
foreach ( $faqs as $faq ) {
$data['faq-data']['mainEntity'][] = [
'@type' => 'Question',
'name' => $faq['tab_title'],
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => $faq['tab_content'],
],
];
}
}
return $data;
}
/**
* Get accordion data.
*
* @param array $elements Elements Data.
*
* @return array
*/
private function get_accordion_data( $elements ) {
if ( ! is_array( $elements ) ) {
return [];
}
$results = [];
if (
isset( $elements['rank_math_add_faq_schema'] ) &&
'yes' === $elements['rank_math_add_faq_schema'] &&
! empty( $elements['tabs'] )
) {
$results[] = $elements['tabs'];
}
foreach ( $elements as $element ) {
$results = array_merge( $results, $this->get_accordion_data( $element ) );
}
return $results;
}
}

View File

@@ -0,0 +1,232 @@
<?php
/**
* Elementor Integration.
*
* @since 2.0.8
* @package RankMathPro
* @subpackage RankMath\Core
* @author Rank Math <support@rankmath.com>
* @copyright Copyright (C) 2008-2019, Elementor Ltd
* The following code is a derivative work of the code from the Elementor(https://github.com/elementor/elementor/), which is licensed under GPL v3.
*/
namespace RankMathPro\Elementor;
use RankMath\Helper;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Typography;
use Elementor\Core\Schemes\Typography;
defined( 'ABSPATH' ) || exit;
/**
* Elementor Breadcrumb Widget class.
*/
class Widget_Breadcrumbs extends Widget_Base {
/**
* Get element name.
*
* @return string The name.
*/
public function get_name() {
return 'breadcrumbs';
}
/**
* Get element title.
*
* @return string Element title.
*/
public function get_title() {
return __( 'Breadcrumbs', 'rank-math-pro' );
}
/**
* Get element icon.
*
* @return string Element icon.
*/
public function get_icon() {
return 'eicon-rank-math';
}
/**
* Get widget keywords.
*
* @return array Widget keywords.
*/
public function get_keywords() {
return [ 'rankmath', 'seo', 'breadcrumbs', 'rank math', 'schema' ];
}
/**
* Register model controls. Used to add new controls to the page settings model.
*/
protected function register_controls() {
$this->start_controls_section(
'section_breadcrumbs_content',
[
'label' => __( 'Breadcrumbs', 'rank-math-pro' ),
]
);
if ( ! Helper::is_breadcrumbs_enabled() ) {
$this->add_control(
'html_disabled_alert',
[
'raw' => __( 'Breadcrumbs are disabled in the Rank Math SEO', 'rank-math-pro' ) . ' ' . sprintf( '<a href="%s" target="_blank">%s</a>', admin_url( 'admin.php?page=rank-math-options-general#setting-panel-breadcrumbs' ), __( 'Breadcrumbs Panel', 'rank-math-pro' ) ),
'type' => Controls_Manager::RAW_HTML,
'content_classes' => 'elementor-panel-alert elementor-panel-alert-danger',
]
);
}
$this->add_responsive_control(
'align',
[
'label' => __( 'Alignment', 'rank-math-pro' ),
'type' => Controls_Manager::CHOOSE,
'options' => [
'left' => [
'title' => __( 'Left', 'rank-math-pro' ),
'icon' => 'eicon-text-align-left',
],
'center' => [
'title' => __( 'Center', 'rank-math-pro' ),
'icon' => 'eicon-text-align-center',
],
'right' => [
'title' => __( 'Right', 'rank-math-pro' ),
'icon' => 'eicon-text-align-right',
],
],
'prefix_class' => 'elementor%s-align-',
]
);
$this->add_control(
'html_tag',
[
'label' => __( 'HTML Tag', 'rank-math-pro' ),
'type' => Controls_Manager::SELECT,
'options' => [
'' => __( 'Default', 'rank-math-pro' ),
'p' => 'p',
'div' => 'div',
'nav' => 'nav',
'span' => 'span',
],
'default' => '',
]
);
$this->add_control(
'html_description',
[
'raw' => __( 'Additional settings are available in the Rank Math SEO', 'rank-math-pro' ) . ' ' . sprintf( '<a href="%s" target="_blank">%s</a>', admin_url( 'admin.php?page=rank-math-options-general#setting-panel-breadcrumbs' ), __( 'Breadcrumbs Panel', 'rank-math-pro' ) ),
'type' => Controls_Manager::RAW_HTML,
'content_classes' => 'elementor-descriptor',
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_style',
[
'label' => __( 'Breadcrumbs', 'rank-math-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'typography',
'selector' => '{{WRAPPER}}',
'scheme' => Typography::TYPOGRAPHY_2,
]
);
$this->add_control(
'text_color',
[
'label' => __( 'Text Color', 'rank-math-pro' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}}' => 'color: {{VALUE}};',
],
]
);
$this->start_controls_tabs( 'tabs_breadcrumbs_style' );
$this->start_controls_tab(
'tab_color_normal',
[
'label' => __( 'Normal', 'rank-math-pro' ),
]
);
$this->add_control(
'link_color',
[
'label' => __( 'Link Color', 'rank-math-pro' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}} a' => 'color: {{VALUE}};',
],
]
);
$this->end_controls_tab();
$this->start_controls_tab(
'tab_color_hover',
[
'label' => __( 'Hover', 'rank-math-pro' ),
]
);
$this->add_control(
'link_hover_color',
[
'label' => __( 'Color', 'rank-math-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} a:hover' => 'color: {{VALUE}};',
],
]
);
$this->end_controls_section();
}
/**
* Get HTML tag. Retrieve the section element HTML tag.
*
* @param array $args Html tags args.
*
* @return array Section HTML tag.
*/
public function get_html_tag( $args ) {
$html_tag = $this->get_settings( 'html_tag' );
if ( $html_tag ) {
$args['wrap_before'] = "<{$html_tag}>";
$args['wrap_after'] = "</{$html_tag}>";
}
return $args;
}
/**
* Render element. Generates the final HTML on the frontend.
*/
protected function render() {
add_filter( 'rank_math/frontend/breadcrumb/args', [ $this, 'get_html_tag' ] );
rank_math_the_breadcrumbs();
}
}

View File

@@ -0,0 +1,619 @@
<?php
/**
* Admin helper Functions.
*
* This file contains functions needed on the admin screens.
*
* @since 2.0.0
* @package RankMath
* @subpackage RankMath\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Helper;
use RankMath\Admin\Admin_Helper as Free_Admin_Helper;
defined( 'ABSPATH' ) || exit;
/**
* Admin_Helper class.
*/
class Admin_Helper {
/**
* Get primary term ID.
*
* @param int $post_id Post ID.
*
* @return int
*/
public static function get_primary_term_id( $post_id = null ) {
$taxonomy = self::get_primary_taxonomy( $post_id );
if ( ! $taxonomy ) {
return 0;
}
$id = get_post_meta( $post_id ? $post_id : get_the_ID(), 'rank_math_primary_' . $taxonomy['name'], true );
return $id ? absint( $id ) : 0;
}
/**
* Get current post type.
*
* @param int $post_id Post ID.
*
* @return string
*/
public static function get_current_post_type( $post_id = null ) {
if ( ! $post_id && function_exists( 'get_current_screen' ) ) {
$screen = get_current_screen();
return isset( $screen->post_type ) ? $screen->post_type : '';
}
return get_post_type( $post_id );
}
/**
* Get primary taxonomy.
*
* @param int $post_id Post ID.
*
* @return bool|array
*/
public static function get_primary_taxonomy( $post_id = null ) {
$taxonomy = false;
$post_type = self::get_current_post_type( $post_id );
/**
* Allow disabling the primary term feature.
*
* @param bool $return True to disable.
*/
if ( false === apply_filters( 'rank_math/admin/disable_primary_term', false ) ) {
$taxonomy = Helper::get_settings( 'titles.pt_' . $post_type . '_primary_taxonomy', false );
}
if ( ! $taxonomy ) {
return false;
}
$taxonomy = get_taxonomy( $taxonomy );
if ( ! $taxonomy ) {
return false;
}
$primary_taxonomy = [
'title' => $taxonomy->labels->singular_name,
'name' => $taxonomy->name,
'singularLabel' => $taxonomy->labels->singular_name,
'restBase' => ( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name,
];
return $primary_taxonomy;
}
/**
* Check if current plan is business.
*
* @return boolean
*/
public static function is_business_plan() {
return in_array( self::get_plan(), [ 'business', 'agency' ], true );
}
/**
* Get current plan.
*
* @return string
*/
public static function get_plan() {
$registered = Free_Admin_Helper::get_registration_data();
return isset( $registered['plan'] ) ? $registered['plan'] : 'pro';
}
/**
* Country.
*
* @return array
*/
public static function choices_countries() {
return [
'all' => __( 'Worldwide', 'rank-math-pro' ),
'AF' => __( 'Afghanistan', 'rank-math-pro' ),
'AL' => __( 'Albania', 'rank-math-pro' ),
'DZ' => __( 'Algeria', 'rank-math-pro' ),
'AS' => __( 'American Samoa', 'rank-math-pro' ),
'AD' => __( 'Andorra', 'rank-math-pro' ),
'AO' => __( 'Angola', 'rank-math-pro' ),
'AI' => __( 'Anguilla', 'rank-math-pro' ),
'AQ' => __( 'Antarctica', 'rank-math-pro' ),
'AG' => __( 'Antigua and Barbuda', 'rank-math-pro' ),
'AR' => __( 'Argentina', 'rank-math-pro' ),
'AM' => __( 'Armenia', 'rank-math-pro' ),
'AW' => __( 'Aruba', 'rank-math-pro' ),
'AU' => __( 'Australia', 'rank-math-pro' ),
'AT' => __( 'Austria', 'rank-math-pro' ),
'AZ' => __( 'Azerbaijan', 'rank-math-pro' ),
'BS' => __( 'Bahamas', 'rank-math-pro' ),
'BH' => __( 'Bahrain', 'rank-math-pro' ),
'BD' => __( 'Bangladesh', 'rank-math-pro' ),
'BB' => __( 'Barbados', 'rank-math-pro' ),
'BY' => __( 'Belarus', 'rank-math-pro' ),
'BE' => __( 'Belgium', 'rank-math-pro' ),
'BZ' => __( 'Belize', 'rank-math-pro' ),
'BJ' => __( 'Benin', 'rank-math-pro' ),
'BM' => __( 'Bermuda', 'rank-math-pro' ),
'BT' => __( 'Bhutan', 'rank-math-pro' ),
'BO' => __( 'Bolivia', 'rank-math-pro' ),
'BA' => __( 'Bosnia and Herzegovina', 'rank-math-pro' ),
'BW' => __( 'Botswana', 'rank-math-pro' ),
'BV' => __( 'Bouvet Island', 'rank-math-pro' ),
'BR' => __( 'Brazil', 'rank-math-pro' ),
'IO' => __( 'British Indian Ocean Territory', 'rank-math-pro' ),
'BN' => __( 'Brunei Darussalam', 'rank-math-pro' ),
'BG' => __( 'Bulgaria', 'rank-math-pro' ),
'BF' => __( 'Burkina Faso', 'rank-math-pro' ),
'BI' => __( 'Burundi', 'rank-math-pro' ),
'KH' => __( 'Cambodia', 'rank-math-pro' ),
'CM' => __( 'Cameroon', 'rank-math-pro' ),
'CA' => __( 'Canada', 'rank-math-pro' ),
'CV' => __( 'Cape Verde', 'rank-math-pro' ),
'KY' => __( 'Cayman Islands', 'rank-math-pro' ),
'CF' => __( 'Central African Republic', 'rank-math-pro' ),
'TD' => __( 'Chad', 'rank-math-pro' ),
'CL' => __( 'Chile', 'rank-math-pro' ),
'CN' => __( 'China', 'rank-math-pro' ),
'CX' => __( 'Christmas Island', 'rank-math-pro' ),
'CC' => __( 'Cocos (Keeling) Islands', 'rank-math-pro' ),
'CO' => __( 'Colombia', 'rank-math-pro' ),
'KM' => __( 'Comoros', 'rank-math-pro' ),
'CG' => __( 'Congo', 'rank-math-pro' ),
'CD' => __( 'Congo, the Democratic Republic of the', 'rank-math-pro' ),
'CK' => __( 'Cook Islands', 'rank-math-pro' ),
'CR' => __( 'Costa Rica', 'rank-math-pro' ),
'CI' => __( "Cote D'ivoire", 'rank-math-pro' ),
'HR' => __( 'Croatia', 'rank-math-pro' ),
'CU' => __( 'Cuba', 'rank-math-pro' ),
'CY' => __( 'Cyprus', 'rank-math-pro' ),
'CZ' => __( 'Czech Republic', 'rank-math-pro' ),
'DK' => __( 'Denmark', 'rank-math-pro' ),
'DJ' => __( 'Djibouti', 'rank-math-pro' ),
'DM' => __( 'Dominica', 'rank-math-pro' ),
'DO' => __( 'Dominican Republic', 'rank-math-pro' ),
'EC' => __( 'Ecuador', 'rank-math-pro' ),
'EG' => __( 'Egypt', 'rank-math-pro' ),
'SV' => __( 'El Salvador', 'rank-math-pro' ),
'GQ' => __( 'Equatorial Guinea', 'rank-math-pro' ),
'ER' => __( 'Eritrea', 'rank-math-pro' ),
'EE' => __( 'Estonia', 'rank-math-pro' ),
'ET' => __( 'Ethiopia', 'rank-math-pro' ),
'FK' => __( 'Falkland Islands (Malvinas)', 'rank-math-pro' ),
'FO' => __( 'Faroe Islands', 'rank-math-pro' ),
'FJ' => __( 'Fiji', 'rank-math-pro' ),
'FI' => __( 'Finland', 'rank-math-pro' ),
'FR' => __( 'France', 'rank-math-pro' ),
'GF' => __( 'French Guiana', 'rank-math-pro' ),
'PF' => __( 'French Polynesia', 'rank-math-pro' ),
'TF' => __( 'French Southern Territories', 'rank-math-pro' ),
'GA' => __( 'Gabon', 'rank-math-pro' ),
'GM' => __( 'Gambia', 'rank-math-pro' ),
'GE' => __( 'Georgia', 'rank-math-pro' ),
'DE' => __( 'Germany', 'rank-math-pro' ),
'GH' => __( 'Ghana', 'rank-math-pro' ),
'GI' => __( 'Gibraltar', 'rank-math-pro' ),
'GR' => __( 'Greece', 'rank-math-pro' ),
'GL' => __( 'Greenland', 'rank-math-pro' ),
'GD' => __( 'Grenada', 'rank-math-pro' ),
'GP' => __( 'Guadeloupe', 'rank-math-pro' ),
'GU' => __( 'Guam', 'rank-math-pro' ),
'GT' => __( 'Guatemala', 'rank-math-pro' ),
'GN' => __( 'Guinea', 'rank-math-pro' ),
'GW' => __( 'Guinea-Bissau', 'rank-math-pro' ),
'GY' => __( 'Guyana', 'rank-math-pro' ),
'HT' => __( 'Haiti', 'rank-math-pro' ),
'HM' => __( 'Heard Island and Mcdonald Islands', 'rank-math-pro' ),
'VA' => __( 'Holy See (Vatican City State)', 'rank-math-pro' ),
'HN' => __( 'Honduras', 'rank-math-pro' ),
'HK' => __( 'Hong Kong', 'rank-math-pro' ),
'HU' => __( 'Hungary', 'rank-math-pro' ),
'IS' => __( 'Iceland', 'rank-math-pro' ),
'IN' => __( 'India', 'rank-math-pro' ),
'ID' => __( 'Indonesia', 'rank-math-pro' ),
'IR' => __( 'Iran, Islamic Republic of', 'rank-math-pro' ),
'IQ' => __( 'Iraq', 'rank-math-pro' ),
'IE' => __( 'Ireland', 'rank-math-pro' ),
'IL' => __( 'Israel', 'rank-math-pro' ),
'IT' => __( 'Italy', 'rank-math-pro' ),
'JM' => __( 'Jamaica', 'rank-math-pro' ),
'JP' => __( 'Japan', 'rank-math-pro' ),
'JO' => __( 'Jordan', 'rank-math-pro' ),
'KZ' => __( 'Kazakhstan', 'rank-math-pro' ),
'KE' => __( 'Kenya', 'rank-math-pro' ),
'KI' => __( 'Kiribati', 'rank-math-pro' ),
'KP' => __( "Korea, Democratic People's Republic of", 'rank-math-pro' ),
'KR' => __( 'Korea, Republic of', 'rank-math-pro' ),
'KW' => __( 'Kuwait', 'rank-math-pro' ),
'KG' => __( 'Kyrgyzstan', 'rank-math-pro' ),
'LA' => __( "Lao People's Democratic Republic", 'rank-math-pro' ),
'LV' => __( 'Latvia', 'rank-math-pro' ),
'LB' => __( 'Lebanon', 'rank-math-pro' ),
'LS' => __( 'Lesotho', 'rank-math-pro' ),
'LR' => __( 'Liberia', 'rank-math-pro' ),
'LY' => __( 'Libyan Arab Jamahiriya', 'rank-math-pro' ),
'LI' => __( 'Liechtenstein', 'rank-math-pro' ),
'LT' => __( 'Lithuania', 'rank-math-pro' ),
'LU' => __( 'Luxembourg', 'rank-math-pro' ),
'MO' => __( 'Macao', 'rank-math-pro' ),
'MK' => __( 'Macedonia, the Former Yugosalv Republic of', 'rank-math-pro' ),
'MG' => __( 'Madagascar', 'rank-math-pro' ),
'MW' => __( 'Malawi', 'rank-math-pro' ),
'MY' => __( 'Malaysia', 'rank-math-pro' ),
'MV' => __( 'Maldives', 'rank-math-pro' ),
'ML' => __( 'Mali', 'rank-math-pro' ),
'MT' => __( 'Malta', 'rank-math-pro' ),
'MH' => __( 'Marshall Islands', 'rank-math-pro' ),
'MQ' => __( 'Martinique', 'rank-math-pro' ),
'MR' => __( 'Mauritania', 'rank-math-pro' ),
'MU' => __( 'Mauritius', 'rank-math-pro' ),
'YT' => __( 'Mayotte', 'rank-math-pro' ),
'MX' => __( 'Mexico', 'rank-math-pro' ),
'FM' => __( 'Micronesia, Federated States of', 'rank-math-pro' ),
'MC' => __( 'Moldova, Republic of', 'rank-math-pro' ),
'MP' => __( 'Northern Mariana Islands', 'rank-math-pro' ),
'MC' => __( 'Monaco', 'rank-math-pro' ),
'MN' => __( 'Mongolia', 'rank-math-pro' ),
'MS' => __( 'Montserrat', 'rank-math-pro' ),
'MA' => __( 'Morocco', 'rank-math-pro' ),
'MZ' => __( 'Mozambique', 'rank-math-pro' ),
'MM' => __( 'Myanmar', 'rank-math-pro' ),
'NA' => __( 'Namibia', 'rank-math-pro' ),
'NR' => __( 'Nauru', 'rank-math-pro' ),
'NP' => __( 'Nepal', 'rank-math-pro' ),
'NL' => __( 'Netherlands', 'rank-math-pro' ),
'AN' => __( 'Netherlands Antilles', 'rank-math-pro' ),
'NC' => __( 'New Caledonia', 'rank-math-pro' ),
'NZ' => __( 'New Zealand', 'rank-math-pro' ),
'NI' => __( 'Nicaragua', 'rank-math-pro' ),
'NE' => __( 'Niger', 'rank-math-pro' ),
'NG' => __( 'Nigeria', 'rank-math-pro' ),
'NU' => __( 'Niue', 'rank-math-pro' ),
'NF' => __( 'Norfolk Island', 'rank-math-pro' ),
'MP' => __( 'Northern Mariana Islands', 'rank-math-pro' ),
'NO' => __( 'Norway', 'rank-math-pro' ),
'OM' => __( 'Oman', 'rank-math-pro' ),
'PK' => __( 'Pakistan', 'rank-math-pro' ),
'PW' => __( 'Palau', 'rank-math-pro' ),
'PS' => __( 'Palestinian Territory, Occupied', 'rank-math-pro' ),
'PA' => __( 'Panama', 'rank-math-pro' ),
'PG' => __( 'Papua New Guinea', 'rank-math-pro' ),
'PY' => __( 'Paraguay', 'rank-math-pro' ),
'PE' => __( 'Peru', 'rank-math-pro' ),
'PH' => __( 'Philippines', 'rank-math-pro' ),
'PN' => __( 'Pitcairn', 'rank-math-pro' ),
'PL' => __( 'Poland', 'rank-math-pro' ),
'PT' => __( 'Portugal', 'rank-math-pro' ),
'PR' => __( 'Puerto Rico', 'rank-math-pro' ),
'QA' => __( 'Qatar', 'rank-math-pro' ),
'RE' => __( 'Reunion', 'rank-math-pro' ),
'RO' => __( 'Romania', 'rank-math-pro' ),
'RU' => __( 'Russian Federation', 'rank-math-pro' ),
'RW' => __( 'Rwanda', 'rank-math-pro' ),
'SH' => __( 'Saint Helena', 'rank-math-pro' ),
'KN' => __( 'Saint Kitts and Nevis', 'rank-math-pro' ),
'LC' => __( 'Saint Lucia', 'rank-math-pro' ),
'PM' => __( 'Saint Pierre and Miquelon', 'rank-math-pro' ),
'VC' => __( 'Saint Vincent and the Grenadines', 'rank-math-pro' ),
'WS' => __( 'Samoa', 'rank-math-pro' ),
'SM' => __( 'San Marino', 'rank-math-pro' ),
'ST' => __( 'Sao Tome and Principe', 'rank-math-pro' ),
'SA' => __( 'Saudi Arabia', 'rank-math-pro' ),
'SN' => __( 'Senegal', 'rank-math-pro' ),
'CS' => __( 'Serbia and Montenegro', 'rank-math-pro' ),
'SC' => __( 'Seychelles', 'rank-math-pro' ),
'SL' => __( 'Sierra Leone', 'rank-math-pro' ),
'SG' => __( 'Singapore', 'rank-math-pro' ),
'SK' => __( 'Slovakia', 'rank-math-pro' ),
'SI' => __( 'Slovenia', 'rank-math-pro' ),
'SB' => __( 'Solomon Islands', 'rank-math-pro' ),
'SO' => __( 'Somalia', 'rank-math-pro' ),
'ZA' => __( 'South Africa', 'rank-math-pro' ),
'GS' => __( 'South Georgia and the South Sandwich Islands', 'rank-math-pro' ),
'ES' => __( 'Spain', 'rank-math-pro' ),
'LK' => __( 'Sri Lanka', 'rank-math-pro' ),
'SD' => __( 'Sudan', 'rank-math-pro' ),
'SR' => __( 'Suriname', 'rank-math-pro' ),
'SJ' => __( 'Svalbard and Jan Mayen', 'rank-math-pro' ),
'SZ' => __( 'Swaziland', 'rank-math-pro' ),
'SE' => __( 'Sweden', 'rank-math-pro' ),
'CH' => __( 'Switzerland', 'rank-math-pro' ),
'SY' => __( 'Syrian Arab Republic', 'rank-math-pro' ),
'TW' => __( 'Taiwan, Province of China', 'rank-math-pro' ),
'TJ' => __( 'Tajikistan', 'rank-math-pro' ),
'TZ' => __( 'Tanzania, United Republic of', 'rank-math-pro' ),
'TH' => __( 'Thailand', 'rank-math-pro' ),
'TL' => __( 'Timor-Leste', 'rank-math-pro' ),
'TG' => __( 'Togo', 'rank-math-pro' ),
'TK' => __( 'Tokelau', 'rank-math-pro' ),
'TO' => __( 'Tonga', 'rank-math-pro' ),
'TT' => __( 'Trinidad and Tobago', 'rank-math-pro' ),
'TN' => __( 'Tunisia', 'rank-math-pro' ),
'TR' => __( 'Turkey', 'rank-math-pro' ),
'TM' => __( 'Turkmenistan', 'rank-math-pro' ),
'TC' => __( 'Turks and Caicos Islands', 'rank-math-pro' ),
'TV' => __( 'Tuvalu', 'rank-math-pro' ),
'UG' => __( 'Uganda', 'rank-math-pro' ),
'UA' => __( 'Ukraine', 'rank-math-pro' ),
'AE' => __( 'United Arab Emirates', 'rank-math-pro' ),
'GB' => __( 'United Kingdom', 'rank-math-pro' ),
'US' => __( 'United States', 'rank-math-pro' ),
'UM' => __( 'United States Minor Outlying Islands', 'rank-math-pro' ),
'UY' => __( 'Uruguay', 'rank-math-pro' ),
'UZ' => __( 'Uzbekistan', 'rank-math-pro' ),
'VU' => __( 'Vanuatu', 'rank-math-pro' ),
'VE' => __( 'Venezuela', 'rank-math-pro' ),
'VN' => __( 'Viet Nam', 'rank-math-pro' ),
'VG' => __( 'Virgin Islands, British', 'rank-math-pro' ),
'VI' => __( 'Virgin Islands, U.S.', 'rank-math-pro' ),
'WF' => __( 'Wallis and Futuna', 'rank-math-pro' ),
'EH' => __( 'Western Sahara', 'rank-math-pro' ),
'YE' => __( 'Yemen', 'rank-math-pro' ),
'ZM' => __( 'Zambia', 'rank-math-pro' ),
'ZW' => __( 'Zimbabwe', 'rank-math-pro' ),
];
}
/**
* Country.
*
* @return array
*/
public static function choices_countries_3() {
return [
'all' => __( 'Worldwide', 'rank-math-pro' ),
'AFG' => __( 'Afghanistan', 'rank-math-pro' ),
'ALA' => __( 'Aland Islands', 'rank-math-pro' ),
'ALB' => __( 'Albania', 'rank-math-pro' ),
'DZA' => __( 'Algeria', 'rank-math-pro' ),
'ASM' => __( 'American Samoa', 'rank-math-pro' ),
'AND' => __( 'Andorra', 'rank-math-pro' ),
'AGO' => __( 'Angola', 'rank-math-pro' ),
'AIA' => __( 'Anguilla', 'rank-math-pro' ),
'ATA' => __( 'Antarctica', 'rank-math-pro' ),
'ATG' => __( 'Antigua & Barbuda', 'rank-math-pro' ),
'ARG' => __( 'Argentina', 'rank-math-pro' ),
'ARM' => __( 'Armenia', 'rank-math-pro' ),
'ABW' => __( 'Aruba', 'rank-math-pro' ),
'AUS' => __( 'Australia', 'rank-math-pro' ),
'AUT' => __( 'Austria', 'rank-math-pro' ),
'AZE' => __( 'Azerbaijan', 'rank-math-pro' ),
'BHS' => __( 'Bahamas', 'rank-math-pro' ),
'BHR' => __( 'Bahrain', 'rank-math-pro' ),
'BGD' => __( 'Bangladesh', 'rank-math-pro' ),
'BRB' => __( 'Barbados', 'rank-math-pro' ),
'BLR' => __( 'Belarus', 'rank-math-pro' ),
'BEL' => __( 'Belgium', 'rank-math-pro' ),
'BLZ' => __( 'Belize', 'rank-math-pro' ),
'BEN' => __( 'Benin', 'rank-math-pro' ),
'BMU' => __( 'Bermuda', 'rank-math-pro' ),
'BTN' => __( 'Bhutan', 'rank-math-pro' ),
'BOL' => __( 'Bolivia', 'rank-math-pro' ),
'BIH' => __( 'Bosnia & Herzegovina', 'rank-math-pro' ),
'BWA' => __( 'Botswana', 'rank-math-pro' ),
'BRA' => __( 'Brazil', 'rank-math-pro' ),
'IOT' => __( 'British Indian Ocean Territory', 'rank-math-pro' ),
'VGB' => __( 'British Virgin Islands', 'rank-math-pro' ),
'BRN' => __( 'Brunei', 'rank-math-pro' ),
'BGR' => __( 'Bulgaria', 'rank-math-pro' ),
'BFA' => __( 'Burkina Faso', 'rank-math-pro' ),
'BDI' => __( 'Burundi', 'rank-math-pro' ),
'KHM' => __( 'Cambodia', 'rank-math-pro' ),
'CMR' => __( 'Cameroon', 'rank-math-pro' ),
'CAN' => __( 'Canada', 'rank-math-pro' ),
'CPV' => __( 'Cape Verde', 'rank-math-pro' ),
'BES' => __( 'Caribbean Netherlands', 'rank-math-pro' ),
'CYM' => __( 'Cayman Islands', 'rank-math-pro' ),
'CAF' => __( 'Central African Republic', 'rank-math-pro' ),
'TCD' => __( 'Chad', 'rank-math-pro' ),
'CHL' => __( 'Chile', 'rank-math-pro' ),
'CHN' => __( 'China', 'rank-math-pro' ),
'CXR' => __( 'Christmas Island', 'rank-math-pro' ),
'COL' => __( 'Colombia', 'rank-math-pro' ),
'COM' => __( 'Comoros', 'rank-math-pro' ),
'COG' => __( 'Congo - Brazzaville', 'rank-math-pro' ),
'COD' => __( 'Congo - Kinshasa', 'rank-math-pro' ),
'COK' => __( 'Cook Islands', 'rank-math-pro' ),
'CRI' => __( 'Costa Rica', 'rank-math-pro' ),
'HRV' => __( 'Croatia', 'rank-math-pro' ),
'CUB' => __( 'Cuba', 'rank-math-pro' ),
'CUW' => __( 'Curaçao', 'rank-math-pro' ),
'CYP' => __( 'Cyprus', 'rank-math-pro' ),
'CZE' => __( 'Czechia', 'rank-math-pro' ),
'DJI' => __( "Côte d'Ivoire", 'rank-math-pro' ),
'DNK' => __( 'Denmark', 'rank-math-pro' ),
'DJI' => __( 'Djibouti', 'rank-math-pro' ),
'DMA' => __( 'Dominica', 'rank-math-pro' ),
'DOM' => __( 'Dominican Republic', 'rank-math-pro' ),
'ECU' => __( 'Ecuador', 'rank-math-pro' ),
'EGY' => __( 'Egypt', 'rank-math-pro' ),
'SLV' => __( 'El Salvador', 'rank-math-pro' ),
'GNQ' => __( 'Equatorial Guinea', 'rank-math-pro' ),
'ERI' => __( 'Eritrea', 'rank-math-pro' ),
'EST' => __( 'Estonia', 'rank-math-pro' ),
'ETH' => __( 'Ethiopia', 'rank-math-pro' ),
'FLK' => __( 'Falkland Islands (Islas Malvinas)', 'rank-math-pro' ),
'FRO' => __( 'Faroe Islands', 'rank-math-pro' ),
'FJI' => __( 'Fiji', 'rank-math-pro' ),
'FIN' => __( 'Finland', 'rank-math-pro' ),
'FRA' => __( 'France', 'rank-math-pro' ),
'GUF' => __( 'French Guiana', 'rank-math-pro' ),
'PYF' => __( 'French Polynesia', 'rank-math-pro' ),
'GAB' => __( 'Gabon', 'rank-math-pro' ),
'GMB' => __( 'Gambia', 'rank-math-pro' ),
'GEO' => __( 'Georgia', 'rank-math-pro' ),
'DEU' => __( 'Germany', 'rank-math-pro' ),
'GHA' => __( 'Ghana', 'rank-math-pro' ),
'GIB' => __( 'Gibraltar', 'rank-math-pro' ),
'GRC' => __( 'Greece', 'rank-math-pro' ),
'GRL' => __( 'Greenland', 'rank-math-pro' ),
'GRD' => __( 'Grenada', 'rank-math-pro' ),
'GLP' => __( 'Guadeloupe', 'rank-math-pro' ),
'GUM' => __( 'Guam', 'rank-math-pro' ),
'GTM' => __( 'Guatemala', 'rank-math-pro' ),
'GGY' => __( 'Guernsey', 'rank-math-pro' ),
'GIN' => __( 'Guinea', 'rank-math-pro' ),
'GNB' => __( 'Guinea-Bissau', 'rank-math-pro' ),
'GUY' => __( 'Guyana', 'rank-math-pro' ),
'HTI' => __( 'Haiti', 'rank-math-pro' ),
'HND' => __( 'Honduras', 'rank-math-pro' ),
'HKG' => __( 'Hong Kong', 'rank-math-pro' ),
'HUN' => __( 'Hungary', 'rank-math-pro' ),
'ISL' => __( 'Iceland', 'rank-math-pro' ),
'IND' => __( 'India', 'rank-math-pro' ),
'IDN' => __( 'Indonesia', 'rank-math-pro' ),
'IRN' => __( 'Iran', 'rank-math-pro' ),
'IRQ' => __( 'Iraq', 'rank-math-pro' ),
'IRL' => __( 'Ireland', 'rank-math-pro' ),
'IMN' => __( 'Isle of Man', 'rank-math-pro' ),
'ISR' => __( 'Israel', 'rank-math-pro' ),
'ITA' => __( 'Italy', 'rank-math-pro' ),
'JAM' => __( 'Jamaica', 'rank-math-pro' ),
'JPN' => __( 'Japan', 'rank-math-pro' ),
'JEY' => __( 'Jersey', 'rank-math-pro' ),
'JOR' => __( 'Jordan', 'rank-math-pro' ),
'KAZ' => __( 'Kazakhstan', 'rank-math-pro' ),
'KEN' => __( 'Kenya', 'rank-math-pro' ),
'KIR' => __( 'Kiribati', 'rank-math-pro' ),
'XKK' => __( 'Kosovo', 'rank-math-pro' ),
'KWT' => __( 'Kuwait', 'rank-math-pro' ),
'KGZ' => __( 'Kyrgyzstan', 'rank-math-pro' ),
'LAO' => __( 'Laos', 'rank-math-pro' ),
'LBN' => __( 'Lebanon', 'rank-math-pro' ),
'LSO' => __( 'Lesotho', 'rank-math-pro' ),
'LBR' => __( 'Liberia', 'rank-math-pro' ),
'LBY' => __( 'Libya', 'rank-math-pro' ),
'LIE' => __( 'Liechtenstein', 'rank-math-pro' ),
'LTU' => __( 'Lithuania', 'rank-math-pro' ),
'LUX' => __( 'Luxembourg', 'rank-math-pro' ),
'MAC' => __( 'Macau', 'rank-math-pro' ),
'MKD' => __( 'Macedonia', 'rank-math-pro' ),
'MDG' => __( 'Madagascar', 'rank-math-pro' ),
'MWI' => __( 'Malawi', 'rank-math-pro' ),
'MYS' => __( 'Malaysia', 'rank-math-pro' ),
'MDV' => __( 'Maldives', 'rank-math-pro' ),
'MLI' => __( 'Mali', 'rank-math-pro' ),
'MLT' => __( 'Malta', 'rank-math-pro' ),
'MHL' => __( 'Marshall Islands', 'rank-math-pro' ),
'MTQ' => __( 'Martinique', 'rank-math-pro' ),
'MRT' => __( 'Mauritania', 'rank-math-pro' ),
'MUS' => __( 'Mauritius', 'rank-math-pro' ),
'MYT' => __( 'Mayotte', 'rank-math-pro' ),
'MEX' => __( 'Mexico', 'rank-math-pro' ),
'FSM' => __( 'Micronesia', 'rank-math-pro' ),
'MDA' => __( 'Moldova', 'rank-math-pro' ),
'MCO' => __( 'Monaco', 'rank-math-pro' ),
'MNG' => __( 'Mongolia', 'rank-math-pro' ),
'MNE' => __( 'Montenegro', 'rank-math-pro' ),
'MSR' => __( 'Montserrat', 'rank-math-pro' ),
'MAR' => __( 'Morocco', 'rank-math-pro' ),
'MOZ' => __( 'Mozambique', 'rank-math-pro' ),
'MMR' => __( 'Myanmar (Burma)', 'rank-math-pro' ),
'NAM' => __( 'Namibia', 'rank-math-pro' ),
'NRU' => __( 'Nauru', 'rank-math-pro' ),
'NPL' => __( 'Nepal', 'rank-math-pro' ),
'NLD' => __( 'Netherlands', 'rank-math-pro' ),
'NCL' => __( 'New Caledonia', 'rank-math-pro' ),
'NZL' => __( 'New Zealand', 'rank-math-pro' ),
'NIC' => __( 'Nicaragua', 'rank-math-pro' ),
'NER' => __( 'Niger', 'rank-math-pro' ),
'NGA' => __( 'Nigeria', 'rank-math-pro' ),
'NIU' => __( 'Niue', 'rank-math-pro' ),
'NFK' => __( 'Norfolk Island', 'rank-math-pro' ),
'PRK' => __( 'North Korea', 'rank-math-pro' ),
'MNP' => __( 'Northern Mariana Islands', 'rank-math-pro' ),
'NOR' => __( 'Norway', 'rank-math-pro' ),
'OMN' => __( 'Oman', 'rank-math-pro' ),
'PAK' => __( 'Pakistan', 'rank-math-pro' ),
'PLW' => __( 'Palau', 'rank-math-pro' ),
'PSE' => __( 'Palestine', 'rank-math-pro' ),
'PAN' => __( 'Panama', 'rank-math-pro' ),
'PNG' => __( 'Papua New Guinea', 'rank-math-pro' ),
'PRY' => __( 'Paraguay', 'rank-math-pro' ),
'PER' => __( 'Peru', 'rank-math-pro' ),
'PHL' => __( 'Philippines', 'rank-math-pro' ),
'POL' => __( 'Poland', 'rank-math-pro' ),
'PRT' => __( 'Portugal', 'rank-math-pro' ),
'PRI' => __( 'Puerto Rico', 'rank-math-pro' ),
'QAT' => __( 'Qatar', 'rank-math-pro' ),
'ROU' => __( 'Romania', 'rank-math-pro' ),
'RUS' => __( 'Russia', 'rank-math-pro' ),
'RWA' => __( 'Rwanda', 'rank-math-pro' ),
'REU' => __( 'Réunion', 'rank-math-pro' ),
'WSM' => __( 'Samoa', 'rank-math-pro' ),
'SMR' => __( 'San Marino', 'rank-math-pro' ),
'SAU' => __( 'Saudi Arabia', 'rank-math-pro' ),
'SEN' => __( 'Senegal', 'rank-math-pro' ),
'SRB' => __( 'Serbia', 'rank-math-pro' ),
'SYC' => __( 'Seychelles', 'rank-math-pro' ),
'SLE' => __( 'Sierra Leone', 'rank-math-pro' ),
'SGP' => __( 'Singapore', 'rank-math-pro' ),
'SXM' => __( 'Sint Maarten', 'rank-math-pro' ),
'SVK' => __( 'Slovakia', 'rank-math-pro' ),
'SVN' => __( 'Slovenia', 'rank-math-pro' ),
'SLB' => __( 'Solomon Islands', 'rank-math-pro' ),
'SOM' => __( 'Somalia', 'rank-math-pro' ),
'ZAF' => __( 'South Africa', 'rank-math-pro' ),
'KOR' => __( 'South Korea', 'rank-math-pro' ),
'SSD' => __( 'South Sudan', 'rank-math-pro' ),
'ESP' => __( 'Spain', 'rank-math-pro' ),
'LKA' => __( 'Sri Lanka', 'rank-math-pro' ),
'SHN' => __( 'St. Helena', 'rank-math-pro' ),
'KNA' => __( 'St. Kitts & Nevis', 'rank-math-pro' ),
'LCA' => __( 'St. Lucia', 'rank-math-pro' ),
'MAF' => __( 'St. Martin', 'rank-math-pro' ),
'SPM' => __( 'St. Pierre & Miquelon', 'rank-math-pro' ),
'VCT' => __( 'St. Vincent & Grenadines', 'rank-math-pro' ),
'SDN' => __( 'Sudan', 'rank-math-pro' ),
'SUR' => __( 'Suriname', 'rank-math-pro' ),
'SJM' => __( 'Svalbard & Jan Mayen', 'rank-math-pro' ),
'SWZ' => __( 'Swaziland', 'rank-math-pro' ),
'SWE' => __( 'Sweden', 'rank-math-pro' ),
'CHE' => __( 'Switzerland', 'rank-math-pro' ),
'SYR' => __( 'Syria', 'rank-math-pro' ),
'STP' => __( 'São Tomé & Príncipe', 'rank-math-pro' ),
'TWN' => __( 'Taiwan', 'rank-math-pro' ),
'TJK' => __( 'Tajikistan', 'rank-math-pro' ),
'TZA' => __( 'Tanzania', 'rank-math-pro' ),
'THA' => __( 'Thailand', 'rank-math-pro' ),
'TLS' => __( 'Timor-Leste', 'rank-math-pro' ),
'TGO' => __( 'Togo', 'rank-math-pro' ),
'TON' => __( 'Tonga', 'rank-math-pro' ),
'TTO' => __( 'Trinidad & Tobago', 'rank-math-pro' ),
'TUN' => __( 'Tunisia', 'rank-math-pro' ),
'TUR' => __( 'Turkey', 'rank-math-pro' ),
'TKM' => __( 'Turkmenistan', 'rank-math-pro' ),
'TCA' => __( 'Turks & Caicos Islands', 'rank-math-pro' ),
'TUV' => __( 'Tuvalu', 'rank-math-pro' ),
'VIR' => __( 'U.S. Virgin Islands', 'rank-math-pro' ),
'UGA' => __( 'Uganda', 'rank-math-pro' ),
'UKR' => __( 'Ukraine', 'rank-math-pro' ),
'ARE' => __( 'United Arab Emirates', 'rank-math-pro' ),
'GBR' => __( 'United Kingdom', 'rank-math-pro' ),
'USA' => __( 'United States', 'rank-math-pro' ),
'URY' => __( 'Uruguay', 'rank-math-pro' ),
'UZB' => __( 'Uzbekistan', 'rank-math-pro' ),
'VUT' => __( 'Vanuatu', 'rank-math-pro' ),
'VEN' => __( 'Venezuela', 'rank-math-pro' ),
'VNM' => __( 'Vietnam', 'rank-math-pro' ),
'WLF' => __( 'Wallis & Futuna', 'rank-math-pro' ),
'ESH' => __( 'Western Sahara', 'rank-math-pro' ),
'YEM' => __( 'Yemen', 'rank-math-pro' ),
'ZMB' => __( 'Zambia', 'rank-math-pro' ),
'ZWE' => __( 'Zimbabwe', 'rank-math-pro' ),
'ZZZ' => __( 'Unknown Region', 'rank-math-pro' ),
];
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* The admin-specific functionality of the plugin.
*
* @since 1.0.0
* @package RankMath
* @subpackage RankMathPro\Admin
* @author MyThemeShop <admin@mythemeshop.com>
*/
namespace RankMathPro\Admin;
use RankMathPro\Updates;
use RankMathPro\Status\System_Status;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Param;
use RankMathPro\Google\Adsense;
defined( 'ABSPATH' ) || exit;
/**
* Admin class.
*
* @codeCoverageIgnore
*/
class Admin {
use Hooker;
/**
* Stores object instances.
*
* @var array
*/
public $components = [];
/**
* Register hooks.
*/
public function __construct() {
$this->action( 'init', 'init_components' );
add_filter( 'rank_math/analytics/classic/pro_notice', '__return_empty_string' );
$this->filter( 'rank_math/settings/sitemap', 'special_seprator' );
$this->action( 'admin_enqueue_scripts', 'enqueue' );
$this->filter( 'wp_helpers_notifications_render', 'prevent_pro_notice', 10, 3 );
new Updates();
new System_Status();
}
/**
* Initialize the required components.
*/
public function init_components() {
$components = [
'bulk_actions' => 'RankMathPro\\Admin\\Bulk_Actions',
'post_filters' => 'RankMathPro\\Admin\\Post_Filters',
'media_filters' => 'RankMathPro\\Admin\\Media_Filters',
'quick_edit' => 'RankMathPro\\Admin\\Quick_Edit',
'trends_tool' => 'RankMathPro\\Admin\\Trends_Tool',
'setup_wizard' => 'RankMathPro\\Admin\\Setup_Wizard',
'links' => 'RankMathPro\\Admin\\Links',
'misc' => 'RankMathPro\\Admin\\Misc',
'csv_import' => 'RankMathPro\\Admin\\CSV_Import_Export\\CSV_Import_Export',
];
if ( Helper::is_amp_active() ) {
$components['amp'] = 'RankMathPro\\Admin\\Amp';
}
$components = apply_filters( 'rank_math/admin/pro_components', $components );
foreach ( $components as $name => $component ) {
$this->components[ $name ] = new $component();
}
}
/**
* Add Special seprator into sitemap option panel
*
* @param array $tabs Hold tabs for optional panel.
*
* @return array
*/
public function special_seprator( $tabs ) {
if ( Helper::is_module_active( 'news-sitemap' ) || Helper::is_module_active( 'video-sitemap' ) || Helper::is_module_active( 'local-seo' ) ) {
$tabs['special'] = [
'title' => esc_html__( 'Special Sitemaps:', 'rank-math-pro' ),
'type' => 'seprator',
];
}
return $tabs;
}
/**
* Load setup wizard.
*/
private function load_setup_wizard() {
if ( Helper::is_wizard() ) {
new Setup_Wizard();
}
}
/**
* Enqueue assets.
*
* @return void
*/
public function enqueue() {
if ( Param::get( 'page' ) !== 'rank-math-options-general' ) {
return;
}
wp_enqueue_style(
'rank-math-pro-general-options',
RANK_MATH_PRO_URL . 'assets/admin/css/general-options.css',
null,
rank_math_pro()->version
);
wp_enqueue_script( 'rank-math-pro-general-options', RANK_MATH_PRO_URL . 'assets/admin/js/general-options.js', [ 'wp-hooks' ], rank_math_pro()->version );
Helper::add_json( 'isAdsenseConnected', Adsense::is_adsense_connected() );
}
/**
* Make sure that our "Upgrade to Pro" admin notice is not showing when the
* Pro version is active.
*
* @param string $output Notice HTML output.
* @param string $message Notice message text.
* @param array $options Notice options.
*
* @return string
*/
public function prevent_pro_notice( $output, $message, $options ) {
if ( 'rank_math_pro_notice' !== $options['id'] ) {
return $output;
}
return '';
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* Pro AMP support.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Redirections\DB;
use RankMath\Redirections\Redirection;
defined( 'ABSPATH' ) || exit;
/**
* Amp tool class.
*
* @codeCoverageIgnore
*/
class Amp {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
if ( Helper::get_settings( 'general.redirections_post_redirect' ) ) {
$this->action( 'rank_math/redirection/post_updated', 'add_amp_redirect', 20 );
$this->action( 'rank_math/redirection/term_updated', 'add_amp_redirect', 20 );
}
}
/**
* Add /amp redirection based on the original redirection.
*
* @param int $redirection_id Redirection ID.
* @return void
*/
public function add_amp_redirect( $redirection_id ) {
$db_redirection = DB::get_redirection_by_id( $redirection_id );
$url_to = trailingslashit( $db_redirection['url_to'] ) . 'amp/';
$redirection = Redirection::from(
[
'url_to' => $url_to,
'header_code' => $db_redirection['header_code'],
]
);
$redirection->set_nocache( true );
$redirection->add_source( trailingslashit( $db_redirection['sources'][0]['pattern'] ) . 'amp/', 'exact' );
$redirection->add_destination( $url_to );
$redirection->save();
}
}

View File

@@ -0,0 +1,350 @@
<?php
/**
* The RankMath API.
*
* @since 1.5.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Admin\Admin_Helper;
defined( 'ABSPATH' ) || exit;
/**
* Api class.
*/
class Api {
/**
* Rank Math SEO Checkup API.
*
* @var string
*/
protected $api_url = 'https://rankmath.com/wp-json/rankmath/v1/';
/**
* Was the last request successful.
*
* @var bool
*/
protected $is_success = false;
/**
* Last error.
*
* @var string
*/
protected $last_error = '';
/**
* Last response.
*
* @var array
*/
protected $last_response = [];
/**
* Last response header code.
*
* @var int
*/
protected $last_code = 0;
/**
* User agent.
*
* @var string
*/
protected $user_agent = '';
/**
* Is request blocking (do we wait for response)?
*
* @var bool
*/
protected $is_blocking = true;
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Api
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Api ) ) {
$instance = new Api();
$instance->is_blocking = true;
$instance->user_agent = 'RankMath/' . md5( esc_url( home_url( '/' ) ) );
}
return $instance;
}
/**
* Was the last request successful?
*
* @return bool True for success, false for failure
*/
public function is_success() {
return $this->is_success;
}
/**
* Get the last error returned by either the network transport, or by the API.
* If something didn't work, this should contain the string describing the problem.
*
* @return array|false describing the error
*/
public function get_error() {
return $this->last_error ? $this->last_error : false;
}
/**
* Get an array containing the HTTP headers and the body of the API response.
*
* @return array Assoc array with keys 'headers' and 'body'
*/
public function get_response() {
return $this->last_response;
}
/**
* Make an HTTP GET request - for retrieving data.
*
* @param string $url URL to do request.
* @param array $args Assoc array of arguments (usually your data).
* @param int $timeout Timeout limit for request in seconds.
*
* @return array|false Assoc array of API response, decoded from JSON.
*/
public function http_get( $url, $args = [], $timeout = 10 ) {
return $this->make_request( 'GET', $url, $args, $timeout );
}
/**
* Make an HTTP POST request - for creating and updating items.
*
* @param string $url URL to do request.
* @param array $args Assoc array of arguments (usually your data).
* @param int $timeout Timeout limit for request in seconds.
*
* @return array|false Assoc array of API response, decoded from JSON.
*/
public function http_post( $url, $args = [], $timeout = 10 ) {
return $this->make_request( 'POST', $url, $args, $timeout );
}
/**
* Make an HTTP PUT request - for creating new items.
*
* @param string $url URL to do request.
* @param array $args Assoc array of arguments (usually your data).
* @param int $timeout Timeout limit for request in seconds.
*
* @return array|false Assoc array of API response, decoded from JSON.
*/
public function http_put( $url, $args = [], $timeout = 10 ) {
return $this->make_request( 'PUT', $url, $args, $timeout );
}
/**
* Make an HTTP DELETE request - for deleting data.
*
* @param string $url URL to do request.
* @param array $args Assoc array of arguments (usually your data).
* @param int $timeout Timeout limit for request in seconds.
*
* @return array|false Assoc array of API response, decoded from JSON.
*/
public function http_delete( $url, $args = [], $timeout = 10 ) {
return $this->make_request( 'DELETE', $url, $args, $timeout );
}
/**
* Performs the underlying HTTP request. Not very exciting.
*
* @param string $http_verb The HTTP verb to use: get, post, put, patch, delete.
* @param string $url URL to do request.
* @param array $args Assoc array of parameters to be passed.
* @param int $timeout Timeout limit for request in seconds.
*
* @return array|false Assoc array of decoded result.
*/
protected function make_request( $http_verb, $url, $args = [], $timeout = 10 ) {
$params = [
'timeout' => $timeout,
'method' => $http_verb,
'user-agent' => $this->user_agent,
'blocking' => $this->is_blocking,
];
if ( ! empty( $args ) && is_array( $args ) ) {
$params['body'] = $args;
}
$this->reset();
$response = wp_remote_request( $this->api_url . $url, $params );
$this->determine_success( $response );
return $this->format_response( $response );
}
/**
* Decode the response and format any error messages for debugging
*
* @param array $response The response from the curl request.
*
* @return array|false The JSON decoded into an array
*/
protected function format_response( $response ) {
$this->last_response = $response;
if ( is_wp_error( $response ) ) {
return false;
}
if ( ! empty( $response['body'] ) ) {
return json_decode( $response['body'], true );
}
return false;
}
/**
* Check if the response was successful or a failure. If it failed, store the error.
*
* @param array $response The response from the curl request.
*/
protected function determine_success( $response ) {
if ( is_wp_error( $response ) ) {
$this->last_error = 'WP_Error: ' . $response->get_error_message();
return;
}
$this->last_code = wp_remote_retrieve_response_code( $response );
if ( in_array( $this->last_code, [ 200, 204 ], true ) ) {
$this->is_success = true;
return;
}
$this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
}
/**
* Reset request.
*/
protected function reset() {
$this->last_code = 0;
$this->last_error = '';
$this->is_success = false;
$this->is_blocking = true;
$this->last_response = [
'body' => null,
'headers' => null,
];
}
public function get_settings() {
$registered = Admin_Helper::get_registration_data();
if ( ! $registered || empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
return false;
}
$response = $this->http_get(
'siteSettings',
[
'username' => $registered['username'],
'api_key' => $registered['api_key'],
'site_url' => esc_url( home_url() ),
]
);
if ( ! $this->is_success() ) {
return false;
}
$registered['plan'] = $response['plan'];
Admin_Helper::get_registration_data( $registered );
update_option( 'rank_math_keyword_quota', $response['keywords'] );
cmb2_update_option( 'rank-math-options-general', 'sync_global_setting', $response['settings']['analytics'] );
}
public function sync_setting( $analytics ) {
$registered = Admin_Helper::get_registration_data();
if ( ! $registered || empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
return false;
}
$this->is_blocking = false;
$response = $this->http_post(
'siteSettings',
[
'username' => $registered['username'],
'api_key' => $registered['api_key'],
'site_url' => esc_url( home_url() ),
'analytics' => $analytics,
]
);
}
/**
* Remove registration data and disconnect from RankMath.com.
*
* @param string $username Username.
* @param string $api_key Api key.
*/
public function deactivate_site( $username, $api_key ) {
$this->is_blocking = false;
$this->http_post(
'deactivateSite',
[
'username' => $username,
'api_key' => $api_key,
'site_url' => esc_url( home_url() ),
]
);
}
/**
* Send analytics summary to RankMath.com.
*/
public function send_summary( $summary ) {
$this->is_blocking = false;
$this->http_post( 'siteStats', $summary );
}
/**
* Send keywords count data to RankMath.com.
*
* @param string $username Username.
* @param string $api_key Api key.
* @param int $count Total keywords count.
*
* @return array|false The respnose of API.
*/
public function keywords_info( $username, $api_key, $count ) {
$response = $this->http_post(
'keywordsInfo',
[
'username' => $username,
'apiKey' => $api_key,
'siteUrl' => esc_url( home_url() ),
'count' => $count,
]
);
if ( ! $this->is_success() ) {
return false;
}
return $response;
}
}

View File

@@ -0,0 +1,414 @@
<?php
/**
* Bulk actions for the manage posts screen.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Param;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
defined( 'ABSPATH' ) || exit;
/**
* Bulk actions class.
*
* @codeCoverageIgnore
*/
class Bulk_Actions {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$post_types = Helper::get_accessible_post_types();
foreach ( $post_types as $post_type ) {
$this->filter( "bulk_actions-edit-{$post_type}", 'post_bulk_actions' );
$this->filter( "handle_bulk_actions-edit-{$post_type}", 'handle_post_bulk_actions', 10, 3 );
}
$taxonomies = Helper::get_accessible_taxonomies();
unset( $taxonomies['post_format'] );
$taxonomies = wp_list_pluck( $taxonomies, 'label', 'name' );
foreach ( $taxonomies as $taxonomy => $label ) {
$this->filter( "bulk_actions-edit-{$taxonomy}", 'tax_bulk_actions' );
$this->filter( "handle_bulk_actions-edit-{$taxonomy}", 'handle_tax_bulk_actions', 10, 3 );
}
$this->action( 'save_post', 'save_post_primary_term' );
$this->action( 'admin_enqueue_scripts', 'enqueue' );
}
/**
* Add bulk actions for applicable posts, pages, CPTs.
*
* @param array $actions Actions.
* @return array New actions.
*/
public function post_bulk_actions( $actions ) {
$new_actions = [ 'rank_math_options' => __( '&#8595; Rank Math', 'rank-math-pro' ) ];
if ( Helper::has_cap( 'onpage_advanced' ) ) {
$new_actions['rank_math_bulk_robots_noindex'] = __( 'Set to noindex', 'rank-math-pro' );
$new_actions['rank_math_bulk_robots_index'] = __( 'Set to index', 'rank-math-pro' );
$new_actions['rank_math_bulk_robots_nofollow'] = __( 'Set to nofollow', 'rank-math-pro' );
$new_actions['rank_math_bulk_robots_follow'] = __( 'Set to follow', 'rank-math-pro' );
$new_actions['rank_math_bulk_remove_canonical'] = __( 'Remove custom canonical URL', 'rank-math-pro' );
if ( Helper::is_module_active( 'redirections' ) && Helper::has_cap( 'redirections' ) ) {
$new_actions['rank_math_bulk_redirect'] = __( 'Redirect', 'rank-math-pro' );
$new_actions['rank_math_bulk_stop_redirect'] = __( 'Remove redirection', 'rank-math-pro' );
}
}
if ( Helper::is_module_active( 'rich-snippet' ) && Helper::has_cap( 'onpage_snippet' ) ) {
$new_actions['rank_math_bulk_schema_none'] = __( 'Set Schema: None', 'rank-math-pro' );
$post_type = Param::get( 'post_type', get_post_type() );
$post_type_default = Helper::get_settings( 'titles.pt_' . $post_type . '_default_rich_snippet' );
if ( $post_type_default ) {
// Translators: placeholder is the default Schema type setting.
$new_actions['rank_math_bulk_schema_default'] = sprintf( __( 'Set Schema: Default (%s)', 'rank-math-pro' ), $post_type_default );
}
}
if ( is_array( $actions ) && count( $new_actions ) > 1 ) {
return array_merge( $actions, $new_actions );
}
return $actions;
}
/**
* Add bulk actions for applicable taxonomies.
*
* @param array $actions Actions.
* @return array New actions.
*/
public function tax_bulk_actions( $actions ) {
if ( ! Helper::has_cap( 'onpage_advanced' ) ) {
return $actions;
}
$actions['rank_math_options'] = __( '&#8595; Rank Math', 'rank-math-pro' );
$actions['rank_math_bulk_robots_noindex'] = __( 'Set to noindex', 'rank-math-pro' );
$actions['rank_math_bulk_robots_index'] = __( 'Set to index', 'rank-math-pro' );
$actions['rank_math_bulk_robots_nofollow'] = __( 'Set to nofollow', 'rank-math-pro' );
$actions['rank_math_bulk_robots_follow'] = __( 'Set to follow', 'rank-math-pro' );
if ( Helper::is_module_active( 'redirections' ) && Helper::has_cap( 'redirections' ) ) {
$actions['rank_math_bulk_redirect'] = __( 'Redirect', 'rank-math-pro' );
$actions['rank_math_bulk_stop_redirect'] = __( 'Remove redirection', 'rank-math-pro' );
}
return $actions;
}
/**
* Handle bulk actions for applicable posts, pages, CPTs.
*
* @param string $redirect Redirect URL.
* @param string $doaction Performed action.
* @param array $object_ids Post IDs.
*
* @return string New redirect URL.
*/
public function handle_post_bulk_actions( $redirect, $doaction, $object_ids ) {
$redirect = remove_query_arg(
[
'rank_math_bulk_robots_noindex',
'rank_math_bulk_robots_index',
'rank_math_bulk_robots_nofollow',
'rank_math_bulk_robots_follow',
'rank_math_bulk_stop_redirect',
'rank_math_bulk_schema_none',
'rank_math_bulk_schema_default',
'rank_math_bulk_remove_canonical',
],
$redirect
);
$edited = 0;
$message = '';
$post_type_object = false;
switch ( $doaction ) {
case 'rank_math_bulk_robots_noindex':
case 'rank_math_bulk_robots_index':
case 'rank_math_bulk_robots_nofollow':
case 'rank_math_bulk_robots_follow':
foreach ( $object_ids as $post_id ) {
if ( ! $post_type_object ) {
$post_type_object = get_post_type_object( get_post_type( $post_id ) );
}
$action = str_replace( 'rank_math_bulk_robots_', '', $doaction );
$robots = (array) get_post_meta( $post_id, 'rank_math_robots', true );
$robots = array_filter( $robots );
// Remove "opposite" robots meta.
$opposite = 'no' . $action;
if ( substr( $action, 0, 2 ) === 'no' ) {
$opposite = substr( $action, 2 );
}
if ( ( $key = array_search( $opposite, $robots ) ) !== false ) { // @codingStandardsIgnoreLine
unset( $robots[ $key ] );
}
// Add new robots meta.
if ( ! in_array( $action, $robots, true ) ) {
$robots[] = $action;
}
$robots = array_unique( $robots );
update_post_meta( $post_id, 'rank_math_robots', $robots );
$edited++;
if ( 'index' === $action || 'noindex' === $action ) {
$this->do_action( 'sitemap/invalidate_object_type', 'post', $post_id );
}
}
// Translators: 1 The number of posts edited. 2 The post type name.
$message = sprintf( __( 'Robots meta edited for %1$d %2$s.', 'rank-math-pro' ), $edited, ( $edited > 1 ? $post_type_object->labels->name : $post_type_object->labels->singular_name ) );
break;
case 'rank_math_bulk_redirect':
$redirect = Helper::get_admin_url( 'redirections' );
$i = 0;
foreach ( $object_ids as $post_id ) {
$post_url = get_permalink( $post_id );
$redirect = add_query_arg( "urls[{$i}]", $post_url, $redirect );
$i++;
}
break;
case 'rank_math_bulk_stop_redirect':
foreach ( $object_ids as $post_id ) {
$redirection = \RankMath\Redirections\Cache::get_by_object_id( $post_id, 'post' );
if ( $redirection ) {
\RankMath\Redirections\DB::change_status( $redirection->redirection_id, 'trashed' );
$edited++;
}
}
// Translators: placeholder is the number of redirections deleted.
$message = sprintf( _n( '%d redirection moved to Trash.', '%d redirections moved to Trash.', $edited, 'rank-math-pro' ), $edited );
break;
case 'rank_math_bulk_schema_none':
foreach ( $object_ids as $post_id ) {
if ( ! $post_type_object ) {
$post_type_object = get_post_type_object( get_post_type( $post_id ) );
}
update_post_meta( $post_id, 'rank_math_rich_snippet', 'off' );
$this->delete_schema( $post_id );
$edited++;
}
// Translators: 1 The number of posts edited. 2 The post type name.
$message = sprintf( __( 'Schema edited for %1$d %2$s.', 'rank-math-pro' ), $edited, ( $edited > 1 ? $post_type_object->labels->name : $post_type_object->labels->singular_name ) );
break;
case 'rank_math_bulk_schema_default':
foreach ( $object_ids as $post_id ) {
if ( ! $post_type_object ) {
$post_type_object = get_post_type_object( get_post_type( $post_id ) );
}
delete_post_meta( $post_id, 'rank_math_rich_snippet' );
$this->delete_schema( $post_id );
$edited++;
}
// Translators: 1 The number of posts edited. 2 The post type name.
$message = sprintf( __( 'Schema edited for %1$d %2$s.', 'rank-math-pro' ), $edited, ( $edited > 1 ? $post_type_object->labels->name : $post_type_object->labels->singular_name ) );
break;
case 'rank_math_bulk_remove_canonical':
foreach ( $object_ids as $post_id ) {
if ( ! $post_type_object ) {
$post_type_object = get_post_type_object( get_post_type( $post_id ) );
}
if ( get_post_meta( $post_id, 'rank_math_canonical_url', true ) ) {
delete_post_meta( $post_id, 'rank_math_canonical_url' );
$edited++;
}
}
// Translators: 1 The number of posts edited. 2 The post type name.
$message = sprintf( __( 'Custom Canonical URL removed from %1$d %2$s.', 'rank-math-pro' ), $edited, ( $edited > 1 ? $post_type_object->labels->name : $post_type_object->labels->singular_name ) );
break;
}
if ( $message ) {
Helper::add_notification( $message );
}
return $redirect;
}
/**
* Delete ALL existing Schema data for a post.
*
* @param int $post_id Post id.
*/
public function delete_schema( $post_id ) {
global $wpdb;
$where = $wpdb->prepare( 'WHERE post_id = %d AND meta_key LIKE %s', $post_id, $wpdb->esc_like( 'rank_math_schema_' ) . '%' );
$wpdb->query( "DELETE FROM {$wpdb->postmeta} {$where}" ); // phpcs:ignore
}
/**
* Handle bulk actions for applicable posts, pages, CPTs.
*
* @param string $redirect Redirect URL.
* @param string $doaction Performed action.
* @param array $object_ids Post IDs.
*
* @return string New redirect URL.
*/
public function handle_tax_bulk_actions( $redirect, $doaction, $object_ids ) {
$redirect = remove_query_arg(
[
'rank_math_bulk_robots_noindex',
'rank_math_bulk_robots_index',
'rank_math_bulk_robots_nofollow',
'rank_math_bulk_robots_follow',
'rank_math_bulk_stop_redirect',
'rank_math_bulk_schema_none',
'rank_math_bulk_schema_default',
'rank_math_bulk_remove_canonical',
],
$redirect
);
$edited = 0;
$message = '';
$tax_object = false;
switch ( $doaction ) {
case 'rank_math_bulk_robots_noindex':
case 'rank_math_bulk_robots_index':
case 'rank_math_bulk_robots_nofollow':
case 'rank_math_bulk_robots_follow':
foreach ( $object_ids as $term_id ) {
if ( ! $tax_object ) {
$tax_object = get_taxonomy( get_term( $term_id )->taxonomy );
}
$action = str_replace( 'rank_math_bulk_robots_', '', $doaction );
$robots = (array) get_term_meta( $term_id, 'rank_math_robots', true );
$robots = array_filter( $robots );
// Remove "opposite" robots meta.
$opposite = 'no' . $action;
if ( substr( $action, 0, 2 ) === 'no' ) {
$opposite = substr( $action, 2 );
}
if ( ( $key = array_search( $opposite, $robots ) ) !== false ) { // @codingStandardsIgnoreLine
unset( $robots[ $key ] );
}
// Add new robots meta.
if ( ! in_array( $action, $robots, true ) ) {
$robots[] = $action;
}
$robots = array_unique( $robots );
update_term_meta( $term_id, 'rank_math_robots', $robots );
$edited++;
if ( 'index' === $action || 'noindex' === $action ) {
$this->do_action( 'sitemap/invalidate_object_type', 'term', $term_id );
}
}
// Translators: 1 The number of terms edited. 2 The term taxonomy name.
$message = sprintf( __( 'Robots meta edited for %1$d %2$s.', 'rank-math-pro' ), $edited, ( $edited > 1 ? $tax_object->labels->name : $tax_object->labels->singular_name ) );
break;
case 'rank_math_bulk_redirect':
$redirect = Helper::get_admin_url( 'redirections' );
$i = 0;
foreach ( $object_ids as $term_id ) {
$term_url = get_term_link( (int) $term_id );
if ( is_wp_error( $term_url ) ) {
continue;
}
$redirect = add_query_arg( "urls[{$i}]", $term_url, $redirect );
$i++;
}
break;
case 'rank_math_bulk_stop_redirect':
foreach ( $object_ids as $term_id ) {
$redirection = \RankMath\Redirections\Cache::get_by_object_id( $term_id, 'term' );
if ( $redirection ) {
\RankMath\Redirections\DB::change_status( $redirection->redirection_id, 'trashed' );
$edited++;
}
}
// Translators: placeholder is the number of redirections deleted.
$message = sprintf( _n( '%d redirection moved to Trash.', '%d redirections moved to Trash.', $edited, 'rank-math-pro' ), $edited );
break;
}
if ( $message ) {
Helper::add_notification( $message );
}
return $redirect;
}
/**
* Save primary term bulk edit. This handles the action performed when the
* user selects one or more posts with the checkbox and then selects "Edit"
* in the Bulk Edit dropdown.
*
* @param int $post_id Post ID.
*
* @return void
*/
public function save_post_primary_term( $post_id ) {
if ( Param::get( 'rank_math_quick_edit_nonce' ) ) {
return;
}
if ( ! wp_verify_nonce( Param::get( 'rank_math_bulk_edit_primary_term' ), 'rank-math-edit-primary-term' ) ) {
return;
}
$taxonomy = ProAdminHelper::get_primary_taxonomy( $post_id );
$input = absint( Param::get( 'rank_math_primary_' . $taxonomy['name'] ) );
if ( ! $input ) {
return;
}
if ( ! has_term( $input, $taxonomy['name'], $post_id ) ) {
return;
}
update_post_meta( $post_id, 'rank_math_primary_' . $taxonomy['name'], absint( $input ) );
}
/**
* Enqueue scripts and add JSON.
*
* @return void
*/
public function enqueue() {
if ( ! Admin_Helper::is_post_list() ) {
return;
}
Helper::add_json( 'confirmSchemaDelete', __( 'Are you sure you want to change the Schema type for the selected posts? Doing so may irreversibly delete the existing Schema data.', 'rank-math-pro' ) );
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* CSV
*
* This class defines all code necessary to run during the CSV export.
*
* @since x.x.x
* @package RankMathPro
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
defined( 'ABSPATH' ) || exit;
/**
* Installer class.
*/
class CSV {
/**
* Do export.
*
* @param array $data Data to export.
* @return void
*/
public function export( $data = [] ) {
if ( empty( $data['items'] ) ) {
return;
}
$this->increase_limits();
$this->headers( $data['filename'] );
$this->output( $data );
exit;
}
/**
* Try to increase time limit on server.
*
* @return void
*/
public function increase_limits() {
set_time_limit( 300 );
}
/**
* Send headers.
*
* @param string $name File name.
* @return void
*/
public function headers( $name = '' ) {
$sitename = sanitize_key( get_bloginfo( 'name' ) );
$filename = $sitename . '_' .$name. '-' . date( 'Y-m-d_H-i-s' ) . '.csv'; // phpcs:ignore
header( 'Content-Type: application/csv' );
header( 'Content-Description: File Transfer' );
header( "Content-Disposition: attachment; filename={$filename}" );
header( 'Pragma: no-cache' );
}
/**
* Output
*
* @param array $data Data to export.
* @return void
*/
public function output( $data = [] ) {
$this->output_csv( $data['columns'] );
foreach ( $data['items'] as $line ) {
$this->output_csv( array_values( $line ) );
}
}
/**
* Output fputcsv instead of saving to a file.
*
* @param array $data Data array.
* @return void
*/
public function output_csv( $data ) {
echo implode( ',', $data ) . "\n"; // phpcs:ignore
}
/**
* Escape CSV: quotes and slashes
*
* @param string $string String to escape.
* @return string
*/
public function escape_csv( $string ) {
return '"' . str_replace( [ "'", '"', '\\' ], [ "''", '""', '\\\\' ], $string ) . '"';
}
}

View File

@@ -0,0 +1,141 @@
<?php
/**
* Links related functionality.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Helper;
use RankMath\Admin\Admin_Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Str;
use MyThemeShop\Helpers\Arr;
defined( 'ABSPATH' ) || exit;
/**
* Links class.
*
* @codeCoverageIgnore
*/
class Links {
use Hooker;
/**
* Base host.
*
* @var string
*/
protected $base_host = '';
/**
* Base path.
*
* @var string
*/
protected $base_path = '';
/**
* Register hooks.
*/
public function __construct() {
$this->base_host = Helper::get_url_part( home_url(), 'host' );
$base_path = Helper::get_url_part( home_url(), 'path' );
if ( $base_path ) {
$this->base_path = trailingslashit( $base_path );
}
$this->action( 'cmb2_admin_init', 'cmb_init', 99 );
$this->action( 'rank_math/links/is_external', 'link_is_external', 20, 2 );
$this->action( 'rank_math/admin/enqueue_scripts', 'add_json', 20 );
}
/**
* Hook CMB2 init process.
*/
public function cmb_init() {
$this->action( 'cmb2_init_hookup_rank-math-options-general_options', 'add_options', 110 );
}
/**
* Add options to Image SEO module.
*
* @param object $cmb CMB object.
*/
public function add_options( $cmb ) {
$field_ids = wp_list_pluck( $cmb->prop( 'fields' ), 'id' );
$fields_position = array_search( 'new_window_external_links', array_keys( $field_ids ), true ) + 1;
$cmb->add_field(
[
'id' => 'affiliate_link_prefixes',
'type' => 'textarea_small',
'name' => esc_html__( 'Affiliate Link Prefix', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Add the URI prefixes you use for affiliate (cloaked) links, which redirect to external sites. These will not count as internal links in the content analysis. Add one per line.', 'rank-math-pro' ) ),
'classes' => 'rank-math-advanced-option',
'dep' => [
[ 'nofollow_external_links', 'on' ],
[ 'nofollow_image_links', 'on' ],
],
'attributes' => [
'placeholder' => esc_html__( 'Example: /get/', 'rank-math-pro' ),
],
],
++$fields_position
);
}
/**
* Make cloaked affiliate links external.
*
* @param bool $is_external Is external link.
* @param array $url_parts URL parts from wp_parse_url().
* @return mixed
*/
public function link_is_external( $is_external, $url_parts ) {
if ( empty( $url_parts['path'] ) ) {
return $is_external;
}
// Check if internal link.
if (
! isset( $url_parts['host'] )
|| '' === $url_parts['host']
|| $url_parts['host'] === $this->base_host
) {
// Check if path starts with one of the affiliate_link_prefixes.
$prefixes = Arr::from_string( Helper::get_settings( 'general.affiliate_link_prefixes' ), "\n" );
foreach ( $prefixes as $prefix ) {
if ( Str::starts_with( $prefix, $url_parts['path'] ) ) {
return true;
}
}
}
return $is_external;
}
/**
* Add JSON.
*
* @return void
*/
public function add_json() {
$prefixes = Arr::from_string( Helper::get_settings( 'general.affiliate_link_prefixes' ), "\n" );
wp_enqueue_script(
'rank-math-pro-links',
RANK_MATH_PRO_URL . 'assets/admin/js/links.js',
[ 'wp-hooks' ],
rank_math_pro()->version
);
Helper::add_json( 'affiliate_link_prefixes', $prefixes );
}
}

View File

@@ -0,0 +1,186 @@
<?php
/**
* Post filters for the manage posts screen.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* Post filters class.
*
* @codeCoverageIgnore
*/
class Media_Filters {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$this->filter( 'ajax_query_attachments_args', 'attachments_query_filter' );
global $pagenow;
if ( 'upload.php' !== $pagenow ) {
return;
}
$this->action( 'wp_enqueue_media', 'enqueue_media', 20 );
$this->action( 'restrict_manage_posts', 'add_seo_filter' );
$this->action( 'pre_get_posts', 'posts_by_seo_filters' );
}
/**
* Enqueue assets for Media Library.
*
* @return void
*/
public function enqueue_media() {
wp_enqueue_script( 'media-library-seo-filter', RANK_MATH_PRO_URL . 'assets/admin/js/media.js', [ 'media-editor', 'media-views', 'media-models' ], rank_math_pro()->version ); // phpcs:ignore
wp_localize_script(
'media-library-seo-filter',
'RankMathProMedia',
[
'filters' => $this->get_filters(),
'filter_all' => __( 'Rank Math SEO Filters', 'rank-math-pro' ),
]
);
}
/**
* Hook to add SEO Filters in List View on attachment page..
*
* @return void
*/
public function add_seo_filter() {
$filter = sanitize_title( wp_unslash( isset( $_GET['seo-filter'] ) ? $_GET['seo-filter'] : '' ) ); // phpcs:ignore
?>
<select id="media-attachment-seo-filter" name="seo-filter" class="attachment-filters">
<option value="all"><?php echo esc_html__( 'Rank Math SEO Filters', 'rank-math-pro' ); ?></option>
<?php foreach ( $this->get_filters() as $key => $value ) { ?>
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $filter, $key ); ?>>
<?php echo esc_html( $value ); ?>
</option>
<?php } ?>
</select>
<?php
}
/**
* Filter attachments in admin by Rank Math's Filter value.
*
* @param \WP_Query $query The wp_query instance.
*/
public function posts_by_seo_filters( $query ) {
$filter = Param::get( 'seo-filter' );
if ( ! $filter ) {
return $query;
}
switch ( $filter ) {
case 'missing_alt':
$query->set( 'meta_key', '_wp_attachment_image_alt' );
$query->set( 'meta_compare', 'NOT EXISTS' );
break;
case 'missing_title':
$this->filter( 'posts_clauses', 'filter_query_attachment_titles' );
break;
case 'missing_caption':
$this->filter( 'posts_clauses', 'filter_query_attachment_captions' );
break;
}
return $query;
}
/**
* Modify media ajax query according to the selected SEO filter.
*
* @param array $query Query parameters.
* @return array New query parameters.
*/
public function attachments_query_filter( $query ) {
if ( empty( $_POST['query']['attachment_seo_filter'] ) ) { // phpcs:ignore
return $query;
}
$filter = sanitize_title( wp_unslash( $_POST['query']['attachment_seo_filter'] ) ); // phpcs:ignore
switch ( $filter ) {
case 'missing_alt':
if ( ! isset( $query['meta_query'] ) ) {
$query['meta_query'] = [];
}
$query['meta_query'][] = [
'key' => '_wp_attachment_image_alt',
'compare' => 'NOT EXISTS',
];
break;
case 'missing_title':
$this->filter( 'posts_clauses', 'filter_query_attachment_titles' );
break;
case 'missing_caption':
$this->filter( 'posts_clauses', 'filter_query_attachment_captions' );
break;
}
return $query;
}
/**
* Filter the SQL clauses of an attachment query to match attachments where
* the title equals the filename.
*
* @param string[] $clauses An array including WHERE, GROUP BY, JOIN, ORDER BY,
* DISTINCT, fields (SELECT), and LIMITS clauses.
* @return string[] The modified array of clauses.
*/
public function filter_query_attachment_titles( $clauses ) {
remove_filter( 'posts_clauses', __FUNCTION__ );
$clauses['where'] .= " AND ( post_title = '' OR ( ( post_title LIKE '%.png' OR post_title LIKE '%.jpg' OR post_title LIKE '%.gif' OR post_title LIKE '%.jpeg' ) AND INSTR( guid, post_title ) != 0 ) )";
return $clauses;
}
/**
* Filter the SQL clauses of an attachment query to match attachments where
* caption is empty.
*
* @param string[] $clauses An array including WHERE, GROUP BY, JOIN, ORDER BY,
* DISTINCT, fields (SELECT), and LIMITS clauses.
* @return string[] The modified array of clauses.
*/
public function filter_query_attachment_captions( $clauses ) {
remove_filter( 'posts_clauses', __FUNCTION__ );
$clauses['where'] .= " AND post_excerpt = ''";
return $clauses;
}
/**
* Get attachment filters option.
*
* @return array The filters array.
*/
private function get_filters() {
return [
'missing_alt' => __( 'Missing alt tag', 'rank-math-pro' ),
'missing_title' => __( 'Missing or default title tag', 'rank-math-pro' ),
'missing_caption' => __( 'Missing caption', 'rank-math-pro' ),
];
}
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* Miscellaneous admin related functionality.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
defined( 'ABSPATH' ) || exit;
/**
* Misc admin class.
*
* @codeCoverageIgnore
*/
class Misc {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$this->action( 'cmb2_default_filter', 'change_fk_default', 20, 2 );
$this->action( 'rank_math/pro_badge', 'header_pro_badge' );
}
/**
* Add options to Image SEO module.
*
* @param mixed $default Default value.
* @param object $field Field object.
*/
public function change_fk_default( $default, $field ) {
if ( 'rank_math_focus_keyword' !== $field->id() ) {
return $default;
}
if ( ! Admin_Helper::is_term_edit() ) {
return $default;
}
return $this->get_term();
}
/**
* Get term.
*
* @return string
*/
public function get_term() {
global $tag;
if ( isset( $tag->name ) ) {
return $tag->name;
}
return '';
}
/**
* Check and print the license type as a badge in the header of Rank Math's setting pages.
*/
public static function header_pro_badge() {
$plan = ProAdminHelper::get_plan();
echo '<span class="rank-math-pro-badge ' . esc_attr( $plan ) . '">' . esc_html( $plan ) . '</span>';
}
}

View File

@@ -0,0 +1,355 @@
<?php
/**
* Post filters for the manage posts screen.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* Post filters class.
*
* @codeCoverageIgnore
*/
class Post_Filters {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$this->filter( 'rank_math/manage_posts/seo_filter_options', 'add_seo_filters', 10, 1 );
$this->filter( 'restrict_manage_posts', 'add_schema_filters', 20, 0 );
$this->filter( 'manage_posts_extra_tablenav', 'add_filter_clear_button', 20, 1 );
$this->filter( 'pre_get_posts', 'posts_by_seo_filters', 20 );
}
/**
* Add our custom options to the SEO filter dropdown.
*
* @param array $options Original options.
*
* @return array New options.
*/
public function add_seo_filters( $options ) {
$new_options = [
'custom_canonical' => __( 'Custom Canonical URL', 'rank-math-pro' ),
'custom_title' => __( 'Custom Meta Title', 'rank-math-pro' ),
'custom_description' => __( 'Custom Meta Description', 'rank-math-pro' ),
'redirected' => __( 'Redirected Posts', 'rank-math-pro' ),
'orphan' => __( 'Orphan Posts', 'rank-math-pro' ),
];
if ( Helper::is_module_active( 'rich-snippet' ) ) {
$new_options['schema_type'] = __( 'Filter by Schema Type', 'rank-math-pro' );
}
return $options + $new_options;
}
/**
* Output dropdown to filter by Schema type.
*/
public function add_schema_filters() {
global $post_type;
if ( 'attachment' === $post_type || ! in_array( $post_type, Helper::get_allowed_post_types(), true ) ) {
return;
}
$options = [
'none' => esc_html__( 'Turned Off', 'rank-math-pro' ),
'Article' => esc_html__( 'Article', 'rank-math-pro' ),
'BlogPosting' => esc_html__( 'Blog Post', 'rank-math-pro' ),
'NewsArticle' => esc_html__( 'News Article', 'rank-math-pro' ),
'Book' => esc_html__( 'Book', 'rank-math-pro' ),
'Course' => esc_html__( 'Course', 'rank-math-pro' ),
'Event' => esc_html__( 'Event', 'rank-math-pro' ),
'PodcastEpisode' => esc_html__( 'Podcast', 'rank-math-pro' ),
'JobPosting' => esc_html__( 'Job Posting', 'rank-math-pro' ),
'MusicGroup' => esc_html__( 'Music', 'rank-math-pro' ),
'Movie' => esc_html__( 'Movie', 'rank-math-pro' ),
'Person' => esc_html__( 'Person', 'rank-math-pro' ),
'Product' => esc_html__( 'Product', 'rank-math-pro' ),
'Recipe' => esc_html__( 'Recipe', 'rank-math-pro' ),
'Restaurant' => esc_html__( 'Restaurant', 'rank-math-pro' ),
'Service' => esc_html__( 'Service', 'rank-math-pro' ),
'SoftwareApplication' => esc_html__( 'Software', 'rank-math-pro' ),
'VideoObject' => esc_html__( 'Video', 'rank-math-pro' ),
'Dataset' => esc_html__( 'Dataset', 'rank-math-pro' ),
'FAQPage' => esc_html__( 'FAQ', 'rank-math-pro' ),
'ClaimReview' => esc_html__( 'FactCheck', 'rank-math-pro' ),
'HowTo' => esc_html__( 'How To', 'rank-math-pro' ),
];
$options = $this->do_filter( 'manage_posts/schema_filter_options', $options, $post_type );
$selected = Param::get( 'schema-filter' );
?>
<select name="schema-filter" id="rank-math-schema-filter" class="hidden">
<?php foreach ( $options as $val => $option ) : ?>
<option value="<?php echo esc_attr( $val ); ?>" <?php selected( $selected, $val, true ); ?>><?php echo esc_html( $option ); ?></option>
<?php endforeach; ?>
</select>
<?php
}
/**
* Filter for pre WP Query on the post list page.
*
* @param object $query Query object passed by reference.
*
* @return void
*/
public function posts_by_seo_filters( $query ) {
if ( ! $query->is_main_query() ) {
return;
}
if ( ! $this->can_seo_filters() ) {
return;
}
$meta_query = $query->get( 'meta_query' );
$filter = Param::get( 'seo-filter' );
$this->set_seo_filters( $meta_query, $filter );
if ( ! empty( $meta_query ) ) {
$query->set( 'meta_query', $meta_query );
}
if ( 'redirected' === $filter ) {
$query->set( 'is_redirected', 1 );
$this->filter( 'posts_where', 'posts_where_redirected', 10, 2 );
} elseif ( 'orphan' === $filter ) {
$query->set( 'is_orphan', 1 );
$this->filter( 'posts_where', 'posts_where_orphan', 10, 2 );
}
}
/**
* Add SEO filters for the meta_query.
*
* @param array $query The meta_query array passed by reference.
* @param string $filter Input filter.
*/
public function set_seo_filters( &$query, $filter ) {
if ( false === $filter ) {
return;
} elseif ( 'schema_type' === $filter ) {
$schema_filter = Param::get( 'schema-filter' );
$this->set_schema_filters( $query, $schema_filter );
return;
}
$hash = [
'custom_canonical' => [
[
'key' => 'rank_math_canonical_url',
'compare' => 'EXISTS',
],
[
'key' => 'rank_math_canonical_url',
'compare' => '!=',
'value' => '',
],
],
'custom_title' => [
[
'key' => 'rank_math_title',
'compare' => 'EXISTS',
],
[
'key' => 'rank_math_title',
'compare' => '!=',
'value' => '',
],
],
'custom_description' => [
[
'key' => 'rank_math_description',
'compare' => 'EXISTS',
],
[
'key' => 'rank_math_description',
'compare' => '!=',
'value' => '',
],
],
];
if ( isset( $hash[ $filter ] ) ) {
foreach ( $hash[ $filter ] as $query_parts ) {
$query[] = $query_parts;
}
$query[] = $hash[ $filter ];
}
}
/**
* Add Schema type filters for the meta_query.
*
* @param array $query The meta_query array passed by reference.
* @param string $filter Input filter.
*/
public function set_schema_filters( &$query, $filter ) {
$post_type = Param::get( 'post_type' );
$post_type_default = Helper::get_settings( 'titles.pt_' . $post_type . '_default_rich_snippet' );
if ( false === $filter ) {
return;
}
if ( 'none' === $filter ) {
$query[] = [
'key' => 'rank_math_rich_snippet',
'value' => 'off',
];
$this->filter( 'posts_where', 'posts_where_no_schema', 20, 2 );
return;
}
switch ( $filter ) {
case 'Event':
$query['relation'] = 'OR';
foreach ( [ 'Event', 'BusinessEvent', 'ChildrensEvent', 'ComedyEvent', 'DanceEvent', 'DeliveryEvent', 'EducationEvent', 'ExhibitionEvent', 'Festival', 'FoodEvent', 'LiteraryEvent', 'MusicEvent', 'PublicationEvent', 'SaleEvent', 'ScreeningEvent', 'SocialEvent', 'SportsEvent', 'TheaterEvent', 'VisualArtsEvent' ] as $type ) {
$query[] = [
'key' => 'rank_math_schema_' . $type,
'compare' => 'LIKE',
];
}
break;
case 'Music':
$query['relation'] = 'OR';
foreach ( [ 'MusicAlbum', 'MusicGroup' ] as $type ) {
$query[] = [
'key' => 'rank_math_schema_' . $type,
'compare' => 'LIKE',
];
}
break;
default:
$query[] = [
'key' => 'rank_math_schema_' . $filter,
'compare' => 'LIKE',
];
break;
}
if ( strtolower( $filter ) === $post_type_default ) {
// Also get not set because we filter for the default.
$query['relation'] = 'OR';
$query[] = [
'key' => 'rank_math_rich_snippet',
'compare' => 'NOT EXISTS',
];
$this->filter( 'posts_where', 'posts_where_no_schema', 20, 2 );
}
}
/**
* Add extra WHERE clause to find posts with no Schema.
*
* @param string $where Original WHERE clause string.
* @param \WP_Query $wp_query WP_Query object.
* @return string
*/
public function posts_where_no_schema( $where, \WP_Query $wp_query ) {
global $wpdb;
$where .= " AND NOT EXISTS ( SELECT meta_id FROM {$wpdb->postmeta} WHERE post_id = {$wpdb->posts}.ID AND meta_key LIKE 'rank_math_schema_%' )";
// Remove this filter for subsequent queries.
$this->remove_filter( 'posts_where', 'posts_where_no_schema', 20, 2 );
return $where;
}
/**
* Can apply SEO filters.
*
* @return bool
*/
private function can_seo_filters() {
global $pagenow;
if ( 'edit.php' !== $pagenow || ! in_array( Param::get( 'post_type' ), Helper::get_allowed_post_types(), true ) ) {
return false;
}
return true;
}
/**
* Add our where clause to the query.
*
* @param string $where Original where clause.
* @param object $query The query object.
*
* @return string New where clause.
*/
public function posts_where_redirected( $where, $query ) {
if ( ! $query->get( 'is_redirected' ) ) {
return $where;
}
global $wpdb;
$redirections_table = $wpdb->prefix . 'rank_math_redirections_cache';
$where .= " AND ID IN ( SELECT object_id FROM {$redirections_table} WHERE is_redirected = 1 AND object_type = 'post' )";
return $where;
}
/**
* Add our where clause to the query.
*
* @param string $where Original where clause.
* @param object $query The query object.
*
* @return string New where clause.
*/
public function posts_where_orphan( $where, $query ) {
if ( ! $query->get( 'is_orphan' ) ) {
return $where;
}
global $wpdb;
$linkmeta_table = $wpdb->prefix . 'rank_math_internal_meta';
$where .= " AND ID IN ( SELECT object_id FROM {$linkmeta_table} WHERE incoming_link_count = 0 )";
return $where;
}
/**
* Add filter reset button
*
* @param string $which Where to place.
*/
public function add_filter_clear_button( $which ) {
if ( 'top' !== $which ) {
return;
}
$post_type = get_post_type();
$clear_label = __( 'Clear Filter', 'rank-math-pro' );
$clear_url = add_query_arg( 'post_type', $post_type, admin_url( 'edit.php' ) );
$clear_classes = 'clear-tablenav-filter';
$filter_params = [ 'm', 'cat', 'seo-filter' ];
$filtered = false;
foreach ( $filter_params as $filter ) {
$val = Param::get( $filter );
if ( ! empty( $val ) ) {
$filtered = true;
break;
}
}
$clear_classes .= $filtered ? '' : ' hidden';
echo '<a href="' . esc_url( $clear_url ) . '" class="' . esc_attr( $clear_classes ) . '" title="' . esc_attr( $clear_label ) . '"><span class="dashicons dashicons-dismiss"></span> ' . esc_html( $clear_label ) . '</a>';
}
}

View File

@@ -0,0 +1,641 @@
<?php
/**
* Extend the quick edit functionality on the manage posts screen.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
use MyThemeShop\Helpers\Param;
use MyThemeShop\Helpers\Arr;
use MyThemeShop\Helpers\Conditional;
defined( 'ABSPATH' ) || exit;
/**
* Quick edit class.
*
* @codeCoverageIgnore
*/
class Quick_Edit {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$this->action( 'admin_enqueue_scripts', 'admin_scripts', 20 );
$this->action( 'rank_math/post/column/seo_details', 'quick_edit_hidden_fields' );
$this->action( 'quick_edit_custom_box', 'quick_edit' );
$this->action( 'bulk_edit_custom_box', 'bulk_edit' );
$this->action( 'save_post', 'save_post' );
$this->action( 'load-edit.php', 'maybe_save_bulk_edit', 20 );
$taxonomies = Helper::get_accessible_taxonomies();
unset( $taxonomies['post_format'] );
$taxonomies = wp_list_pluck( $taxonomies, 'label', 'name' );
foreach ( $taxonomies as $taxonomy => $label ) {
$this->filter( "manage_edit-{$taxonomy}_columns", 'add_tax_seo_column' );
$this->filter( "manage_{$taxonomy}_custom_column", 'tax_seo_column_content', 10, 3 );
$this->filter( "edited_{$taxonomy}", 'save_tax' );
}
}
/**
* Add hidden column for SEO details for the quick edit.
*
* @param string[] $columns Original columns array.
*
* @return string[] New columns array.
*/
public function add_tax_seo_column( $columns ) {
$columns['rank_math_tax_seo_details'] = __( 'SEO Details', 'rank-math-pro' );
return $columns;
}
/**
* Add the hidden fields in the SEO Details column of the terms listing screen.
*
* @param string $string Current content.
* @param string $column_name Column name.
* @param int $term_id Term ID.
*
* @return string New content.
*/
public function tax_seo_column_content( $string, $column_name, $term_id ) {
if ( 'rank_math_tax_seo_details' !== $column_name ) {
return $string;
}
ob_start();
$this->quick_edit_hidden_fields( $term_id, 'term' );
return ob_get_clean();
}
/**
* Output hidden fields in the `seo_details` column on the posts and the
* terms screen, to use the data in the quick edit form.
*
* @param int $object_id Post/term ID.
* @param object $object_type Object type: post or term.
*
* @return void
*/
public function quick_edit_hidden_fields( $object_id, $object_type = 'post' ) {
if ( ! in_array( $object_type, [ 'post', 'term' ], true ) ) {
return;
}
if ( 'post' === $object_type && ! $this->can_bulk_edit() ) {
return;
}
$robots = array_filter( (array) get_metadata( $object_type, $object_id, 'rank_math_robots', true ) );
if ( empty( $robots ) ) {
$robots = Helper::get_robots_defaults();
}
// Maybe product with hidden visibility!
if ( Conditional::is_woocommerce_active() && 'product' === get_post_type( $object_id ) && Helper::get_settings( 'general.noindex_hidden_products' ) ) {
$product = \wc_get_product( $object_id );
if ( $product && $product->get_catalog_visibility() === 'hidden' ) {
// Preserve other robots values.
$robots = array_filter(
$robots,
function ( $robot ) {
return 'index' !== $robot;
}
);
$robots = array_merge( $robots, [ 'noindex' ] );
}
}
$title = get_metadata( $object_type, $object_id, 'rank_math_title', true );
if ( ! $title ) {
if ( 'post' === $object_type ) {
$post_type = get_post_type( $object_id );
$title = Helper::get_settings( "titles.pt_{$post_type}_title" );
} elseif ( 'term' === $object_type ) {
$term = get_term( $object_id );
$taxonomy = $term->taxonomy;
$title = Helper::get_settings( "titles.tax_{$taxonomy}_title" );
}
}
$description = get_metadata( $object_type, $object_id, 'rank_math_description', true );
if ( ! $description ) {
if ( 'post' === $object_type ) {
$post_type = get_post_type( $object_id );
$description = Helper::get_settings( "titles.pt_{$post_type}_description" );
} elseif ( 'term' === $object_type ) {
$term = get_term( $object_id );
$taxonomy = $term->taxonomy;
$description = Helper::get_settings( "titles.tax_{$taxonomy}_description" );
}
}
$canonical = get_metadata( $object_type, $object_id, 'rank_math_canonical_url', true );
$focus_keywords = Arr::from_string( get_metadata( $object_type, $object_id, 'rank_math_focus_keyword', true ) );
$primary_keyword = ! empty( $focus_keywords ) ? $focus_keywords[0] : '';
$canonical_placeholder = '';
if ( 'post' === $object_type ) {
$canonical_placeholder = get_permalink( $object_id );
} elseif ( 'term' === $object_type ) {
$canonical_placeholder = get_term_link( $object_id );
}
?>
<input type="hidden" class="rank-math-title-value" id="rank-math-title-<?php echo esc_attr( $object_id ); ?>" value="<?php echo esc_attr( $title ); ?>">
<input type="hidden" class="rank-math-description-value" id="rank-math-description-<?php echo esc_attr( $object_id ); ?>" value="<?php echo esc_attr( $description ); ?>">
<input type="hidden" class="rank-math-robots-meta-value" id="rank-math-robots-meta-<?php echo esc_attr( $object_id ); ?>" value="<?php echo esc_attr( wp_json_encode( $robots ) ); ?>">
<input type="hidden" class="rank-math-canonical-url-value" id="rank-math-canonical-url-<?php echo esc_attr( $object_id ); ?>" value="<?php echo esc_attr( $canonical ); ?>">
<input type="hidden" class="rank-math-canonical-placeholder-value" id="rank-math-canonical-placeholder-<?php echo esc_attr( $object_id ); ?>" value="<?php echo esc_attr( $canonical_placeholder ); ?>">
<input type="hidden" class="rank-math-focus-keywords-value" id="rank-math-focus-keywords-<?php echo esc_attr( $object_id ); ?>" value="<?php echo esc_attr( $primary_keyword ); ?>">
<?php if ( 'post' === $object_type ) : ?>
<input type="hidden" class="rank-math-primary-term-value" id="rank-math-primary-term-<?php echo esc_attr( $object_id ); ?>" value="<?php echo esc_attr( ProAdminHelper::get_primary_term_id( $object_id ) ); ?>">
<?php endif; ?>
<?php
}
/**
* Enqueue styles and scripts.
*/
public function admin_scripts() {
global $pagenow;
if ( Admin_Helper::is_post_list() ) {
wp_enqueue_script( 'rank-math-pro-post-list', RANK_MATH_PRO_URL . 'assets/admin/js/post-list.js', [], RANK_MATH_PRO_VERSION, true );
wp_enqueue_style( 'rank-math-pro-post-list', RANK_MATH_PRO_URL . 'assets/admin/css/post-list.css', [], RANK_MATH_PRO_VERSION );
} elseif ( 'edit-tags.php' === $pagenow ) {
wp_enqueue_script( 'rank-math-pro-term-list', RANK_MATH_PRO_URL . 'assets/admin/js/term-list.js', [], RANK_MATH_PRO_VERSION, true );
wp_enqueue_style( 'rank-math-pro-term-list', RANK_MATH_PRO_URL . 'assets/admin/css/term-list.css', [], RANK_MATH_PRO_VERSION );
}
}
/**
* Display our custom content on the quick-edit interface. No values can be
* pre-populated (all done in JS).
*
* @param string $column Column name.
* @param bool $bulk_edit Is bulk edit row.
* @return void
*/
public function quick_edit( $column, $bulk_edit = false ) {
if ( ! $this->can_bulk_edit() ) {
return;
}
global $post_type;
$ptype = $post_type;
if ( is_a( $ptype, 'WP_Post_Type' ) ) {
$ptype = $ptype->name;
}
global $pagenow;
if ( 'edit-tags.php' === $pagenow ) {
global $taxonomy;
$ptype = $taxonomy;
}
$columns = get_column_headers( 'edit-' . $ptype );
if ( ! isset( $columns['rank_math_seo_details'] ) && ! isset( $columns['rank_math_tax_seo_details'] ) ) {
return;
}
$robots = [
'index' => __( 'Index', 'rank-math-pro' ),
'noindex' => __( 'No Index', 'rank-math-pro' ),
'nofollow' => __( 'No Follow', 'rank-math-pro' ),
'noarchive' => __( 'No Archive', 'rank-math-pro' ),
'noimageindex' => __( 'No Image Index', 'rank-math-pro' ),
'nosnippet' => __( 'No Snippet', 'rank-math-pro' ),
];
switch ( $column ) {
case 'rank_math_seo_details':
wp_nonce_field( 'rank-math-quick-edit', 'rank_math_quick_edit_nonce' );
?>
<div class="rank-math-quick-edit wp-clearfix">
<fieldset class="inline-edit-col-left clear">
<legend class="inline-edit-legend">
<?php esc_html_e( 'SEO Settings', 'rank-math-pro' ); ?>
</legend>
<div class="inline-edit-col wp-clearfix">
<?php
break;
case 'rank_math_title':
?>
<label>
<span class="title"><?php esc_html_e( 'SEO Title', 'rank-math-pro' ); ?></span>
<span class="input-text-wrap rank-math-quick-edit-text-wrap">
<input type="text" name="rank_math_title" id="rank_math_title" value="">
</span>
</label>
<?php
break;
case 'rank_math_description':
?>
<label class="inline-edit-seo-description">
<span class="title"><?php esc_html_e( 'SEO Description', 'rank-math-pro' ); ?></span>
<textarea name="rank_math_description" id="rank_math_description"></textarea>
</label>
</div></fieldset>
<fieldset class="inline-edit-col-center inline-edit-robots">
<div class="inline-edit-col">
<span class="title inline-edit-robots-label"><?php esc_html_e( 'Robots Meta', 'rank-math-pro' ); ?></span>
<ul class="cat-checklist category-checklist rank-math-robots-checklist">
<?php foreach ( $robots as $val => $option ) : ?>
<li id="rank_math_robots_<?php echo esc_attr( $val ); ?>" class="rank_math_robots">
<label class="selectit">
<input type="checkbox" name="rank_math_robots[]" id="rank_math_robots_<?php echo esc_attr( $val ); ?>_input" value="<?php echo esc_attr( $val ); ?>" checked="">
<?php echo esc_html( $option ); ?>
</label>
</li>
<?php endforeach; ?>
</ul>
</div>
</fieldset>
<fieldset class="inline-edit-col-right">
<div class="inline-edit-col">
<?php if ( ! $bulk_edit ) { ?>
<label>
<span class="title"><?php esc_html_e( 'Primary Focus Keyword', 'rank-math-pro' ); ?></span>
<span class="input-text-wrap rank-math-quick-edit-text-wrap">
<input type="text" name="rank_math_focus_keyword" id="rank_math_focus_keyword" value="">
</span>
</label>
<label>
<span class="title"><?php esc_html_e( 'Canonical URL', 'rank-math-pro' ); ?></span>
<span class="input-text-wrap rank-math-quick-edit-text-wrap">
<input type="text" name="rank_math_canonical_url" id="rank_math_canonical_url" value="">
</span>
</label>
<?php
if ( false === $this->do_filter( 'admin/disable_primary_term', false ) ) :
$taxonomy = ProAdminHelper::get_primary_taxonomy();
if ( false !== $taxonomy ) :
?>
<fieldset class="inline-edit-rank-math-primary-term inline-edit-categories">
<?php wp_nonce_field( 'rank-math-edit-primary-term', 'rank_math_bulk_edit_primary_term' ); ?>
<label class="rank-math-primary-term">
<span class="title">
<?php // Translators: placeholder is taxonomy name, e.g. "Category". ?>
<?php echo esc_html( sprintf( __( 'Primary %s', 'rank-math-pro' ), $taxonomy['singularLabel'] ) ); ?>
</span>
<span class="input-text-wrap rank-math-quick-edit-text-wrap">
<?php
wp_dropdown_categories(
[
'name' => 'rank_math_primary_term',
'id' => 'rank_math_primary_term',
'class' => '',
'selected' => '0',
'orderby' => 'name',
'taxonomy' => $taxonomy['name'],
'hide_empty' => false,
'show_option_all' => false,
'show_option_none' => __( '&mdash; Not Selected &mdash;', 'rank-math-pro' ),
'option_none_value' => '0',
]
);
?>
</span>
</label>
</fieldset>
<?php
endif;
endif;
?>
<?php } ?>
</div>
</fieldset>
</div>
<?php
break;
case 'rank_math_tax_seo_details':
wp_nonce_field( 'rank-math-quick-edit', 'rank_math_quick_edit_nonce' );
?>
<div class="rank-math-quick-edit wp-clearfix inline-edit-row">
<fieldset>
<legend class="inline-edit-legend">
<?php esc_html_e( 'SEO Settings', 'rank-math-pro' ); ?>
</legend>
<div class="inline-edit-col">
<label>
<span class="title"><?php esc_html_e( 'SEO Title', 'rank-math-pro' ); ?></span>
<span class="input-text-wrap rank-math-quick-edit-text-wrap">
<input type="text" name="rank_math_title" id="rank_math_title" value="">
</span>
</label>
<label class="inline-edit-seo-description">
<div><?php esc_html_e( 'SEO Description', 'rank-math-pro' ); ?></div>
<textarea name="rank_math_description" id="rank_math_description"></textarea>
</label>
<div class="inline-edit-robots-label"><?php esc_html_e( 'Robots Meta', 'rank-math-pro' ); ?></div>
<ul class="cat-checklist category-checklist rank-math-robots-checklist">
<?php foreach ( $robots as $val => $option ) : ?>
<li id="rank_math_robots_<?php echo esc_attr( $val ); ?>" class="rank_math_robots">
<label class="selectit">
<input type="checkbox" name="rank_math_robots[]" id="rank_math_robots_<?php echo esc_attr( $val ); ?>_input" value="<?php echo esc_attr( $val ); ?>" checked="">
<?php echo esc_html( $option ); ?>
</label>
</li>
<?php endforeach; ?>
</ul>
<label class="clear">
<div class="title"><?php esc_html_e( 'Primary Focus Keyword', 'rank-math-pro' ); ?></div>
<div class="rank-math-quick-edit-text-wrap">
<input type="text" name="rank_math_focus_keyword" id="rank_math_focus_keyword" value="">
</div>
</label>
<label class="clear">
<div class="title"><?php esc_html_e( 'Canonical URL', 'rank-math-pro' ); ?></div>
<div class="input-text-wrap rank-math-quick-edit-text-wrap">
<input type="text" name="rank_math_canonical_url" id="rank_math_canonical_url" value="">
</div>
</label>
</div>
</fieldset>
</div>
<?php
break;
}
}
/**
* Add fields for the bulk edit row.
* Just a wrapper for the quick_edit() method, since the fields are mostly the same.
*
* @param string $column Column name.
* @return void
*/
public function bulk_edit( $column ) {
$this->quick_edit( $column, true );
}
/**
* Save bulk edit data if needed.
*
* @return void
*/
public function maybe_save_bulk_edit() {
if ( ! Param::request( 'bulk_edit' ) ) {
return;
}
$this->save_bulk_edit( $_REQUEST ); // phpcs:ignore
}
/**
* Save bulk edit data.
*
* @param array $post_data Post data input.
* @return void
*/
public function save_bulk_edit( $post_data ) {
if ( empty( $post_data ) ) {
$post_data = &$_POST; // phpcs:ignore
}
if ( isset( $post_data['post_type'] ) ) {
$ptype = get_post_type_object( $post_data['post_type'] );
} else {
$ptype = get_post_type_object( 'post' );
}
if ( ! current_user_can( $ptype->cap->edit_posts ) ) {
return;
}
if ( ! Helper::has_cap( 'onpage_general' ) ) {
return;
}
if ( ! $this->can_bulk_edit( $ptype ) ) {
return;
}
$save_fields = [
'title',
'description',
'robots',
'primary_term',
];
$post_ids = array_map( 'intval', (array) $post_data['post'] );
foreach ( $post_ids as $post_id ) {
foreach ( $save_fields as $field ) {
$field_name = 'rank_math_' . $field;
$field_value = isset( $post_data[ $field_name ] ) ? $post_data[ $field_name ] : '';
if ( is_string( $field_value ) ) {
$field_value = trim( $field_value );
}
if ( empty( $field_value ) ) {
// Skip if not set.
continue;
}
if ( 'robots' === $field ) {
$field_value = (array) $field_value;
} elseif ( 'primary_term' === $field ) {
$taxonomy = ProAdminHelper::get_primary_taxonomy( $post_id );
$field_name = 'rank_math_primary_' . $taxonomy['name'];
}
update_post_meta( $post_id, $field_name, $field_value );
}
}
}
/**
* Save post quick edit.
*
* @param int $post_id Post ID.
* @return mixed
*/
public function save_post( $post_id ) {
if ( wp_is_post_revision( $post_id ) || ! wp_verify_nonce( Param::post( 'rank_math_quick_edit_nonce' ), 'rank-math-quick-edit' ) ) {
return;
}
if ( ! Helper::has_cap( 'onpage_general' ) ) {
return;
}
$post_type = get_post_type( $post_id );
if ( ! $this->can_bulk_edit( $post_type ) ) {
return;
}
$taxonomy = ProAdminHelper::get_primary_taxonomy( $post_id );
$save_fields = [
'title',
'description',
'robots',
'focus_keyword',
'canonical_url',
'primary_term',
];
foreach ( $save_fields as $field ) {
$field_name = 'rank_math_' . $field;
$flag = [];
if ( 'robots' === $field ) {
$flag = FILTER_REQUIRE_ARRAY;
}
$field_value = Param::post( $field_name, false, FILTER_DEFAULT, $flag );
if ( false === $field_value ) {
continue;
}
$default_value = '';
if ( $post_type ) {
$default_value = Helper::get_settings( 'titles.pt_' . $post_type . '_' . $field );
}
if ( 'robots' === $field ) {
$field_value = array_filter( $field_value );
$field_value = array_unique( $field_value );
$field_value = array_intersect( $field_value, [ 'index', 'noindex', 'nofollow', 'noarchive', 'noimageindex', 'nosnippet' ] );
} elseif ( 'canonical_url' === $field ) {
$field_value = esc_url_raw( $field_value );
} elseif ( 'focus_keyword' === $field ) {
$current_value = get_post_meta( $post_id, $field_name, true );
$current = Arr::from_string( $current_value );
$keywords = Arr::from_string( $field_value );
$current[0] = ! empty( $keywords ) ? $keywords[0] : '';
if ( '' === $current[0] ) {
array_shift( $current );
}
$field_value = join( ', ', $current );
} elseif ( 'primary_term' === $field ) {
if ( ! $field_value ) {
delete_post_meta( $post_id, $field_name );
continue;
}
if ( ! has_term( absint( $field_value ), $taxonomy['name'], $post_id ) ) {
continue;
}
$field_name = 'rank_math_primary_' . $taxonomy['name'];
}
if ( empty( $field_value ) || $field_value === $default_value ) {
delete_post_meta( $post_id, $field_name );
continue;
}
update_post_meta( $post_id, $field_name, $field_value );
}
}
/**
* Save taxonomy term quick edit.
*
* @param int $term_id Term ID.
*
* @return void
*/
public function save_tax( $term_id ) {
$term_id = Param::post( 'tax_ID' );
if ( ! $term_id ) {
return;
}
if ( ! wp_verify_nonce( Param::post( 'rank_math_quick_edit_nonce' ), 'rank-math-quick-edit' ) ) {
return;
}
if ( ! Helper::has_cap( 'onpage_general' ) ) {
return;
}
$save_fields = [
'title',
'description',
'robots',
'focus_keyword',
'canonical_url',
];
foreach ( $save_fields as $field ) {
$field_name = 'rank_math_' . $field;
$flag = [];
if ( 'robots' === $field ) {
$flag = FILTER_REQUIRE_ARRAY;
}
$field_value = Param::post( $field_name, false, FILTER_DEFAULT, $flag );
if ( false === $field_value ) {
continue;
}
if ( 'robots' === $field ) {
$field_value = array_filter( $field_value );
$field_value = array_unique( $field_value );
$field_value = array_intersect( $field_value, [ 'index', 'noindex', 'nofollow', 'noarchive', 'noimageindex', 'nosnippet' ] );
} elseif ( 'canonical_url' === $field ) {
$field_value = esc_url_raw( $field_value );
} elseif ( 'focus_keyword' === $field ) {
$current = get_term_meta( $term_id, $field_name, true );
$current = Arr::from_string( $current );
$keywords = Arr::from_string( $field_value );
$current[0] = ! empty( $keywords ) ? $keywords[0] : '';
if ( '' === $current[0] ) {
array_shift( $current );
}
$field_value = join( ', ', $current );
}
update_term_meta( $term_id, $field_name, $field_value );
}
}
/**
* Check if bulk editing is enabled for the current post type.
*
* @param string $ptype Post type name.
*
* @return boolean
*/
public function can_bulk_edit( $ptype = null ) {
global $post_type;
if ( ! $ptype ) {
$ptype = $post_type;
}
if ( is_a( $ptype, 'WP_Post_Type' ) ) {
$ptype = $ptype->name;
}
$allow_editing = Helper::get_settings( 'titles.pt_' . $ptype . '_bulk_editing', true );
if ( ! $allow_editing || 'readonly' === $allow_editing ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,302 @@
<?php
/**
* Setup wizard.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\KB;
use RankMath\Helper;
use RankMath\Traits\Ajax;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Param;
use MyThemeShop\Helpers\WordPress;
defined( 'ABSPATH' ) || exit;
/**
* Trends tool class.
*
* @codeCoverageIgnore
*/
class Setup_Wizard {
use Hooker, Ajax;
/**
* Register hooks.
*/
public function __construct() {
$this->action( 'admin_init', 'enqueue', 20 );
$this->action( 'rank_math/wizard/settings/compatibility', 'add_sw_custom_mode', 20 );
$this->filter( 'rank_math/wizard/steps', 'steps' );
$this->ajax( 'import_settings', 'ajax_import_settings' );
}
/**
* Enqueue assets.
*
* @return void
*/
public function enqueue() {
if ( Param::get( 'page' ) !== 'rank-math-wizard' ) {
return;
}
wp_enqueue_style(
'rank-math-pro-setup-wizard',
RANK_MATH_PRO_URL . 'assets/admin/css/setup-wizard.css',
null,
rank_math_pro()->version
);
wp_enqueue_script(
'rank-math-pro-setup-wizard',
RANK_MATH_PRO_URL . 'assets/admin/js/setup-wizard.js',
[ 'jquery' ],
rank_math_pro()->version,
true
);
}
/**
* Add custom option in Setup Wizard.
*
* @param CMB2 $cmb CMB instance.
*/
public function add_sw_custom_mode( $cmb ) {
$field = $cmb->get_field( 'setup_mode' );
if ( false !== $field ) {
$import_html = '<div id="rank-math-custom-mode-form" class="hidden">';
$import_html .= '<div id="rank-math-custom-mode-form-initial">';
$import_html .= '<input type="file" id="rank-math-custom-mode-file-field" value="" accept=".txt,.json">';
$import_html .= '<button type="button" class="button button-primary button-small" id="rank-math-custom-mode-submit">' . __( 'Upload', 'rank-math-pro' ) . '</button>';
$import_html .= '<p id="rank-math-custom-mode-import-help">';
$import_html .= __( 'Select a Rank Math settings file on your computer and upload it to use the custom settings.', 'rank-math-pro' ) . ' ';
// Translators: placeholder is a list of filetypes.
$import_html .= sprintf( __( 'Allowed filetypes: %s', 'rank-math-pro' ), '<code>.txt, .json</code>' ) . ' ';
$import_html .= '<a href="' . KB::get( 'import-export-settings', 'SW Import Settings Option' ) . '">' . __( 'More information', 'rank-math-pro' ) . '</a> ';
$import_html .= '</p>';
$import_html .= '</div>';
$import_html .= '<p id="rank-math-custom-mode-import-progress" class="hidden"><img src="' . esc_url( get_admin_url() . 'images/loading.gif' ) . '" /> ' . __( 'Uploading...', 'rank-math-pro' ) . '</p>';
$import_html .= '<p id="rank-math-custom-mode-import-success" class="hidden">' . __( 'Import finished. In the next steps you can review the settings.', 'rank-math-pro' ) . '</p>';
$import_html .= '<p id="rank-math-custom-mode-import-fail" class="hidden">' . __( 'Import failed.', 'rank-math-pro' ) . ' <span id="rank-math-custom-mode-import-message"></span></p>';
$import_html .= '</div>';
$field->args['options'] = [
/* translators: Option Description */
'easy' => '<div class="rank-math-mode-title">' . sprintf( __( 'Easy %s', 'rank-math-pro' ), '</div><p>' . __( 'For websites where you only want to change the basics and let Rank Math do most of the heavy lifting. Most settings are set to default as per industry best practices. One just has to set it and forget it.', 'rank-math-pro' ) . '</p>' ),
/* translators: Option Description */
'advanced' => '<div class="rank-math-mode-title">' . sprintf( __( 'Advanced %s', 'rank-math-pro' ), '</div><p>' . __( 'For the advanced users who want to control every SEO aspect of the website. You are offered options to change everything and have full control over the websites SEO.', 'rank-math-pro' ) . '</p>' ),
/* translators: Option Description */
'custom' => '<div class="rank-math-mode-title">' . sprintf( __( 'Custom Mode %s', 'rank-math-pro' ), '</div><p class="rank-math-mode-description">' . __( 'Select this if you have a custom Rank Math settings file you want to use.', 'rank-math-pro' ) . '</p>' ) . $import_html,
];
}
}
/**
* Ajax import settings.
*/
public function ajax_import_settings() {
$this->verify_nonce( 'rank-math-ajax-nonce' );
$this->has_cap_ajax( 'general' );
$file = $this->has_valid_import_file();
if ( false === $file ) {
return false;
}
// Parse Options.
$wp_filesystem = WordPress::get_filesystem();
if ( is_null( $wp_filesystem ) ) {
return false;
}
$settings = $wp_filesystem->get_contents( $file['file'] );
$settings = json_decode( $settings, true );
\unlink( $file['file'] );
if ( is_array( $settings ) && $this->do_import_data( $settings ) ) {
$this->success( __( 'Import successful.', 'rank-math-pro' ) );
exit();
}
$this->error( __( 'No settings found to be imported.', 'rank-math-pro' ) );
exit();
}
/**
* Add News/Video Sitemap in Setup Wizard.
*
* @param array $steps Wizard Steps.
*
* @return array
*/
public function steps( $steps ) {
if ( isset( $steps['sitemaps'] ) ) {
$steps['sitemaps']['class'] = '\\RankMathPro\\Wizard\\Sitemap';
}
return $steps;
}
/**
* Import has valid file.
*
* @return mixed
*/
private function has_valid_import_file() {
if ( empty( $_FILES['import-me'] ) ) {
$this->error( __( 'No file selected.', 'rank-math-pro' ) );
return false;
}
$this->filter( 'upload_mimes', 'allow_txt_upload' );
$file = wp_handle_upload( $_FILES['import-me'], [ 'test_form' => false ] );
$this->remove_filter( 'upload_mimes', 'allow_txt_upload', 10 );
if ( is_wp_error( $file ) ) {
$this->error( __( 'Settings file could not be imported:', 'rank-math-pro' ) . ' ' . $file->get_error_message() );
return false;
}
if ( isset( $file['error'] ) ) {
$this->error( __( 'Settings could not be imported:', 'rank-math-pro' ) . ' ' . $file['error'] );
return false;
}
if ( ! isset( $file['file'] ) ) {
$this->error( __( 'Settings could not be imported: Upload failed.', 'rank-math-pro' ) );
return false;
}
return $file;
}
/**
* Allow txt & json file upload.
*
* @param array $types Mime types keyed by the file extension regex corresponding to those types.
*
* @return array
*/
public function allow_txt_upload( $types ) {
$types['txt'] = 'text/plain';
$types['json'] = 'application/json';
return $types;
}
/**
* Does import data.
*
* @param array $data Import data.
* @param bool $suppress_hooks Suppress hooks or not.
* @return bool
*/
private function do_import_data( array $data, $suppress_hooks = false ) {
$this->run_import_hooks( 'pre_import', $data, $suppress_hooks );
// Import options.
$down = $this->import_set_options( $data );
// Import capabilities.
if ( ! empty( $data['role-manager'] ) ) {
$down = true;
Helper::set_capabilities( $data['role-manager'] );
}
// Import redirections.
if ( ! empty( $data['redirections'] ) ) {
$down = true;
$this->import_set_redirections( $data['redirections'] );
}
$this->run_import_hooks( 'after_import', $data, $suppress_hooks );
return $down;
}
/**
* Set options from data.
*
* @param array $data An array of data.
*/
private function import_set_options( $data ) {
$set = false;
$hash = [
'modules' => 'rank_math_modules',
'general' => 'rank-math-options-general',
'titles' => 'rank-math-options-titles',
'sitemap' => 'rank-math-options-sitemap',
];
foreach ( $hash as $key => $option_key ) {
if ( ! empty( $data[ $key ] ) ) {
$set = true;
update_option( $option_key, $data[ $key ] );
}
}
return $set;
}
/**
* Set redirections.
*
* @param array $redirections An array of redirections to import.
*/
private function import_set_redirections( $redirections ) {
foreach ( $redirections as $key => $redirection ) {
$matched = \RankMath\Redirections\DB::match_redirections_source( $redirection['sources'] );
if ( ! empty( $matched ) ) {
continue;
}
$sources = maybe_unserialize( $redirection['sources'] );
if ( ! is_array( $sources ) ) {
continue;
}
\RankMath\Redirections\DB::add(
[
'url_to' => $redirection['url_to'],
'sources' => $sources,
'header_code' => $redirection['header_code'],
'hits' => $redirection['hits'],
'created' => $redirection['created'],
'updated' => $redirection['updated'],
]
);
}
}
/**
* Run import hooks
*
* @param string $hook Hook to fire.
* @param array $data Import data.
* @param bool $suppress Suppress hooks or not.
*/
private function run_import_hooks( $hook, $data, $suppress ) {
if ( ! $suppress ) {
/**
* Fires while importing settings.
*
* @since 0.9.0
*
* @param array $data Import data.
*/
$this->do_action( 'import/settings/' . $hook, $data );
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* Google Trends tool for the post editor.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Admin\Admin_Helper;
defined( 'ABSPATH' ) || exit;
/**
* Trends tool class.
*
* @codeCoverageIgnore
*/
class Trends_Tool {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$this->action( 'rank_math/admin/editor_scripts', 'editor_scripts', 20 );
}
/**
* Enqueue assets for post/term/user editors.
*
* @return void
*/
public function editor_scripts() {
global $pagenow;
if ( ! Admin_Helper::is_post_edit() && 'term.php' !== $pagenow && ! Admin_Helper::is_user_edit() ) {
return;
}
if ( ! wp_script_is( 'rank-math-editor' ) ) {
return;
}
wp_enqueue_script(
'rank-math-pro-editor',
RANK_MATH_PRO_URL . 'assets/admin/js/gutenberg.js',
[
'jquery-ui-autocomplete',
'rank-math-editor',
],
RANK_MATH_PRO_VERSION,
true
);
wp_enqueue_style( 'rank-math-pro-editor', RANK_MATH_PRO_URL . 'assets/admin/css/gutenberg.css', [], RANK_MATH_PRO_VERSION );
}
}

View File

@@ -0,0 +1,421 @@
<?php
/**
* The admin-specific functionality of the plugin.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin\CSV_Import_Export;
use RankMath\Helper;
use RankMath\Traits\Hooker;
defined( 'ABSPATH' ) || exit;
/**
* CSV Import Export class.
*
* @codeCoverageIgnore
*/
class CSV_Import_Export {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$this->filter( 'rank_math/admin/import_export_panels', 'add_panel', 15 );
$this->action( 'admin_enqueue_scripts', 'enqueue' );
$this->action( 'admin_init', 'maybe_do_import', 99 );
$this->action( 'admin_init', 'maybe_do_export', 110 );
$this->action( 'admin_init', 'maybe_cancel_import', 120 );
$this->action( 'wp_ajax_csv_import_progress', 'csv_import_progress' );
Import_Background_Process::get();
}
/**
* Add CSV import/export panel.
*
* @param array $panels Panels array.
* @return array
*/
public function add_panel( $panels ) {
// Insert after "import".
$position = array_search( 'import', array_keys( $panels ), true ) + 1;
$new = array_slice( $panels, 0, $position );
$new['csv'] = [
'view' => RANK_MATH_PRO_PATH . 'includes/views/csv-import-export-panel.php',
'class' => 'import-export-csv',
];
$end = array_slice( $panels, $position );
$result = array_merge( $new, $end );
return $result;
}
/**
* Check if current screen is Status & Tools > Import / Export.
*
* @return bool
*/
public function is_import_export_screen() {
return is_admin() && ! wp_doing_ajax() && isset( $_GET['page'] ) && 'rank-math-status' === $_GET['page'] && isset( $_GET['view'] ) && 'import_export' === $_GET['view']; // phpcs:ignore
}
/**
* Enqueue styles.
*
* @return void
*/
public function enqueue() {
if ( ! $this->is_import_export_screen() ) {
return;
}
Helper::add_json( 'confirmCsvImport', __( 'Are you sure you want to import meta data from this CSV file?', 'rank-math-pro' ) );
Helper::add_json( 'confirmCsvCancel', __( 'Are you sure you want to stop the import process?', 'rank-math-pro' ) );
Helper::add_json( 'csvProgressNonce', wp_create_nonce( 'rank_math_csv_progress' ) );
wp_enqueue_style( 'rank-math-pro-csv-import-export', RANK_MATH_PRO_URL . 'assets/admin/css/import-export.css', [], RANK_MATH_PRO_VERSION );
wp_enqueue_script( 'rank-math-pro-csv-import-export', RANK_MATH_PRO_URL . 'assets/admin/js/import-export.js', [], RANK_MATH_PRO_VERSION, true );
}
/**
* Add notice after import is started.
*
* @return void
*/
public function add_notice() {
if ( ! $this->is_import_export_screen() ) {
return;
}
Helper::add_notification( esc_html__( 'CSV import is in progress...', 'rank-math-pro' ), [ 'type' => 'success' ] );
}
/**
* Start export if requested and allowed.
*
* @return void
*/
public function maybe_do_export() {
if ( ! is_admin() || empty( $_POST['rank_math_pro_csv_export'] ) ) {
return;
}
if ( empty( $_POST['object_types'] ) || ! is_array( $_POST['object_types'] ) ) {
wp_die( esc_html__( 'Please select at least one object type to export.', 'rank-math-pro' ) );
}
if ( ! wp_verify_nonce( isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : '', 'rank_math_pro_csv_export' ) ) {
wp_die( esc_html__( 'Invalid nonce.', 'rank-math-pro' ) );
}
if ( ! current_user_can( 'export' ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to export the content of this site.', 'rank-math-pro' ) );
}
$use_advanced_options = ! empty( $_POST['use_advanced_options'] );
$advanced_options = [
'post_types' => isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] ) ? array_map( 'sanitize_title', wp_unslash( $_POST['post_types'] ) ) : [],
'taxonomies' => isset( $_POST['taxonomies'] ) && is_array( $_POST['taxonomies'] ) ? array_map( 'sanitize_title', wp_unslash( $_POST['taxonomies'] ) ) : [],
'roles' => isset( $_POST['roles'] ) && is_array( $_POST['roles'] ) ? array_map( 'sanitize_title', wp_unslash( $_POST['roles'] ) ) : [],
'readonly_columns' => ! empty( $_POST['readonly_columns'] ),
];
$exporter = new Exporter( array_map( 'sanitize_title', wp_unslash( $_POST['object_types'] ) ), $use_advanced_options ? $advanced_options : false );
$exporter->process_export();
}
/**
* Start import if requested and allowed.
*
* @return void
*/
public function maybe_do_import() {
if ( ! is_admin() || empty( $_POST['object_id'] ) || 'csv-import-plz' !== $_POST['object_id'] ) {
return;
}
if ( empty( $_FILES['csv-import-me'] ) || empty( $_FILES['csv-import-me']['name'] ) ) {
wp_die( esc_html__( 'Please select a file to import.', 'rank-math-pro' ) );
}
if ( ! wp_verify_nonce( isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : '', 'rank_math_pro_csv_import' ) ) {
wp_die( esc_html__( 'Invalid nonce.', 'rank-math-pro' ) );
}
if ( ! current_user_can( 'import' ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to import contents to this site.', 'rank-math-pro' ) );
}
// Rename file.
$info = pathinfo( $_FILES['csv-import-me']['name'] );
$_FILES['csv-import-me']['name'] = uniqid( 'rm-csv-' ) . ( ! empty( $info['extension'] ) ? '.' . $info['extension'] : '' );
// Handle file.
$this->filter( 'upload_mimes', 'allow_csv_upload' );
$file = wp_handle_upload( $_FILES['csv-import-me'], [ 'test_form' => false ] );
$this->remove_filter( 'upload_mimes', 'allow_csv_upload', 10 );
if ( ! $this->validate_file( $file ) ) {
return false;
}
$settings = [
'no_overwrite' => ! empty( $_POST['no_overwrite'] ),
];
$importer = new Importer();
$importer->start( $file['file'], $settings );
}
/**
* Allow CSV file upload.
*
* @param array $types Mime types keyed by the file extension regex corresponding to those types.
* @return array
*/
public function allow_csv_upload( $types ) {
$types['csv'] = 'text/csv';
return $types;
}
/**
* Validate file.
*
* @param mixed $file File array or object.
* @return bool
*/
public function validate_file( $file ) {
if ( is_wp_error( $file ) ) {
Helper::add_notification( esc_html__( 'CSV could not be imported:', 'rank-math-pro' ) . ' ' . $file->get_error_message(), [ 'type' => 'error' ] );
return false;
}
if ( isset( $file['error'] ) ) {
Helper::add_notification( esc_html__( 'CSV could not be imported:', 'rank-math-pro' ) . ' ' . $file['error'], [ 'type' => 'error' ] );
return false;
}
if ( ! isset( $file['file'] ) ) {
Helper::add_notification( esc_html__( 'CSV could not be imported: Upload failed.', 'rank-math-pro' ), [ 'type' => 'error' ] );
return false;
}
if ( ! isset( $file['type'] ) || 'text/csv' !== $file['type'] ) {
\unlink( $file['file'] );
Helper::add_notification( esc_html__( 'CSV could not be imported: File type error.', 'rank-math-pro' ), [ 'type' => 'error' ] );
return false;
}
return true;
}
/**
* Get import/export CSV columns.
*
* @return array
*/
public static function get_columns() {
$columns = [
'id',
'object_type',
'slug',
'seo_title',
'seo_description',
'is_pillar_content',
'focus_keyword',
'seo_score',
'robots',
'advanced_robots',
'canonical_url',
'primary_term',
'schema_data',
'social_facebook_thumbnail',
'social_facebook_title',
'social_facebook_description',
'social_twitter_thumbnail',
'social_twitter_title',
'social_twitter_description',
];
if ( Helper::is_module_active( 'redirections' ) ) {
$columns[] = 'redirect_to';
$columns[] = 'redirect_type';
}
/**
* Filter columns array.
*/
return apply_filters( 'rank_math/admin/csv_export_columns', $columns );
}
/**
* Get object types.
*
* @return array
*/
public static function get_possible_object_types() {
$object_types = [
'post' => __( 'Posts', 'rank-math-pro' ),
'term' => __( 'Terms', 'rank-math-pro' ),
'user' => __( 'Users', 'rank-math-pro' ),
];
/**
* Filter object types array.
*/
return apply_filters( 'rank_math/admin/csv_export_object_types', $object_types );
}
/**
* Check if cancel request is valid.
*
* @return void
*/
public static function maybe_cancel_import() {
if ( ! is_admin() || empty( $_GET['rank_math_cancel_csv_import'] ) ) {
return;
}
if ( ! wp_verify_nonce( isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : '', 'rank_math_pro_cancel_csv_import' ) ) {
Helper::add_notification( esc_html__( 'Import could not be canceled: invalid nonce. Please try again.', 'rank-math-pro' ), [ 'type' => 'error' ] );
wp_safe_redirect( remove_query_arg( 'rank_math_cancel_csv_import' ) );
exit;
}
if ( ! current_user_can( 'import' ) ) {
Helper::add_notification( esc_html__( 'Import could not be canceled: you are not allowed to import content to this site.', 'rank-math-pro' ), [ 'type' => 'error' ] );
wp_safe_redirect( remove_query_arg( 'rank_math_cancel_csv_import' ) );
exit;
}
self::cancel_import();
}
/**
* Cancel import.
*
* @param bool $silent Cancel silently.
* @return void
*/
public static function cancel_import( $silent = false ) {
$file_path = get_option( 'rank_math_csv_import' );
delete_option( 'rank_math_csv_import' );
delete_option( 'rank_math_csv_import_total' );
delete_option( 'rank_math_csv_import_status' );
delete_option( 'rank_math_csv_import_settings' );
Import_Background_Process::get()->cancel_process();
if ( ! $file_path ) {
if ( ! $silent ) {
Helper::add_notification( esc_html__( 'Import could not be canceled.', 'rank-math-pro' ), [ 'type' => 'error' ] );
}
wp_safe_redirect( remove_query_arg( 'rank_math_cancel_csv_import' ) );
exit;
}
unlink( $file_path );
if ( ! $silent ) {
Helper::add_notification(
__( 'CSV import canceled.', 'rank-math-pro' ),
[
'type' => 'success',
'classes' => 'is-dismissible',
]
);
}
wp_safe_redirect( remove_query_arg( 'rank_math_cancel_csv_import' ) );
exit;
}
/**
* Show import progress via AJAX.
*
* @return void
*/
public function csv_import_progress() {
check_ajax_referer( 'rank_math_csv_progress' );
if ( ! current_user_can( 'import' ) ) {
exit( '0' );
}
self::import_progress_details();
exit;
}
/**
* Output import progress details.
*
* @return void
*/
public static function import_progress_details() {
$import_in_progress = (bool) get_option( 'rank_math_csv_import' );
if ( $import_in_progress ) {
$total_lines = (int) get_option( 'rank_math_csv_import_total' );
$remaining_items = Import_Background_Process::get()->count_remaining_items();
$progress = $total_lines ? ( $total_lines - $remaining_items + 1 ) / $total_lines * 100 : 0;
?>
<p><?php esc_html_e( 'Import in progress...', 'rank-math-pro' ); ?></p>
<p class="csv-import-status">
<?php // Translators: placeholders represent count like 15/36. ?>
<?php printf( esc_html__( 'Items processed: %1$s/%2$s', 'rank-math-pro' ), absint( min( $total_lines, $total_lines - $remaining_items + 1 ) ), absint( $total_lines ) ); ?>
</p>
<div id="csv-import-progress-bar">
<div class="total">
<div class="progress-bar" style="width: <?php echo absint( $progress ); ?>%;"></div>
</div>
<input type="hidden" id="csv-import-progress-value" value="<?php echo absint( $progress ); ?>">
</div>
<?php
} else {
$status = (array) get_option( 'rank_math_csv_import_status', [] );
$classes = 'import-finished';
if ( ! empty( $status['errors'] ) ) {
$classes .= ' import-errors';
}
$message = self::get_import_complete_message();
?>
<p class="<?php echo esc_attr( $classes ); ?>"><?php echo wp_kses_post( $message ); ?></p>
<?php
}
}
/**
* Get status message after import is complete.
*
* @return string
*/
public static function get_import_complete_message() {
$status = (array) get_option( 'rank_math_csv_import_status', [] );
$message = sprintf(
// Translators: placeholder is the number of rows imported.
__( 'CSV import completed. Successfully imported %d rows.', 'rank-math-pro' ),
count( $status['imported_rows'] )
);
if ( ! empty( $status['errors'] ) ) {
$message = __( 'CSV import completed.', 'rank-math-pro' ) . ' ';
$message .= sprintf(
// Translators: placeholder is the number of rows imported.
__( 'Imported %d rows.', 'rank-math-pro' ) . ' ',
count( $status['imported_rows'] )
);
if ( ! empty( $status['errors'] ) ) {
$message .= __( 'One or more errors occured while importing: ', 'rank-math-pro' ) . '<br>';
$message .= join( '<br>', $status['errors'] ) . '<br>';
}
if ( ! empty( $status['failed_rows'] ) ) {
$message .= __( 'The following lines could not be imported: ', 'rank-math-pro' ) . '<br>';
$message .= join( ', ', $status['failed_rows'] );
}
}
return $message;
}
}

View File

@@ -0,0 +1,672 @@
<?php
/**
* The CSV Export class.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin\CSV_Import_Export;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Redirections\DB;
use RankMath\Redirections\Cache;
use RankMathPro\Admin\CSV;
defined( 'ABSPATH' ) || exit;
/**
* CSV Export.
*
* @codeCoverageIgnore
*/
class Exporter extends CSV {
use Hooker;
/**
* Data
*
* @var array
*/
private $data = [];
/**
* Term ID => slug cache.
*
* @var array
*/
private $term_slugs = [];
/**
* Not applicable placeholder.
*
* @var string
*/
private $not_applicable_value = 'n/a';
/**
* Object types we want to export.
*
* @var array
*/
private $object_types = [];
/**
* Use advanced options for export.
*
* @var bool
*/
private $use_advanced_options = false;
/**
* Advanced options.
*
* @var array
*/
private $advanced_options = [];
/**
* Redirection cache.
*
* @var array
*/
private $redirection = [];
/**
* Columns.
*
* @var array
*/
private $columns = [];
/**
* Whether we need link counts.
*
* @var bool
*/
private $needs_link_count = false;
/**
* Constructor.
*
* @param mixed $object_types Object types to export.
* @param mixed $advanced_options Options.
* @return void
*/
public function __construct( $object_types, $advanced_options ) {
$this->object_types = array_intersect( array_keys( CSV_Import_Export::get_possible_object_types() ), $object_types );
$this->use_advanced_options = ! empty( $advanced_options );
$this->advanced_options = $advanced_options;
if ( empty( $this->object_types ) ) {
wp_die( esc_html__( 'Please select at least one object type to export.', 'rank-math-pro' ) );
}
$this->not_applicable_value = apply_filters( 'rank_math/admin/csv_export_not_applicable', $this->not_applicable_value );
$this->needs_link_count = false;
if ( $this->use_advanced_options && ! empty( $this->advanced_options['readonly_columns'] ) ) {
if ( Helper::is_module_active( 'link-counter' ) ) {
$this->needs_link_count = true;
}
$this->filter( 'rank_math/admin/csv_export_columns', 'add_readonly_columns' );
}
$this->columns = CSV_Import_Export::get_columns();
}
/**
* Do export.
*
* @return void
*/
public function process_export() {
$this->export(
[
'filename' => 'rank-math',
'columns' => $this->columns,
'items' => $this->get_items(),
]
);
exit;
}
/**
* Output column contents.
*
* @return array
*/
public function get_items() {
foreach ( $this->object_types as $object_type ) {
$this->get_objects( $object_type );
}
return $this->data;
}
/**
* Get value for given column.
*
* @param string $column Column name.
* @param object $object WP_Post, WP_Term or WP_User.
*
* @return string
*/
public function get_column_value( $column, $object ) {
global $wpdb;
$value = '';
$object_type = 'post';
if ( ! empty( $object->term_id ) ) {
$object_type = 'term';
} elseif ( ! empty( $object->user_login ) ) {
$object_type = 'user';
}
$table = "{$object_type}meta";
$primary_column = "{$object_type}_id";
$object_id = isset( $object->ID ) ? $object->ID : $object->$primary_column;
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$meta_rows = $wpdb->get_results(
$wpdb->prepare(
/* translators: %d: object id, %s: table name */
"SELECT * FROM {$wpdb->$table} WHERE {$primary_column} = %d AND meta_key LIKE %s",
$object_id,
$wpdb->esc_like( 'rank_math_' ) . '%'
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$meta = $this->process_meta_rows( $meta_rows );
$internal_meta = (object) [];
if ( 'post' === $object_type && $this->needs_link_count ) {
$internal_meta = $this->get_link_counts( $object_id );
}
if ( 'user' !== $object_type && in_array( $column, [ 'redirect_to', 'redirect_type' ], true ) ) {
$redirection = $this->get_redirection( $object_type, $object_id );
}
switch ( $column ) {
case 'id':
$value = $object_id;
break;
case 'object_type':
$value = $object_type;
break;
case 'slug':
$slug = '';
if ( 'user' === $object_type ) {
$slug = $object->user_nicename;
} elseif ( 'post' === $object_type ) {
$slug = $object->post_name;
} elseif ( 'term' === $object_type ) {
$slug = $object->slug;
}
$value = urldecode( $slug );
break;
case 'seo_title':
if ( isset( $meta['rank_math_title'] ) ) {
$value = $meta['rank_math_title'];
}
break;
case 'seo_description':
if ( isset( $meta['rank_math_description'] ) ) {
$value = $meta['rank_math_description'];
}
break;
case 'is_pillar_content':
$value = 'no';
if ( in_array( $object_type, [ 'term', 'user' ], true ) ) {
$value = $this->not_applicable_value;
break;
}
if ( ! empty( $meta['rank_math_pillar_content'] ) ) {
$value = 'yes';
}
break;
case 'focus_keyword':
if ( isset( $meta['rank_math_focus_keyword'] ) ) {
$value = $meta['rank_math_focus_keyword'];
}
break;
case 'seo_score':
if ( isset( $meta['rank_math_seo_score'] ) ) {
$value = $meta['rank_math_seo_score'];
}
break;
case 'robots':
if ( isset( $meta['rank_math_robots'] ) ) {
$value = $this->process_robots( $meta['rank_math_robots'] );
}
break;
case 'advanced_robots':
if ( isset( $meta['rank_math_advanced_robots'] ) ) {
$value = $this->process_advanced_robots( $meta['rank_math_advanced_robots'] );
}
break;
case 'canonical_url':
if ( isset( $meta['rank_math_canonical_url'] ) ) {
$value = $meta['rank_math_canonical_url'];
}
break;
case 'primary_term':
if ( in_array( $object_type, [ 'term', 'user' ], true ) ) {
$value = $this->not_applicable_value;
break;
}
$value = $this->get_primary_term( $meta );
break;
case 'schema_data':
if ( in_array( $object_type, [ 'term', 'user' ], true ) ) {
$value = $this->not_applicable_value;
break;
}
$value = $this->process_schema_data( $meta );
break;
case 'social_facebook_thumbnail':
if ( isset( $meta['rank_math_facebook_image'] ) ) {
$value = $meta['rank_math_facebook_image'];
}
break;
case 'social_facebook_title':
if ( isset( $meta['rank_math_facebook_title'] ) ) {
$value = $meta['rank_math_facebook_title'];
}
break;
case 'social_facebook_description':
if ( isset( $meta['rank_math_facebook_description'] ) ) {
$value = $meta['rank_math_facebook_description'];
}
break;
case 'social_twitter_thumbnail':
if ( empty( $meta['rank_math_twitter_use_facebook'] ) || 'on' !== $meta['rank_math_twitter_use_facebook'] ) {
break;
}
if ( isset( $meta['rank_math_twitter_image'] ) ) {
$value = $meta['rank_math_twitter_image'];
}
break;
case 'social_twitter_title':
if ( ! isset( $meta['rank_math_twitter_use_facebook'] ) || 'on' !== $meta['rank_math_twitter_use_facebook'] ) {
break;
}
if ( isset( $meta['rank_math_twitter_title'] ) ) {
$value = $meta['rank_math_twitter_title'];
}
break;
case 'social_twitter_description':
if ( ! isset( $meta['rank_math_twitter_use_facebook'] ) || 'on' !== $meta['rank_math_twitter_use_facebook'] ) {
break;
}
if ( isset( $meta['rank_math_twitter_description'] ) ) {
$value = $meta['rank_math_twitter_description'];
}
break;
case 'redirect_to':
if ( 'user' === $object_type ) {
$value = $this->not_applicable_value;
break;
}
if ( empty( $redirection['id'] ) ) {
break;
}
$value = $redirection['url_to'];
break;
case 'redirect_type':
if ( 'user' === $object_type ) {
$value = $this->not_applicable_value;
break;
}
if ( empty( $redirection['id'] ) ) {
break;
}
$value = $redirection['header_code'];
break;
case 'internal_link_count':
case 'external_link_count':
case 'incoming_link_count':
$value = $this->not_applicable_value;
if ( isset( $internal_meta->$column ) ) {
$value = $internal_meta->$column;
}
break;
}
return $this->escape_csv( apply_filters( "rank_math/admin/csv_export_column_{$column}", $value, $object ) ); //phpcs:ignore
}
/**
* Get redirection for object.
*
* @param string $object_type Object type (post/term).
* @param int $object_id Object ID.
* @return array
*/
public function get_redirection( $object_type, $object_id ) {
if ( isset( $this->redirection[ $object_id ] ) ) {
return $this->redirection[ $object_id ];
}
$url = 'term' === $object_type ? get_term_link( (int) $object_id ) : get_permalink( $object_id );
$url = wp_parse_url( $url, PHP_URL_PATH );
$url = trim( $url, '/' );
$redirection = Cache::get_by_object_id( $object_id, $object_type );
$redirection = $redirection ? DB::get_redirection_by_id( $redirection->redirection_id, 'active' ) : [
'id' => '',
'url_to' => '',
'header_code' => Helper::get_settings( 'general.redirections_header_code' ),
];
$this->redirection = [ $object_id => $redirection ];
return $redirection;
}
/**
* From DB format to key => value.
*
* @param array $rows Meta data rows from DB.
* @return array
*/
public function process_meta_rows( $rows ) {
$out = [];
foreach ( $rows as $meta ) {
$out[ $meta->meta_key ] = $meta->meta_value;
}
return $out;
}
/**
* From DB format to CSV compatible.
*
* @param array $meta Robots meta value from DB.
* @return string
*/
public function process_robots( $meta ) {
$meta = maybe_unserialize( $meta );
return join( ',', $meta );
}
/**
* From DB format to CSV compatible.
*
* @param array $meta Robots meta value from DB.
* @return string
*/
public function process_advanced_robots( $meta ) {
$meta = maybe_unserialize( $meta );
return http_build_query( $meta, '', ', ' );
}
/**
* From DB format to JSON-encoded.
*
* @param array $metadata Schema data meta value from DB.
* @return string
*/
public function process_schema_data( $metadata ) {
$output = [];
$schema_data = $this->filter_schema_meta( $metadata );
if ( empty( $schema_data ) ) {
return '';
}
foreach ( $schema_data as $meta_key => $meta_value ) {
$name = substr( $meta_key, 17 );
$meta_value = maybe_unserialize( $meta_value );
if ( $name ) {
$output[ $name ] = $meta_value;
}
}
return wp_json_encode( $output, JSON_UNESCAPED_SLASHES );
}
/**
* Get all the rank_math_schema_* post meta values from all the values.
*
* @param array $metadata Schema data meta value from DB.
* @return array
*/
private function filter_schema_meta( $metadata ) {
$found = [];
foreach ( $metadata as $meta_key => $meta_value ) {
if ( substr( $meta_key, 0, 17 ) === 'rank_math_schema_' ) {
$found[ $meta_key ] = $meta_value;
}
}
return $found;
}
/**
* Get primary term for given object.
*
* @param mixed $meta Processed meta data.
* @return string
*/
public function get_primary_term( $meta ) {
if ( empty( $meta['rank_math_primary_category'] ) ) {
return '';
}
return $this->get_term_slug( $meta['rank_math_primary_category'] );
}
/**
* Get all post IDs.
*
* @return array
*/
public function get_post_ids() {
global $wpdb;
$where = $this->get_posts_where();
$post_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE $where" ); // phpcs:ignore
return $post_ids;
}
/**
* Get all term IDs.
*
* @return array
*/
public function get_term_ids() {
global $wpdb;
$taxonomies = Helper::get_allowed_taxonomies();
if ( $this->use_advanced_options ) {
if ( empty( $this->advanced_options['taxonomies'] ) ) {
return [];
}
$taxonomies = $this->advanced_options['taxonomies'];
}
$term_ids = get_terms(
[
'taxonomy' => $taxonomies,
'fields' => 'ids',
'hide_empty' => false,
]
);
return $term_ids;
}
/**
* Get all user IDs.
*
* @return array
*/
public function get_user_ids() {
$args = [ 'fields' => [ 'ID' ] ];
if ( $this->use_advanced_options ) {
if ( empty( $this->advanced_options['roles'] ) ) {
return [];
}
$args['role__in'] = $this->advanced_options['roles'];
}
$user_ids = get_users( $args );
return wp_list_pluck( $user_ids, 'ID' );
}
/**
* Export all items of specified object type. Output column values.
*
* @param string $object_type Object type to export.
* @return array
*/
public function get_objects( $object_type ) {
global $wpdb;
$object_type_plural = $object_type . 's';
// get_post_ids, get_term_ids, get_user_ids.
$method = "get_{$object_type}_ids";
$ids = $this->$method();
if ( ! $ids ) {
return [];
}
$primary_column = 'ID';
if ( 'term' === $object_type ) {
$primary_column = "{$object_type}_id";
}
$cols = $this->columns;
// Fetch 50 at a time rather than loading the entire table into memory.
while ( $next_batch = array_splice( $ids, 0, 50 ) ) { // phpcs:ignore
$where = 'WHERE ' . $primary_column . ' IN (' . join( ',', $next_batch ) . ')';
$objects = $wpdb->get_results( "SELECT * FROM {$wpdb->$object_type_plural} $where" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$current_object = 0;
// Begin Loop.
foreach ( $objects as $object ) {
$current_object++;
$current_col = 0;
$columns = [];
foreach ( $cols as $column ) {
$current_col++;
$columns[] = $this->get_column_value( $column, $object ); // phpcs:ignore
}
$this->data[] = $columns;
}
}
return $this->data;
}
/**
* Get WHERE for post types.
*
* @return string
*/
private function get_posts_where() {
global $wpdb;
$post_types = Helper::get_allowed_post_types();
if ( $this->use_advanced_options ) {
if ( empty( $this->advanced_options['post_types'] ) ) {
return [];
}
$post_types = $this->advanced_options['post_types'];
}
$esses = array_fill( 0, count( $post_types ), '%s' );
$where = $wpdb->prepare( "{$wpdb->posts}.post_type IN (" . implode( ',', $esses ) . ')', $post_types ); // phpcs:ignore
$where .= " AND {$wpdb->posts}.post_status != 'auto-draft'";
return $where;
}
/**
* Get slug from term ID.
*
* @param int $term_id Term ID.
* @return string
*/
public function get_term_slug( $term_id ) {
if ( isset( $this->term_slugs[ $term_id ] ) ) {
return $this->term_slugs[ $term_id ];
}
global $wpdb;
$where = 'term_id = ' . absint( $term_id ) . '';
$this->term_slugs[ $term_id ] = $wpdb->get_var( "SELECT slug FROM {$wpdb->terms} WHERE $where" ); // phpcs:ignore
return $this->term_slugs[ $term_id ];
}
/**
* Add read-only columns.
*
* @param array $columns Columns.
* @return array
*/
public function add_readonly_columns( $columns ) {
$columns[] = 'seo_score';
if ( $this->needs_link_count ) {
$columns[] = 'internal_link_count';
$columns[] = 'external_link_count';
$columns[] = 'incoming_link_count';
}
return $columns;
}
/**
* Get post link counts.
*
* @param int $post_id Post ID.
*
* @return object
*/
public function get_link_counts( $post_id ) {
global $wpdb;
$counts = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}rank_math_internal_meta WHERE object_id = {$post_id}" ); // phpcs:ignore
$counts = ! empty( $counts ) ? $counts : (object) [
'internal_link_count' => '',
'external_link_count' => '',
'incoming_link_count' => '',
];
return $counts;
}
}

View File

@@ -0,0 +1,170 @@
<?php
/**
* The CSV Import class.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin\CSV_Import_Export;
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
/**
* CSV Import Export class.
*
* @codeCoverageIgnore
*/
class Import_Background_Process extends \WP_Background_Process {
/**
* Prefix.
*
* (default value: 'wp')
*
* @var string
* @access protected
*/
protected $prefix = 'rank_math';
/**
* Action.
*
* @var string
*/
protected $action = 'csv_import';
/**
* Importer instance.
*
* @var Importer
*/
private $importer;
/**
* Main instance.
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Import_Background_Process
*/
public static function get() {
static $instance;
if ( is_null( $instance ) || ! ( $instance instanceof Import_Background_Process ) ) {
$instance = new Import_Background_Process();
}
return $instance;
}
/**
* Start creating batches.
*
* @param [type] $posts [description].
*/
public function start( $lines_number ) {
$chunks = array_chunk( range( 0, $lines_number ), apply_filters( 'rank_math/admin/csv_import_chunk_size', 100 ) );
foreach ( $chunks as $chunk ) {
$this->push_to_queue( $chunk );
}
Helper::add_notification(
sprintf(
// Translators: placeholders are opening and closing tags for link.
__( 'CSV import in progress. You can see its progress and cancel it in the %1$sImport & Export panel%2$s.', 'rank-math-pro' ),
'<a href="' . esc_url( Helper::get_admin_url( 'status', [ 'view' => 'import_export' ] ) ) . '">',
'</a>'
),
[
'type' => 'success',
'classes' => 'is-dismissible',
]
);
$this->save()->dispatch();
}
/**
* Task.
*
* Override this method to perform any actions required on each
* queue item. Return the modified item for further processing
* in the next pass through. Or, return false to remove the
* item from the queue.
*
* @param mixed $item Queue item to iterate over.
*
* @return mixed
*/
protected function task( $item ) {
try {
$this->importer = new Importer();
foreach ( $item as $row ) {
$this->importer->import_line( $row );
}
$this->importer->batch_done( $item );
return false;
} catch ( \Exception $error ) {
return true;
}
}
/**
* Import complete. Clear options & add notification.
*
* @return void
*/
protected function complete() {
unlink( get_option( 'rank_math_csv_import' ) );
delete_option( 'rank_math_csv_import' );
delete_option( 'rank_math_csv_import_total' );
delete_option( 'rank_math_csv_import_settings' );
$status = (array) get_option( 'rank_math_csv_import_status', [] );
$notification_args = [
'type' => 'success',
'classes' => 'is-dismissible',
];
if ( ! empty( $status['errors'] ) ) {
$notification_args = [
'type' => 'error',
'classes' => 'is-dismissible',
];
}
Helper::add_notification(
CSV_Import_Export::get_import_complete_message(),
$notification_args
);
parent::complete();
}
/**
* Count remaining items in batch.
*
* @return int
*/
public function count_remaining_items() {
if ( $this->is_queue_empty() ) {
// This fixes an issue where get_batch() runs too early and results in a PHP notice.
return get_option( 'rank_math_csv_import_total' );
}
$batch = $this->get_batch();
$count = 0;
if ( ! empty( $batch->data ) && is_array( $batch->data ) ) {
foreach ( $batch->data as $items ) {
$count += count( $items );
}
}
return $count;
}
}

View File

@@ -0,0 +1,776 @@
<?php
/**
* The CSV Import class.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin\CSV_Import_Export;
use RankMath\Helper;
use RankMath\Redirections\DB;
use RankMath\Redirections\Cache;
use RankMath\Redirections\Redirection;
use MyThemeShop\Helpers\Arr;
defined( 'ABSPATH' ) || exit;
/**
* CSV Importer class.
*
* @codeCoverageIgnore
*/
class Import_Row {
/**
* Row data.
*
* @var array
*/
private $data = [];
/**
* Import settings.
*
* @var array
*/
private $settings = [];
/**
* Columns.
*
* @var array
*/
private $columns = [];
/**
* Redirection.
*
* @var array
*/
private $redirection = [];
/**
* Object URI.
*
* @var string
*/
private $object_uri = '';
/**
* Constructor.
*
* @param array $data Row data.
* @param array $settings Import settings.
* @return void
*/
public function __construct( $data, $settings ) {
$this->data = $data;
$this->settings = $settings;
foreach ( $this->data as $key => $value ) {
// Skip empty or n/a.
if ( empty( $value ) || $this->is_not_applicable( $value ) ) {
continue;
}
$clear_method = "clear_{$key}";
if ( $this->is_clear_command( $value ) && method_exists( $this, $clear_method ) ) {
$this->$clear_method();
continue;
}
if ( $this->settings['no_overwrite'] ) {
$is_empty_method = "is_empty_{$key}";
if ( ! method_exists( $this, $is_empty_method ) || ! $this->$is_empty_method() ) {
continue;
}
}
$import_method = "import_{$key}";
if ( method_exists( $this, $import_method ) ) {
$this->$import_method( $value );
}
}
/**
* Do custom action after importing a row.
*/
do_action( 'rank_math/admin/csv_import_row', $data, $settings, $this );
}
/**
* Check if given column value is empty or not applicable.
*
* @param mixed $value Column value.
* @return bool
*/
public function is_not_applicable( $value ) {
return $value === $this->settings['not_applicable_value'];
}
/**
* Check if given column value is the delete command.
*
* @param mixed $value Column value.
* @return bool
*/
public function is_clear_command( $value ) {
return $value === $this->settings['clear_command'];
}
/**
* Magic getter.
*
* Return column value if is set and column name is in allowed columns list.
*
* @param string $property Property we want to get.
* @return string
*/
public function __get( $property ) {
if ( in_array( $property, $this->get_columns(), true ) && isset( $this->data[ $property ] ) ) {
return $this->data[ $property ];
}
return '';
}
/**
* Get CSV columns.
*
* @return array
*/
public function get_columns() {
if ( ! empty( $this->columns ) ) {
return $this->columns;
}
$this->columns = CSV_Import_Export::get_columns();
return $this->columns;
}
/**
* Clear SEO Title column.
*
* @return void
*/
public function clear_seo_title() {
$this->delete_meta( 'title' );
}
/**
* Clear SEO Description column.
*
* @return void
*/
public function clear_seo_description() {
$this->delete_meta( 'description' );
}
/**
* Clear Focus Keyword column.
*
* @return void
*/
public function clear_focus_keyword() {
$this->delete_meta( 'focus_keyword' );
}
/**
* Clear Robots column.
*
* @return void
*/
public function clear_robots() {
$this->delete_meta( 'robots' );
}
/**
* Clear Advanced Robots column.
*
* @return void
*/
public function clear_advanced_robots() {
$this->delete_meta( 'advanced_robots' );
}
/**
* Clear Canonical URL column.
*
* @return void
*/
public function clear_canonical_url() {
$this->delete_meta( 'canonical_url' );
}
/**
* Clear Primary Term column.
*
* @return void
*/
public function clear_primary_term() {
$this->delete_meta( 'primary_category' );
}
/**
* Clear Schema Data column. Schema data must be valid JSON.
*
* @return void
*/
public function clear_schema_data() {
$current_meta = $this->get_meta();
foreach ( $current_meta as $key => $value ) {
if ( substr( $key, 0, 17 ) === 'rank_math_schema_' ) {
// Cut off "rank_math_" prefix.
$this->delete_meta( substr( $key, 10 ) );
}
}
}
/**
* Clear FB Thumbnail column.
*
* @return void
*/
public function clear_social_facebook_thumbnail() {
$this->delete_meta( 'facebook_image' );
}
/**
* Clear FB Title column.
*
* @return void
*/
public function clear_social_facebook_title() {
$this->delete_meta( 'facebook_title' );
}
/**
* Clear FB Description column.
*
* @return void
*/
public function clear_social_facebook_description() {
$this->delete_meta( 'facebook_description' );
}
/**
* Clear Twitter Thumbnail column.
*
* @return void
*/
public function clear_social_twitter_thumbnail() {
$this->delete_meta( 'twitter_image' );
}
/**
* Clear Twitter Title column.
*
* @return void
*/
public function clear_social_twitter_title() {
$this->delete_meta( 'twitter_title' );
}
/**
* Clear Twitter Description column.
*
* @return void
*/
public function clear_social_twitter_description() {
$this->delete_meta( 'twitter_description' );
}
/**
* Clear Redirection URL column. Only if 'redirect_type' column is set, too.
*
* @return void
*/
public function clear_redirect_to() {
if ( ! $this->is_empty_redirect_to() ) {
DB::delete( $this->get_redirection()['id'] );
}
}
/**
* Import slug column.
*
* @param string $value Column value.
* @return void
*/
public function import_slug( $value ) {
switch ( $this->object_type ) {
case 'post':
wp_update_post(
[
'ID' => $this->id,
'post_name' => $value,
]
);
break;
case 'term':
global $wpdb;
$wpdb->update(
$wpdb->terms,
[ 'slug' => sanitize_title( $value ) ], // Update.
[ 'term_id' => sanitize_title( $value ) ], // Where.
[ '%s' ], // Format.
[ '%d' ] // Where format.
);
break;
case 'user':
update_user_meta( $this->id, 'rank_math_permalink', $value );
break;
}
// Refresh URI.
$this->get_object_uri( true );
}
/**
* Import SEO Title column.
*
* @param string $value Column value.
* @return void
*/
public function import_seo_title( $value ) {
$this->update_meta( 'title', $value );
}
/**
* Import SEO Description column.
*
* @param string $value Column value.
* @return void
*/
public function import_seo_description( $value ) {
$this->update_meta( 'description', $value );
}
/**
* Import Is Pillar Content column.
*
* @param string $value Column value.
* @return void
*/
public function import_is_pillar_content( $value ) {
$lcvalue = strtolower( $value );
if ( 'yes' === $lcvalue ) {
$this->update_meta( 'pillar_content', 'on' );
} elseif ( 'no' === $lcvalue ) {
$this->delete_meta( 'pillar_content' );
}
}
/**
* Import Focus Keyword column.
*
* @param string $value Column value.
* @return void
*/
public function import_focus_keyword( $value ) {
$this->update_meta( 'focus_keyword', $value );
}
/**
* Import Robots column.
*
* @param string $value Column value.
* @return void
*/
public function import_robots( $value ) {
$this->update_meta( 'robots', Arr::from_string( $value ) );
}
/**
* Import Advanced Robots column.
*
* @param string $value Column value.
* @return void
*/
public function import_advanced_robots( $value ) {
$robots = [];
$robots_rules = Arr::from_string( $value );
foreach ( $robots_rules as $robots_rule ) {
$parts = Arr::from_string( $robots_rule, '=' );
if ( count( $parts ) === 2 ) {
$robots[ $parts[0] ] = $parts[1];
}
}
$this->update_meta( 'advanced_robots', $robots );
}
/**
* Import Canonical URL column.
*
* @param string $value Column value.
* @return void
*/
public function import_canonical_url( $value ) {
$this->update_meta( 'canonical_url', $value );
}
/**
* Import Primary Term column.
*
* @param string $value Column value.
* @return void
*/
public function import_primary_term( $value ) {
$term_id = Importer::get_term_id( $value );
if ( ! $term_id ) {
return;
}
$this->update_meta( 'primary_category', $term_id );
}
/**
* Import Schema Data column. Schema data must be valid JSON.
*
* @param string $value Column value.
* @return void
*/
public function import_schema_data( $value ) {
$value = json_decode( $value, true );
if ( ! $value ) {
return;
}
foreach ( $value as $key => $value ) {
$meta_key = 'schema_' . $key;
$this->update_meta( $meta_key, $value );
}
}
/**
* Import FB Thumbnail column.
*
* @param string $value Column value.
* @return void
*/
public function import_social_facebook_thumbnail( $value ) {
$this->update_meta( 'facebook_image', $value );
}
/**
* Import FB Title column.
*
* @param string $value Column value.
* @return void
*/
public function import_social_facebook_title( $value ) {
$this->update_meta( 'facebook_title', $value );
}
/**
* Import FB Description column.
*
* @param string $value Column value.
* @return void
*/
public function import_social_facebook_description( $value ) {
$this->update_meta( 'facebook_description', $value );
}
/**
* Import Twitter Thumbnail column.
*
* @param string $value Column value.
* @return void
*/
public function import_social_twitter_thumbnail( $value ) {
$this->update_meta( 'twitter_image', $value );
$this->update_meta( 'twitter_use_facebook', 'off' );
}
/**
* Import Twitter Title column.
*
* @param string $value Column value.
* @return void
*/
public function import_social_twitter_title( $value ) {
$this->update_meta( 'twitter_title', $value );
$this->update_meta( 'twitter_use_facebook', 'off' );
}
/**
* Import Twitter Description column.
*
* @param string $value Column value.
* @return void
*/
public function import_social_twitter_description( $value ) {
$this->update_meta( 'twitter_description', $value );
$this->update_meta( 'twitter_use_facebook', 'off' );
}
/**
* Import Redirection URL column. Only if 'redirect_type' column is set, too.
*
* @param string $value Column value.
* @return void
*/
public function import_redirect_to( $value ) {
if ( empty( $this->data['redirect_type'] ) ) {
return;
}
if ( ! $this->is_empty_redirect_to() ) {
DB::delete( $this->get_redirection()['id'] );
}
$redirection = Redirection::from(
[
'id' => '',
'url_to' => $this->redirect_to,
'sources' => [
[
'pattern' => $this->get_object_uri(),
'comparison' => 'exact',
],
],
'header_code' => $this->redirect_type,
]
);
$redirection->save();
}
/**
* Check if empty: SEO Title
*
* @return bool
*/
public function is_empty_seo_title() {
return ! $this->get_meta( 'title' );
}
/**
* Check if empty: SEO Description
*
* @return bool
*/
public function is_empty_seo_description() {
return ! $this->get_meta( 'description' );
}
/**
* Check if empty: Is Pillar Content column.
* We return true so this will always be overwritten.
*
* @return bool
*/
public function is_empty_is_pillar_content() {
return true;
}
/**
* Check if empty: Focus Keyword column.
*
* @return bool
*/
public function is_empty_focus_keyword() {
return ! $this->get_meta( 'focus_keyword' );
}
/**
* Check if empty: Robots column.
*
* @return bool
*/
public function is_empty_robots() {
return empty( $this->get_meta( 'robots' ) );
}
/**
* Check if empty: Advanced Robots column.
*
* @return bool
*/
public function is_empty_advanced_robots() {
return empty( $this->get_meta( 'advanced_robots' ) );
}
/**
* Check if empty: Canonical URL column.
*
* @return bool
*/
public function is_empty_canonical_url() {
return ! $this->get_meta( 'canonical_url' );
}
/**
* Check if empty: Primary Term column.
*
* @return bool
*/
public function is_empty_primary_term() {
return empty( $this->get_meta( 'primary_category' ) );
}
/**
* Check if empty: Schema Data column.
* We return true so this will always be overwritten.
*
* @return bool
*/
public function is_empty_schema_data() {
return true;
}
/**
* Check if empty: FB Thumbnail column.
*
* @return bool
*/
public function is_empty_social_facebook_thumbnail() {
return ! $this->get_meta( 'facebook_image' );
}
/**
* Check if empty: FB Title column.
*
* @return bool
*/
public function is_empty_social_facebook_title() {
return ! $this->get_meta( 'facebook_title' );
}
/**
* Check if empty: FB Description column.
*
* @return bool
*/
public function is_empty_social_facebook_description() {
return ! $this->get_meta( 'facebook_description' );
}
/**
* Check if empty: Twitter Thumbnail column.
*
* @return bool
*/
public function is_empty_social_twitter_thumbnail() {
return ! $this->get_meta( 'twitter_image' );
}
/**
* Check if empty: Twitter Title column.
*
* @return bool
*/
public function is_empty_social_twitter_title() {
return ! $this->get_meta( 'twitter_title' );
}
/**
* Check if empty: Twitter Description column.
*
* @return bool
*/
public function is_empty_social_twitter_description() {
return ! $this->get_meta( 'twitter_description' );
}
/**
* Check if empty: Redirect URL.
*
* @return bool
*/
public function is_empty_redirect_to() {
return ! (bool) $this->get_redirection()['id'];
}
/**
* Get redirection for object.
*
* @return mixed
*/
public function get_redirection() {
if ( isset( $this->redirection ) ) {
return $this->redirection;
}
$object_type = $this->object_type;
$object_id = $this->id;
$this->get_object_uri();
$redirection = Cache::get_by_object_id( $object_id, $object_type );
$redirection = $redirection ? DB::get_redirection_by_id( $redirection->redirection_id, 'active' ) : [
'id' => '',
'url_to' => '',
'header_code' => Helper::get_settings( 'general.redirections_header_code' ),
];
$this->redirection = $redirection;
return $redirection;
}
/**
* Get object URI.
*
* @param bool $refresh Force refresh.
*
* @return string
*/
public function get_object_uri( $refresh = false ) {
if ( isset( $this->object_uri ) && ! $refresh ) {
return $this->object_uri;
}
$url = 'term' === $this->object_type ? get_term_link( (int) $this->id ) : get_permalink( $this->id );
if ( empty( $url ) || is_wp_error( $url ) ) {
return false;
}
$url = wp_parse_url( $url, PHP_URL_PATH );
$this->object_uri = trim( $url, '/' );
return $this->object_uri;
}
/**
* Update object meta.
*
* @param string $key Meta key.
* @param mixed $value Meta value.
* @return void
*/
public function update_meta( $key, $value ) {
$update_meta = "update_{$this->object_type}_meta";
$update_meta( $this->id, 'rank_math_' . $key, $value );
}
/**
* Get object meta.
*
* @param string $key Meta key.
* @return bool
*/
public function get_meta( $key = '' ) {
$get_meta = "get_{$this->object_type}_meta";
return $get_meta( $this->id, $key ? 'rank_math_' . $key : '', (bool) $key );
}
/**
* Delete object meta.
*
* @param string $key Meta key.
* @return void
*/
public function delete_meta( $key ) {
$delete_meta = "delete_{$this->object_type}_meta";
$delete_meta( $this->id, 'rank_math_' . $key );
}
}

View File

@@ -0,0 +1,358 @@
<?php
/**
* The CSV Import class.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Admin\CSV_Import_Export;
use MyThemeShop\Helpers\Arr;
defined( 'ABSPATH' ) || exit;
/**
* CSV Importer class.
*
* @codeCoverageIgnore
*/
class Importer {
/**
* Term slug => ID cache.
*
* @var array
*/
private static $term_ids = [];
/**
* Settings array. Default values.
*
* @var array
*/
private $settings = [
'not_applicable_value' => 'n/a',
'clear_command' => 'DELETE',
'no_overwrite' => true,
];
/**
* Lines in the CSV that could not be imported for any reason.
*
* @var array
*/
private $failed_rows = [];
/**
* Lines in the CSV that could be imported successfully.
*
* @var array
*/
private $imported_rows = [];
/**
* Error messages.
*
* @var array
*/
private $errors = [];
/**
* SplFileObject instance.
*
* @var \SplFileObject
*/
private $spl;
/**
* Column headers.
*
* @var array
*/
private $column_headers = [];
/**
* Constructor.
*
* @return void
*/
public function __construct() {
$this->load_settings();
}
/**
* Load settings.
*
* @return void
*/
public function load_settings() {
$this->settings = apply_filters( 'rank_math/admin/csv_import_settings', wp_parse_args( get_option( 'rank_math_csv_import_settings', [] ), $this->settings ) );
}
/**
* Start import from file.
*
* @param string $file Path to temporary CSV file.
* @param string $settings Import settings.
* @return void
*/
public function start( $file, $settings = [] ) {
update_option( 'rank_math_csv_import', $file );
update_option( 'rank_math_csv_import_settings', $settings );
delete_option( 'rank_math_csv_import_status' );
$this->load_settings();
$lines = $this->count_lines( $file );
update_option( 'rank_math_csv_import_total', $lines );
Import_Background_Process::get()->start( $lines );
}
/**
* Count all lines in CSV file.
*
* @param mixed $file Path to CSV.
* @return int
*/
public function count_lines( $file ) {
$file = new \SplFileObject( $file );
while ( $file->valid() ) {
$file->fgets();
}
$count = $file->key();
// Check if last line is empty.
$file->seek( $count );
$contents = $file->current();
if ( empty( trim( $contents ) ) ) {
$count--;
}
// Unlock file.
$file = null;
return $count;
}
/**
* Get specified line from CSV.
*
* @param string $file Path to file.
* @param int $line Line number.
* @return string
*/
public function get_line( $file, $line ) {
if ( empty( $this->spl ) ) {
$this->spl = new \SplFileObject( $file );
}
if ( ! $this->spl->eof() ) {
$this->spl->seek( $line );
$contents = $this->spl->current();
}
return $contents;
}
/**
* Parse and return column headers (first line in CSV).
*
* @param string $file Path to file.
* @return array
*/
public function get_column_headers( $file ) {
if ( ! empty( $this->column_headers ) ) {
return $this->column_headers;
}
if ( empty( $this->spl ) ) {
$this->spl = new \SplFileObject( $file );
}
if ( ! $this->spl->eof() ) {
$this->spl->seek( 0 );
$contents = $this->spl->current();
}
if ( empty( $contents ) ) {
return [];
}
$this->column_headers = Arr::from_string( $contents, apply_filters( 'rank_math/csv_import/separator', ',' ) );
return $this->column_headers;
}
/**
* Import specified line.
*
* @param int $line_number Selected line number.
* @return void
*/
public function import_line( $line_number ) {
// Skip headers.
if ( 0 === $line_number ) {
return;
}
$file = get_option( 'rank_math_csv_import' );
if ( ! $file ) {
$this->add_error( esc_html__( 'Missing import file.', 'rank-math-pro' ), 'missing_file' );
CSV_Import_Export::cancel_import( true );
return;
}
$headers = $this->get_column_headers( $file );
if ( empty( $headers ) ) {
$this->add_error( esc_html__( 'Missing CSV headers.', 'rank-math-pro' ), 'missing_headers' );
return;
}
$required_columns = [ 'id', 'object_type', 'slug' ];
if ( count( array_intersect( $headers, $required_columns ) ) !== count( $required_columns ) ) {
$this->add_error( esc_html__( 'Missing one or more required columns.', 'rank-math-pro' ), 'missing_required_columns' );
return;
}
$raw_data = $this->get_line( $file, $line_number );
if ( empty( $raw_data ) ) {
$total_lines = (int) get_option( 'rank_math_csv_import_total' );
// Last line can be empty, that is not an error.
if ( $line_number !== $total_lines ) {
$this->add_error( esc_html__( 'Empty column data.', 'rank-math-pro' ), 'missing_data' );
$this->row_failed( $line_number );
}
return;
}
$csv_separator = apply_filters( 'rank_math/csv_import/separator', ',' );
$decoded = str_getcsv( $raw_data, $csv_separator );
if ( count( $headers ) !== count( $decoded ) ) {
$this->add_error( esc_html__( 'Columns number mismatch.', 'rank-math-pro' ), 'columns_number_mismatch' );
$this->row_failed( $line_number );
return;
}
$data = array_combine( $headers, $decoded );
if ( ! in_array( $data['object_type'], array_keys( CSV_Import_Export::get_possible_object_types() ), true ) ) {
$this->add_error( esc_html__( 'Unknown object type.', 'rank-math-pro' ), 'unknown_object_type' );
$this->row_failed( $line_number );
return;
}
new Import_Row( $data, $this->settings );
$this->row_imported( $line_number );
}
/**
* Get term ID from slug.
*
* @param string $term_slug Term slug.
* @return int
*/
public static function get_term_id( $term_slug ) {
global $wpdb;
if ( ! empty( self::$term_ids[ $term_slug ] ) ) {
return self::$term_ids[ $term_slug ];
}
self::$term_ids[ $term_slug ] = $wpdb->get_var(
$wpdb->prepare( "SELECT term_id FROM {$wpdb->terms} WHERE slug = %s", $term_slug )
);
return self::$term_ids[ $term_slug ];
}
/**
* After each batch is finished.
*
* @param array $items Processed items.
*/
public function batch_done( $items ) { // phpcs:ignore
unset( $this->spl );
$status = (array) get_option( 'rank_math_csv_import_status', [] );
if ( ! isset( $status['errors'] ) || ! is_array( $status['errors'] ) ) {
$status['errors'] = [];
}
if ( ! isset( $status['failed_rows'] ) || ! is_array( $status['failed_rows'] ) ) {
$status['failed_rows'] = [];
}
if ( ! isset( $status['imported_rows'] ) || ! is_array( $status['imported_rows'] ) ) {
$status['imported_rows'] = [];
}
$status['imported_rows'] = array_merge( $status['imported_rows'], $this->get_imported_rows() );
$errors = $this->get_errors();
if ( $errors ) {
$status['errors'] = array_merge( $status['errors'], $errors );
$status['failed_rows'] = array_merge( $status['failed_rows'], $this->get_failed_rows() );
}
update_option( 'rank_math_csv_import_status', $status );
}
/**
* Set row import status.
*
* @param int $row Row index.
*/
private function row_failed( $row ) {
$this->failed_rows[] = $row + 1;
}
/**
* Set row import status.
*
* @param int $row Row index.
*/
private function row_imported( $row ) {
$this->imported_rows[] = $row + 1;
}
/**
* Get failed rows array.
*
* @return array
*/
private function get_failed_rows() {
return $this->failed_rows;
}
/**
* Get failed rows array.
*
* @return array
*/
private function get_imported_rows() {
return $this->imported_rows;
}
/**
* Get all import errors.
*
* @return mixed Array of errors or false if there is no error.
*/
public function get_errors() {
return empty( $this->errors ) ? false : $this->errors;
}
/**
* Add import error.
*
* @param string $message Error message.
* @param int $code Error code.
*/
public function add_error( $message, $code = null ) {
if ( is_null( $code ) ) {
$this->errors[] = $message;
return;
}
$this->errors[ $code ] = $message;
}
}

View File

@@ -0,0 +1 @@
<?php // Silence is golden.

View File

@@ -0,0 +1,318 @@
<?php
/**
* The Sitemap wizard step
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Sitemap
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Wizard;
use RankMath\KB;
use RankMath\Helper;
use RankMath\Wizard\Wizard_Step;
defined( 'ABSPATH' ) || exit;
/**
* Step class.
*/
class Sitemap implements Wizard_Step {
/**
* Render step body.
*
* @param object $wizard Wizard class instance.
*
* @return void
*/
public function render( $wizard ) {
?>
<header>
<h1><?php esc_html_e( 'Sitemap', 'rank-math-pro' ); ?> </h1>
<p>
<?php
/* translators: Link to How to Setup Sitemap KB article */
printf( esc_html__( 'Choose your Sitemap configuration and select which type of posts or pages you want to include in your Sitemaps. %s', 'rank-math-pro' ), '<a href="' . KB::get( 'configure-sitemaps', 'SW Sitemap Step' ) . '" target="_blank">' . esc_html__( 'Learn more.', 'rank-math-pro' ) . '</a>' );
?>
</p>
</header>
<?php $wizard->cmb->show_form(); ?>
<footer class="form-footer wp-core-ui rank-math-ui">
<?php $wizard->get_skip_link(); ?>
<button type="submit" class="button button-primary"><?php esc_html_e( 'Save and Continue', 'rank-math-pro' ); ?></button>
</footer>
<?php
}
/**
* Render form for step.
*
* @param object $wizard Wizard class instance.
*
* @return void
*/
public function form( $wizard ) {
$wizard->cmb->add_field(
[
'id' => 'sitemap',
'type' => 'toggle',
'name' => esc_html__( 'Sitemaps', 'rank-math-pro' ),
'desc' => esc_html__( 'XML Sitemaps help search engines index your website&#039;s content more effectively.', 'rank-math-pro' ),
'default' => Helper::is_module_active( 'sitemap' ) ? 'on' : 'off',
]
);
$wizard->cmb->add_field(
[
'id' => 'include_images',
'type' => 'toggle',
'name' => esc_html__( 'Include Images', 'rank-math-pro' ),
'desc' => esc_html__( 'Include reference to images from the post content in sitemaps. This helps search engines index your images better.', 'rank-math-pro' ),
'default' => Helper::get_settings( 'sitemap.include_images' ) ? 'on' : 'off',
'classes' => 'features-child',
'dep' => [ [ 'sitemap', 'on' ] ],
]
);
// Post Types.
$post_types = $this->get_post_types();
$wizard->cmb->add_field(
[
'id' => 'sitemap_post_types',
'type' => 'multicheck',
'name' => esc_html__( 'Public Post Types', 'rank-math-pro' ),
'desc' => esc_html__( 'Select post types to enable SEO options for them and include them in the sitemap.', 'rank-math-pro' ),
'options' => $post_types['post_types'],
'default' => $post_types['defaults'],
'classes' => 'features-child cmb-multicheck-inline' . ( count( $post_types['post_types'] ) === count( $post_types['defaults'] ) ? ' multicheck-checked' : '' ),
'dep' => [ [ 'sitemap', 'on' ] ],
]
);
// Taxonomies.
$taxonomies = $this->get_taxonomies();
$wizard->cmb->add_field(
[
'id' => 'sitemap_taxonomies',
'type' => 'multicheck',
'name' => esc_html__( 'Public Taxonomies', 'rank-math-pro' ),
'desc' => esc_html__( 'Select taxonomies to enable SEO options for them and include them in the sitemap.', 'rank-math-pro' ),
'options' => $taxonomies['taxonomies'],
'default' => $taxonomies['defaults'],
'classes' => 'features-child cmb-multicheck-inline' . ( count( $taxonomies['taxonomies'] ) === count( $taxonomies['defaults'] ) ? ' multicheck-checked' : '' ),
'dep' => [ [ 'sitemap', 'on' ] ],
]
);
$news_sitemap_dep = [ 'relation' => 'and' ] + [ [ 'sitemap', 'on' ] ];
$news_sitemap_dep[] = [ 'news-sitemap', 'on' ];
$wizard->cmb->add_field(
[
'id' => 'news_sitemap_title',
'type' => 'raw',
'content' => sprintf( '<br><br><div class="cmb-form cmb-row nopb"><header class="sitemap-title"><h1>%1$s</h1><p>%2$s</p></header><div class="rank-math-cmb-dependency hidden" data-relation="or"><span class="hidden" data-field="sitemap" data-comparison="=" data-value="on"></span></div></div>', esc_html__( 'News Sitemap', 'rank-math-pro' ), esc_html__( 'News Sitemaps allow you to control which content you submit to Google News.', 'rank-math-pro' ) ),
]
);
$wizard->cmb->add_field(
[
'id' => 'news-sitemap',
'type' => 'toggle',
'name' => esc_html__( 'News Sitemaps', 'rank-math-pro' ),
'desc' => esc_html__( 'You will generally only need a News Sitemap when your website is included in Google News.', 'rank-math-pro' ),
'default' => Helper::is_module_active( 'news-sitemap' ) ? 'on' : 'off',
'dep' => [ [ 'sitemap', 'on' ] ],
]
);
$wizard->cmb->add_field(
[
'id' => 'news_sitemap_publication_name',
'type' => 'text',
'name' => esc_html__( 'Google News Publication Name', 'rank-math-pro' ),
'classes' => 'features-child cmb-multicheck-inline',
'desc' => wp_kses_post( __( 'The name of the news publication. It must match the name exactly as it appears on your articles in news.google.com, omitting any trailing parentheticals. <a href="https://support.google.com/news/publisher-center/answer/9606710" target="_blank">More information at support.google.com</a>', 'rank-math-pro' ) ),
'default' => Helper::get_settings( 'sitemap.news_sitemap_publication_name' ),
'dep' => $news_sitemap_dep,
]
);
$wizard->cmb->add_field(
[
'id' => 'news_sitemap_post_type',
'type' => 'multicheck_inline',
'name' => esc_html__( 'News Post Type', 'rank-math-pro' ),
'desc' => esc_html__( 'Select the post type you use for News articles.', 'rank-math-pro' ),
'classes' => 'features-child cmb-multicheck-inline',
'options' => $post_types['post_types'],
'default' => Helper::get_settings( 'sitemap.news_sitemap_post_type' ),
'dep' => $news_sitemap_dep,
]
);
$wizard->cmb->add_field(
[
'id' => 'video_sitemap_title',
'type' => 'raw',
'content' => sprintf( '<br><br><div class="cmb-form cmb-row nopb"><header class="sitemap-title"><h1>%1$s</h1><p>%2$s</p></header><div class="rank-math-cmb-dependency hidden" data-relation="or"><span class="hidden" data-field="sitemap" data-comparison="=" data-value="on"></span></div></div>', esc_html__( 'Video Sitemap', 'rank-math-pro' ), esc_html__( 'Video Sitemaps give search engines information about video content on your site.', 'rank-math-pro' ) ),
]
);
$wizard->cmb->add_field(
[
'id' => 'video-sitemap',
'type' => 'toggle',
'name' => esc_html__( 'Video Sitemaps', 'rank-math-pro' ),
'desc' => esc_html__( 'You will generally only need a Video Sitemap when your website has video content.', 'rank-math-pro' ),
'default' => Helper::is_module_active( 'video-sitemap' ) ? 'on' : 'off',
'dep' => [ [ 'sitemap', 'on' ] ],
]
);
$wizard->cmb->add_field(
[
'id' => 'video_sitemap_post_type',
'type' => 'multicheck_inline',
'name' => esc_html__( 'Video Post Type', 'rank-math-pro' ),
'desc' => esc_html__( 'Select the post type where you use videos and want them to be shown in the Video search.', 'rank-math-pro' ),
'classes' => 'features-child cmb-multicheck-inline',
'options' => $post_types['post_types'],
'default' => Helper::get_settings( 'sitemap.video_sitemap_post_type', array_keys( $post_types['post_types'] ) ),
'dep' => [ 'relation' => 'and' ] + [ [ 'sitemap', 'on' ], [ 'video-sitemap', 'on' ] ],
]
);
}
/**
* Save handler for step.
*
* @param array $values Values to save.
* @param object $wizard Wizard class instance.
*
* @return bool
*/
public function save( $values, $wizard ) {
$settings = rank_math()->settings->all_raw();
Helper::update_modules( [ 'sitemap' => $values['sitemap'] ] );
Helper::update_modules( [ 'news-sitemap' => $values['news-sitemap'] ] );
Helper::update_modules( [ 'video-sitemap' => $values['video-sitemap'] ] );
if ( 'on' === $values['sitemap'] ) {
$settings['sitemap']['include_images'] = $values['include_images'];
$settings = $this->save_post_types( $settings, $values );
$settings = $this->save_taxonomies( $settings, $values );
Helper::update_all_settings( null, null, $settings['sitemap'] );
}
if ( 'on' === $values['news-sitemap'] ) {
$settings['sitemap']['news_sitemap_publication_name'] = ! empty( $values['news_sitemap_publication_name'] ) ? $values['news_sitemap_publication_name'] : '';
$settings['sitemap']['news_sitemap_post_type'] = ! empty( $values['news_sitemap_post_type'] ) ? $values['news_sitemap_post_type'] : [];
Helper::update_all_settings( null, null, $settings['sitemap'] );
}
if ( 'on' === $values['video-sitemap'] ) {
$settings['sitemap']['video_sitemap_post_type'] = ! empty( $values['video_sitemap_post_type'] ) ? $values['video_sitemap_post_type'] : [];
Helper::update_all_settings( null, null, $settings['sitemap'] );
}
Helper::schedule_flush_rewrite();
return true;
}
/**
* Get post type data.
*
* @return array
*/
private function get_post_types() {
$p_defaults = [];
$post_types = Helper::choices_post_types();
if ( Helper::get_settings( 'general.attachment_redirect_urls', true ) ) {
unset( $post_types['attachment'] );
}
foreach ( $post_types as $post_type => $object ) {
if ( true === Helper::get_settings( "sitemap.pt_{$post_type}_sitemap" ) ) {
$p_defaults[] = $post_type;
}
}
return [
'defaults' => $p_defaults,
'post_types' => $post_types,
];
}
/**
* Get taxonomies data.
*
* @return array
*/
private function get_taxonomies() {
$t_defaults = [];
$taxonomies = Helper::get_accessible_taxonomies();
unset( $taxonomies['post_tag'], $taxonomies['post_format'], $taxonomies['product_tag'] );
$taxonomies = wp_list_pluck( $taxonomies, 'label', 'name' );
foreach ( $taxonomies as $taxonomy => $label ) {
if ( true === Helper::get_settings( "sitemap.tax_{$taxonomy}_sitemap" ) ) {
$t_defaults[] = $taxonomy;
}
}
return [
'defaults' => $t_defaults,
'taxonomies' => $taxonomies,
];
}
/**
* Save Post Types
*
* @param array $settings Array of all settings.
* @param array $values Array of posted values.
*
* @return array
*/
private function save_post_types( $settings, $values ) {
$post_types = Helper::choices_post_types();
if ( ! isset( $values['sitemap_post_types'] ) ) {
$values['sitemap_post_types'] = [];
}
foreach ( $post_types as $post_type => $object ) {
$settings['sitemap'][ "pt_{$post_type}_sitemap" ] = in_array( $post_type, $values['sitemap_post_types'], true ) ? 'on' : 'off';
}
return $settings;
}
/**
* Save Taxonomies
*
* @param array $settings Array of all settings.
* @param array $values Array of posted values.
*
* @return array
*/
private function save_taxonomies( $settings, $values ) {
$taxonomies = Helper::get_accessible_taxonomies();
$taxonomies = wp_list_pluck( $taxonomies, 'label', 'name' );
if ( ! isset( $values['sitemap_taxonomies'] ) ) {
$values['sitemap_taxonomies'] = [];
}
foreach ( $taxonomies as $taxonomy => $label ) {
$settings['sitemap'][ "tax_{$taxonomy}_sitemap" ] = in_array( $taxonomy, $values['sitemap_taxonomies'], true ) ? 'on' : 'off';
}
return $settings;
}
}

View File

@@ -0,0 +1,217 @@
<?php
/**
* Miscellaneous functions.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Admin\Admin_Helper as PROAdminHelper;
use MyThemeShop\Helpers\Url;
use MyThemeShop\Helpers\Str;
use MyThemeShop\Helpers\Arr;
use MyThemeShop\Helpers\Conditional;
defined( 'ABSPATH' ) || exit;
/**
* Admin class.
*
* @codeCoverageIgnore
*/
class Common {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$this->action( 'rank_math/admin_bar/items', 'add_admin_bar_items' );
$this->filter( 'rank_math/focus_keyword/maxtags', 'limit_maxtags', 5 );
$this->filter( 'rank_math/metabox/values', 'add_json_data' );
$this->filter( 'wp_helpers_is_affiliate_link', 'is_affiliate_link', 10, 2 );
$this->filter( 'rank_math/link/add_attributes', 'can_add_attributes' );
$this->filter( 'rank_math/researches/tests', 'add_product_tests', 10, 2 );
$this->action( 'rank_math/admin/editor_scripts', 'enqueue' );
}
/**
* Increase the focus keyword max tags.
*
* @param int $limit The max tags limit.
*/
public function limit_maxtags( $limit ) {
return 100;
}
/**
* Add Pinterest Rich Pins Validator to the top admin bar.
*
* @param object $object The Admin_Bar_Menu object.
*/
public function add_admin_bar_items( $object ) {
$url = rawurlencode( Url::get_current_url() );
$object->add_sub_menu(
'rich-pins',
[
'title' => esc_html__( 'Rich Pins Validator', 'rank-math-pro' ),
'href' => 'https://developers.pinterest.com/tools/url-debugger/?link=' . $url,
'meta' => [
'title' => esc_html__( 'Pinterest Debugger', 'rank-math-pro' ),
'target' => '_blank',
],
],
'third-party'
);
}
/**
* Add settings in the Advanced tab of the metabox.
*
* @param array $values Localized data.
*/
public function add_json_data( $values ) {
if ( ! Helper::is_site_connected() ) {
$values['trendsIcon'] = $this->get_icon_svg();
$values['trendsUpgradeLink'] = esc_url_raw( admin_url( 'admin.php?page=rank-math&view=help' ) );
$values['trendsUpgradeLabel'] = esc_html__( 'Activate now', 'rank-math-pro' );
}
if ( Conditional::is_woocommerce_active() && 'product' === PROAdminHelper::get_current_post_type() ) {
$values['assessor']['isReviewEnabled'] = 'yes' === get_option( 'woocommerce_enable_reviews', 'yes' );
}
return $values;
}
/**
* Checks whether a link is an affiliate link.
*
* @param string $is_affiliate Is affiliate link.
* @param string $url Anchor link.
*
* @return string
*/
public function is_affiliate_link( $is_affiliate, $url ) {
$url = str_replace( home_url(), '', $url );
$prefixes = Arr::from_string( Helper::get_settings( 'general.affiliate_link_prefixes' ), "\n" );
if ( empty( $url ) || empty( $prefixes ) ) {
return $is_affiliate;
}
foreach ( $prefixes as $prefix ) {
if ( Str::starts_with( $prefix, trim( $url ) ) ) {
$is_affiliate = true;
break;
}
}
return $is_affiliate;
}
/**
* Run a fucntion to add sponsored rel tag to the affiliate links.
*
* @param bool $value Whether to run the function to add link attributes.
*/
public function can_add_attributes( $value ) {
$prefixes = Arr::from_string( Helper::get_settings( 'general.affiliate_link_prefixes' ), "\n" );
return ! empty( $prefixes ) ? true : $value;
}
/**
* Remove few tests on static Homepage.
*
* @since 3.0.7
*
* @param array $tests Array of tests with score.
* @param string $type Object type. Can be post, user or term.
*/
public function add_product_tests( $tests, $type ) {
if ( 'post' !== $type ) {
return $tests;
}
$post_type = PROAdminHelper::get_current_post_type();
$is_woocommerce = Conditional::is_woocommerce_active() && 'product' === $post_type;
$is_edd = Conditional::is_edd_active() && 'download' === $post_type;
if ( ! $is_woocommerce && ! $is_edd ) {
return $tests;
}
$exclude_tests = [
'keywordInSubheadings' => true,
'linksHasExternals' => true,
'linksNotAllExternals' => true,
'linksHasInternal' => true,
'titleSentiment' => true,
'titleHasNumber' => true,
'contentHasTOC' => true,
];
$tests = array_diff_assoc( $tests, $exclude_tests );
$tests['hasProductSchema'] = true;
if ( $is_woocommerce ) {
$tests['isReviewEnabled'] = true;
}
return $tests;
}
/**
* Enqueue script to analyze product data.
*
* @since 3.0.7
*/
public function enqueue() {
if ( ! Admin_Helper::is_post_edit() ) {
return;
}
$post_type = PROAdminHelper::get_current_post_type();
$is_woocommerce = Conditional::is_woocommerce_active() && 'product' === $post_type;
$is_edd = Conditional::is_edd_active() && 'download' === $post_type;
if ( ! $is_woocommerce && ! $is_edd ) {
return;
}
wp_enqueue_script( 'rank-math-gallery-analysis', RANK_MATH_PRO_URL . 'assets/admin/js/product-analysis.js', [ 'rank-math-editor' ], rank_math_pro()->version, true );
}
/**
* Get Trends icon <svg> element.
*
* @return string
*/
private function get_icon_svg() {
return '<svg width="100%" height="100%" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fit="" preserveAspectRatio="xMidYMid meet" focusable="false">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Trends-Arrow">
<g id="TrendsArrow">
<path d="M1.11227159,26.3181534 L10.2875029,17.1429221 L13.7152617,20.5706809 L4.5400304,29.7459122 C4.20518633,30.0807563 3.66229681,30.0807562 3.32745277,29.7459122 L1.11136262,27.529822 C0.776518575,27.194978 0.776518548,26.6520885 1.11136262,26.3172444 L1.11227159,26.3181534 Z" id="Shape" fill="#4285F4" fill-rule="nonzero"></path>
<path d="M14.3201543,14.3211528 L22.283717,22.2847155 L19.4658829,25.1025495 C19.1310388,25.4373936 18.5881494,25.4373937 18.2533053,25.1025495 L10.2906516,17.1398959 L13.1084857,14.3220618 C13.4429747,13.987572 13.9851638,13.9871653 14.3201543,14.3211528 Z" id="Shape" fill="#EA4335" fill-rule="nonzero"></path>
<polygon id="Rectangle-path" fill="#FABB05" fill-rule="nonzero" points="18.8573051 18.8577571 28.2843236 9.43073862 31.7120824 12.8584974 22.2850639 22.2855159"></polygon>
<path d="M35.0711567,15.5054713 L35.0711567,7 L35.0711567,7 C35.0711567,6.44771525 34.6234415,6 34.0711567,6 L25.5656854,6 L25.5656854,6 C25.0134007,6 24.5656854,6.44771525 24.5656854,7 C24.5656854,7.26521649 24.6710423,7.5195704 24.8585786,7.70710678 L33.3640499,16.2125781 L33.3640499,16.2125781 C33.7545742,16.6031024 34.3877392,16.6031024 34.7782635,16.2125781 C34.9657999,16.0250417 35.0711567,15.7706878 35.0711567,15.5054713 Z" id="Shape" fill="#34A853" fill-rule="nonzero"></path>
<rect id="Rectangle-path" x="0" y="0" width="36" height="36"></rect>
</g>
</g>
</g>
</svg>';
}
}

View File

@@ -0,0 +1,196 @@
<?php
/**
* Plugin activation and deactivation functionality.
*
* This class defines all code necessary to run during the plugin's activation.
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro;
use RankMath\Helper;
use RankMath\Analytics\Workflow\Workflow;
defined( 'ABSPATH' ) || exit;
/**
* Installer class.
*/
class Installer {
/**
* Bind all events.
*/
public function __construct() {
register_activation_hook( RANK_MATH_PRO_FILE, [ $this, 'activation' ] );
register_deactivation_hook( RANK_MATH_PRO_FILE, [ $this, 'deactivation' ] );
add_action( 'wpmu_new_blog', [ $this, 'activate_blog' ] );
add_action( 'activate_blog', [ $this, 'activate_blog' ] );
add_filter( 'wpmu_drop_tables', [ $this, 'on_delete_blog' ] );
}
/**
* Do things when activating Rank Math.
*
* @param bool $network_wide Whether the plugin is being activated network-wide.
*/
public function activation( $network_wide = false ) {
if ( ! is_multisite() || ! $network_wide ) {
$this->activate();
return;
}
$this->network_activate_deactivate( true );
}
/**
* Do things when deactivating Rank Math.
*
* @param bool $network_wide Whether the plugin is being activated network-wide.
*/
public function deactivation( $network_wide = false ) {
if ( ! is_multisite() || ! $network_wide ) {
$this->deactivate();
return;
}
$this->network_activate_deactivate( false );
}
/**
* Fired when a new site is activated with a WPMU environment.
*
* @param int $blog_id ID of the new blog.
*/
public function activate_blog( $blog_id ) {
if ( 1 !== did_action( 'wpmu_new_blog' ) ) {
return;
}
switch_to_blog( $blog_id );
$this->activate();
restore_current_blog();
}
/**
* Uninstall tables when MU blog is deleted.
*
* @param array $tables List of tables that will be deleted by WP.
* @return array
*/
public function on_delete_blog( $tables ) {
global $wpdb;
$tables[] = $wpdb->prefix . 'rank_math_analytics_ga';
$tables[] = $wpdb->prefix . 'rank_math_analytics_adsense';
$tables[] = $wpdb->prefix . 'rank_math_analytics_keyword_manager';
return $tables;
}
/**
* Run network-wide activation/deactivation of the plugin.
*
* @param bool $activate True for plugin activation, false for de-activation.
*
* Forked from Yoast (https://github.com/Yoast/wordpress-seo/)
*/
private function network_activate_deactivate( $activate ) {
global $wpdb;
$blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs WHERE archived = '0' AND spam = '0' AND deleted = '0'" );
if ( empty( $blog_ids ) ) {
return;
}
foreach ( $blog_ids as $blog_id ) {
$func = true === $activate ? 'activate' : 'deactivate';
switch_to_blog( $blog_id );
$this->$func();
restore_current_blog();
}
}
/**
* Runs on activation of the plugin.
*/
private function activate() {
\RankMathPro\Admin\Api::get()->get_settings();
$this->create_options();
// Add Analytics Installer.
Workflow::do_workflow( 'analytics', 90, null, null );
Workflow::do_workflow( 'adsense', 90, null, null );
// Add action for scheduler.
if ( function_exists( 'as_unschedule_all_actions' ) ) {
$task_name = 'rank_math/analytics/data_fetch';
if ( false === as_next_scheduled_action( $task_name ) ) {
$fetch_gap = 3;
$schedule_in_minute = wp_rand( 3, 1380 );
$time_to_schedule = ( strtotime( 'tomorrow' ) + ( $schedule_in_minute * MINUTE_IN_SECONDS ) );
as_schedule_recurring_action(
$time_to_schedule,
DAY_IN_SECONDS * $fetch_gap,
$task_name,
[],
'rank-math'
);
}
}
}
/**
* Runs on deactivation of the plugin.
*/
private function deactivate() {}
/**
* Add default values.
*/
private function create_options() {
$all_opts = rank_math()->settings->all_raw();
$general = $all_opts['general'];
$sitemap = $all_opts['sitemap'];
if ( empty( $sitemap['video_sitemap_post_type'] ) ) {
$sitemap['video_sitemap_post_type'] = array_values( Helper::get_accessible_post_types() );
}
if ( empty( $general['google_updates'] ) ) {
$general['google_updates'] = 'on';
}
if ( empty( $general['auto_add_focus_keywords'] ) ) {
$general['auto_add_focus_keywords'] = [
'enable_auto_import' => 1,
'post_types' => [ 'post', 'page' ],
];
}
if ( empty( $general['noindex_hidden_products'] ) ) {
$general['noindex_hidden_products'] = 'on';
}
$podcast_options = [
'podcast_title' => '%sitename%',
'podcast_description' => '%sitedesc%',
'podcast_explicit' => 'off',
];
foreach ( $podcast_options as $key => $value ) {
if ( empty( $general[ $key ] ) ) {
$general[ $key ] = $value;
}
}
Helper::update_all_settings( $general, null, $sitemap );
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* The Pro Module loader
*
* @since 1.0.0
* @package RankMath
* @subpackage RankMathPro
* @author MyThemeShop <admin@mythemeshop.com>
*/
namespace RankMathPro;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Conditional;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* Modules class.
*/
class Modules {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
if ( Conditional::is_heartbeat() ) {
return;
}
$this->filter( 'rank_math/modules', 'setup_core', 1 );
$this->action( 'admin_enqueue_scripts', 'enqueue' );
$this->action( 'rank_math/module_changed', 'flush_rewrite_rules', 10, 2 );
}
/**
* Setup core modules.
*
* @param array $modules Array of modules.
*
* @return array
*/
public function setup_core( $modules ) {
$active_modules = get_option( 'rank_math_modules', [] );
$modules['news-sitemap'] = [
'title' => esc_html__( 'News Sitemap', 'rank-math-pro' ),
'desc' => esc_html__( 'Create a News Sitemap for your news-related content. You only need a News sitemap if you plan on posting news-related content on your website.', 'rank-math-pro' ),
'class' => 'RankMathPro\Sitemap\News_Sitemap',
'icon' => 'post',
'settings' => Helper::get_admin_url( 'options-sitemap' ) . '#setting-panel-news-sitemap',
'probadge' => defined( 'RANK_MATH_PRO_FILE' ),
'disabled' => ( ! in_array( 'sitemap', $active_modules, true ) ),
'dep_modules' => [ 'sitemap' ],
'disabled_text' => esc_html__( 'Please activate Sitemap module to use this module.', 'rank-math-pro' ),
];
$modules['video-sitemap'] = [
'title' => esc_html__( 'Video Sitemap', 'rank-math-pro' ),
'desc' => esc_html__( 'For your video content, a Video Sitemap is a recommended step for better rankings and inclusion in the Video search.', 'rank-math-pro' ),
'class' => 'RankMathPro\Sitemap\Video_Sitemap',
'icon' => 'video',
'settings' => Helper::get_admin_url( 'options-sitemap' ) . '#setting-panel-video-sitemap',
'probadge' => defined( 'RANK_MATH_PRO_FILE' ),
'disabled' => ( ! in_array( 'rich-snippet', $active_modules, true ) || ! in_array( 'sitemap', $active_modules, true ) ),
'dep_modules' => [ 'sitemap', 'rich-snippet' ],
'disabled_text' => esc_html__( 'Please activate Schema & Sitemap module to use this module.', 'rank-math-pro' ),
];
$modules['podcast'] = [
'title' => esc_html__( 'Podcast', 'rank-math-pro' ),
'desc' => esc_html__( 'Make your podcasts discoverable via Google Podcasts, Apple Podcasts, and similar services with Podcast RSS feed and Schema Markup generated by Rank Math.', 'rank-math-pro' ),
'class' => 'RankMathPro\Podcast\Podcast',
'icon' => 'podcast',
'betabadge' => true,
'settings' => Helper::get_admin_url( 'options-general' ) . '#setting-panel-podcast',
'probadge' => defined( 'RANK_MATH_PRO_FILE' ),
'disabled' => ! in_array( 'rich-snippet', $active_modules, true ),
'dep_modules' => [ 'rich-snippet' ],
'disabled_text' => esc_html__( 'Please activate Schema module to use this module.', 'rank-math-pro' ),
];
// Replace Schema Loader.
$modules['rich-snippet']['class'] = '\RankMathPro\Schema\Schema';
return $modules;
}
/**
* Enqueue styles and scripts.
*/
public function enqueue() {
if ( 'rank-math' !== Param::get( 'page' ) ) {
return;
}
wp_enqueue_script( 'rank-math-pro-dashboard', RANK_MATH_PRO_URL . 'assets/admin/js/dashboard.js', [ 'jquery' ], rank_math_pro()->version, true );
}
/**
* Function to run when Module is enabled/disabled.
*
* @param string $module Module.
* @param string $state Module state.
*/
public function flush_rewrite_rules( $module, $state ) {
if ( 'podcast' === $module ) {
Helper::schedule_flush_rewrite();
}
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Register replacement vars.
*
* @since 1.0
* @package RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Arr;
defined( 'ABSPATH' ) || exit;
/**
* Register replacement vars class.
*
* @codeCoverageIgnore
*/
class Register_Vars {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$this->action( 'rank_math/vars/register_extra_replacements', 'register_replacements' );
}
/**
* Registers variable replacements for Rank Math Pro.
*/
public function register_replacements() {
rank_math_register_var_replacement(
'randomword',
[
'name' => esc_html__( 'Random Word', 'rank-math-pro' ),
'description' => esc_html__( 'Persistent random word chosen from a list', 'rank-math-pro' ),
'variable' => 'randomword(word1|word2|word3)',
'example' => ' ',
],
[ $this, 'get_randomword' ]
);
rank_math_register_var_replacement(
'randomword_np',
[
'name' => esc_html__( 'Random Word', 'rank-math-pro' ),
'description' => esc_html__( 'Non-persistent random word chosen from a list. A new random word will be chosen on each page load.', 'rank-math-pro' ),
'variable' => 'randomword_np(word1|word2|word3)',
'example' => ' ',
],
[ $this, 'get_randomword_np' ]
);
}
/**
* Get random word from list of words. Use the object ID for the seed if persistent.
*
* @param string $list Words list in spintax-like format.
* @param string $persistent Get persistent return value.
* @return string Random word.
*/
public function get_randomword( $list = null, $persistent = true ) {
$words = Arr::from_string( $list, '|' );
$max = count( $words );
if ( ! $max ) {
return '';
} elseif ( 1 === $max ) {
return $words[0];
}
if ( $persistent ) {
$queried_id = (int) get_queried_object_id();
$hash = (int) crc32( serialize( $words ) . $queried_id );
mt_srand( $hash );
}
$rand = mt_rand( 0, $max - 1 );
return $words[ $rand ];
}
/**
* Get random word from list of words.
*
* @param string $list Words list in spintax-like format.
* @return string Random word.
*/
public function get_randomword_np( $list = null ) {
return $this->get_randomword( $list, false );
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* The Global functionality of the plugin.
*
* Defines the functionality loaded on admin.
*
* @since 1.0.15
* @package RankMathPro
* @subpackage RankMathPro\Rest
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Rest;
use WP_Error;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Controller;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
defined( 'ABSPATH' ) || exit;
/**
* Rest class.
*/
class Rest extends WP_REST_Controller {
/**
* Constructor.
*/
public function __construct() {
$this->namespace = \RankMath\Rest\Rest_Helper::BASE;
}
/**
* Registers the routes for the objects of the controller.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/pingSettings',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'ping_settings' ],
'permission_callback' => [ $this, 'has_ping_permission' ],
]
);
}
/**
* Check API key in request.
*
* @param WP_REST_Request $request Full details about the request.
* @return bool Whether the API key matches or not.
*/
public function has_ping_permission( WP_REST_Request $request ) {
$data = Admin_Helper::get_registration_data();
return $request->get_param( 'apiKey' ) === $data['api_key'] &&
$request->get_param( 'username' ) === $data['username'];
}
/**
* Disconnect website.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function ping_settings( WP_REST_Request $request ) {
$data = Admin_Helper::get_registration_data();
$data['plan'] = $request->get_param( 'plan' );
Admin_Helper::get_registration_data( $data );
update_option( 'rank_math_keyword_quota', json_decode( $request->get_param( 'keywords' ) ) );
$settings = json_decode( $request->get_param( 'settings' ), true );
if ( ! ProAdminHelper::is_business_plan() && ! empty( $settings['analytics'] ) ) {
cmb2_update_option( 'rank-math-options-general', 'sync_global_setting', $settings['analytics'] );
}
return true;
}
}

View File

@@ -0,0 +1,549 @@
<?php
/**
* Social thumbnail overlays.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Str;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* Admin class.
*
* @codeCoverageIgnore
*/
class Thumbnail_Overlays {
use Hooker;
/**
* Register hooks.
*/
public function __construct() {
$this->filter( 'rank_math/social/overlay_images', 'add_custom_overlays' );
$this->filter( 'rank_math/social/overlay_image_position', 'apply_overlay_position', 20, 2 );
$this->filter( 'rank_math/social/overlay_image_positions', 'get_position_margins', 20, 4 );
$this->action( 'cmb2_admin_init', 'cmb_init' );
$this->filter( 'cmb2_default_filter', 'get_cmb_default', 20, 2 );
$this->filter( 'default_post_metadata', 'get_postmeta_default', 10, 5 );
$this->filter( 'default_term_metadata', 'get_termmeta_default', 10, 5 );
$this->filter( 'default_user_metadata', 'get_usermeta_default', 10, 5 );
$this->action( 'admin_init', 'enqueue', 20 );
}
/**
* Apply position for custom overlays.
*
* @param string $position Original position.
* @param string $type Overlay type.
*
* @return string New position.
*/
public function apply_overlay_position( $position, $type ) {
$custom_overlays = $this->get_custom_overlays();
if ( empty( $custom_overlays ) ) {
return $position;
}
foreach ( $custom_overlays as $overlay ) {
$id = sanitize_title( $overlay['name'], md5( $overlay['name'] ) );
if ( $id === $type ) {
return $overlay['position'];
}
}
return $position;
}
/**
* Calculate margins for new position values.
*
* @param array $margins Original margins array.
* @param resource $image GD image resource identifier.
* @param resource $stamp GD image resource identifier.
* @param string $module PHP module used for the image manipulation ('gd' or 'imagick').
*
* @return array
*/
public function get_position_margins( $margins, $image, $stamp, $module ) {
$method = 'get_positions_' . $module;
if ( ! method_exists( $this, $method ) ) {
return $margins;
}
$positions = $this->$method( $margins, $image, $stamp );
if ( empty( $positions ) ) {
return $margins;
}
$new_margins = [
'top_left' => [],
'top_center' => [],
'top_right' => [],
'middle_left' => [],
'middle_right' => [],
'bottom_left' => [],
'bottom_center' => [],
'bottom_right' => [],
];
$new_margins['top_left']['top'] = $positions['top'];
$new_margins['top_left']['left'] = $positions['left'];
$new_margins['top_center']['top'] = $positions['top'];
$new_margins['top_center']['left'] = $positions['center'];
$new_margins['top_right']['top'] = $positions['top'];
$new_margins['top_right']['left'] = $positions['right'];
$new_margins['middle_left']['top'] = $positions['middle'];
$new_margins['middle_left']['left'] = $positions['left'];
$new_margins['middle_right']['top'] = $positions['middle'];
$new_margins['middle_right']['left'] = $positions['right'];
$new_margins['bottom_left']['top'] = $positions['bottom'];
$new_margins['bottom_left']['left'] = $positions['left'];
$new_margins['bottom_center']['top'] = $positions['bottom'];
$new_margins['bottom_center']['left'] = $positions['center'];
$new_margins['bottom_right']['top'] = $positions['bottom'];
$new_margins['bottom_right']['left'] = $positions['right'];
return $margins + $new_margins;
}
/**
* Get margins for GD image manipulation.
*
* @param array $margins Original margins array.
* @param resource $image GD image resource identifier.
* @param resource $stamp GD image resource identifier.
*
* @return array
*/
private function get_positions_gd( $margins, $image, $stamp ) {
$left = 0;
$top = 0;
$right = abs( imagesx( $image ) - imagesx( $stamp ) );
$bottom = abs( imagesy( $image ) - imagesy( $stamp ) );
$center = round( $right / 2 );
$middle = round( $bottom / 2 );
return compact( 'left', 'top', 'right', 'bottom', 'center', 'middle' );
}
/**
* Get margins for Imagick image manipulation.
*
* @param array $margins Original margins array.
* @param object $image Imagick image object.
* @param object $stamp Imagick image object.
*
* @return array
*/
private function get_positions_imagick( $margins, $image, $stamp ) {
$left = 0;
$top = 0;
$right = abs( $image->getImageWidth() - $stamp->getImageWidth() );
$bottom = abs( $image->getImageHeight() - $stamp->getImageHeight() );
$center = round( $right / 2 );
$middle = round( $bottom / 2 );
return compact( 'left', 'top', 'right', 'bottom', 'center', 'middle' );
}
/**
* Set default value for overlay meta options.
*
* @param mixed $value The value to return, either a single metadata value or an array
* of values depending on the value of `$single`.
* @param int $object_id ID of the object metadata is for.
* @param string $meta_key Metadata key.
* @param bool $single Whether to return only the first value of the specified `$meta_key`.
* @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
* or any other object type with an associated meta table.
* @return mixed
*/
public function get_postmeta_default( $value, $object_id, $meta_key, $single, $meta_type ) {
if ( ! $this->is_overlay_field( $meta_key ) ) {
return $value;
}
return $this->get_meta_default( $meta_key, get_post_type( $object_id ), $value );
}
/**
* Set default value for overlay meta options.
*
* @param mixed $value The value to return, either a single metadata value or an array
* of values depending on the value of `$single`.
* @param int $object_id ID of the object metadata is for.
* @param string $meta_key Metadata key.
* @param bool $single Whether to return only the first value of the specified `$meta_key`.
* @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
* or any other object type with an associated meta table.
* @return mixed
*/
public function get_termmeta_default( $value, $object_id, $meta_key, $single, $meta_type ) {
if ( ! $this->is_overlay_field( $meta_key ) ) {
return $value;
}
return $this->get_meta_default( $meta_key, '', $value );
}
/**
* Set default value for overlay meta options.
*
* @param mixed $value The value to return, either a single metadata value or an array
* of values depending on the value of `$single`.
* @param int $object_id ID of the object metadata is for.
* @param string $meta_key Metadata key.
* @param bool $single Whether to return only the first value of the specified `$meta_key`.
* @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
* or any other object type with an associated meta table.
* @return mixed
*/
public function get_usermeta_default( $value, $object_id, $meta_key, $single, $meta_type ) {
if ( ! $this->is_overlay_field( $meta_key ) ) {
return $value;
}
return $this->get_meta_default( $meta_key, '', $value );
}
/**
* Set default value for overlay CMB options.
*
* @param mixed $default Original default value.
* @param object $field CMB Field object.
* @return mixed
*/
public function get_cmb_default( $default, $field ) {
$meta_key = $field->id();
if ( ! $this->is_overlay_field( $meta_key ) ) {
return $default;
}
return $this->get_pt_default( $meta_key, get_post_type(), $default );
}
/**
* Check if a field ID is for an overlay related field.
*
* @param string $field Field ID.
* @return boolean
*/
public function is_overlay_field( $field ) {
$overlay_fields = [ 'rank_math_facebook_enable_image_overlay', 'rank_math_twitter_enable_image_overlay', 'rank_math_facebook_image_overlay', 'rank_math_twitter_image_overlay' ];
return in_array( $field, $overlay_fields, true );
}
/**
* Get default overlay as set in the plugin settings, or return $default.
*
* @param string $key Field ID (custom field name).
* @param string $post_type Post type.
* @param string $default Default value.
* @return mixed
*/
public function get_meta_default( $key, $post_type, $default = false ) {
if ( $post_type ) {
$pt_default = Helper::get_settings( 'titles.pt_' . $post_type . '_image_overlay' );
if ( $pt_default ) {
if ( strpos( $key, '_enable_image_overlay' ) !== false ) {
return 'on';
}
return $pt_default;
}
}
$global_default = Helper::get_settings( 'titles.default_image_overlay' );
if ( $global_default ) {
if ( strpos( $key, '_enable_image_overlay' ) !== false ) {
return 'on';
}
return $global_default;
}
return $default;
}
/**
* Add custom overlays to the list.
*
* @param array $overlays Original overlays.
*
* @return array New overlays.
*/
public function add_custom_overlays( $overlays ) {
$custom_overlays = $this->get_custom_overlays();
if ( empty( $custom_overlays ) ) {
return $overlays;
}
foreach ( $custom_overlays as $custom_overlay ) {
$new_id = sanitize_title( $custom_overlay['name'], md5( $custom_overlay['name'] ) );
$upload_dir = wp_upload_dir();
$image_path = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $custom_overlay['image'] );
$overlays[ $new_id ] = [
'name' => $custom_overlay['name'],
'url' => $custom_overlay['image'],
'path' => $image_path,
];
}
return $overlays;
}
/**
* Hook CMB2 init process.
*/
public function cmb_init() {
$this->action( 'rank_math/admin/settings/global', 'add_thumbnail_watermark_options', 10, 2 );
foreach ( Helper::get_accessible_post_types() as $post_type ) {
if ( 'attachment' === $post_type && Helper::get_settings( 'general.attachment_redirect_urls', true ) ) {
continue;
}
$this->action( "rank_math/admin/settings/post-type-{$post_type}", 'add_thumbnail_watermark_options', 10, 2 );
}
foreach ( Helper::get_accessible_taxonomies() as $slug => $taxonomy ) {
if ( ! $this->is_taxonomy_allowed( $taxonomy->name ) ) {
continue;
}
$this->action( "rank_math/admin/settings/taxonomy-{$slug}", 'add_thumbnail_watermark_options', 10, 2 );
}
}
/**
* Add Custom Image Overlay option in Titles & Meta settings.
*
* @param object $cmb CMB2 instance.
* @param array $tab Current settings tab.
*/
public function add_thumbnail_watermark_options( $cmb, $tab ) {
$overlays = array_merge( [ '' => __( 'Off', 'rank-math-pro' ) ], Helper::choices_overlay_images( 'names' ) );
$field_ids = wp_list_pluck( $cmb->prop( 'fields' ), 'id' );
if ( isset( $tab['post_type'] ) || isset( $tab['taxonomy'] ) ) {
$id = isset( $tab['taxonomy'] ) ? "tax_{$tab['taxonomy']}_image_overlay" : "pt_{$tab['post_type']}_image_overlay";
$position = isset( $tab['taxonomy'] ) ? array_search( "remove_{$tab['taxonomy']}_snippet_data", array_keys( $field_ids ), true ) + 1 : $this->get_field_position( $tab['post_type'], $field_ids );
$cmb->add_field(
[
'id' => $id,
'type' => 'radio_inline',
'name' => esc_html__( 'Default Thumbnail Watermark', 'rank-math-pro' ),
'desc' => esc_html__( 'Select the default watermark that will be applied if no specific watermark is selected.', 'rank-math-pro' ),
'options' => $overlays,
'default' => '',
'classes' => 'default-overlay-field',
],
++$position
);
return;
}
$fields_position = array_search( 'twitter_card_type', array_keys( $field_ids ), true ) + 1;
$overlays_fields = $cmb->add_field(
[
'id' => 'custom_image_overlays',
'type' => 'group',
'name' => esc_html__( 'Custom Image Watermarks', 'rank-math-pro' ),
'desc' => esc_html__( 'Add more image watermarks to choose from for the social thumbnails.', 'rank-math-pro' ),
'options' => [
'add_button' => esc_html__( 'Add Watermark', 'rank-math-pro' ),
'remove_button' => esc_html__( 'Remove', 'rank-math-pro' ),
],
'classes' => 'cmb-group-text-only',
'sanitization_cb' => [ $this, 'sanitize_overlays' ],
],
++$fields_position
);
$cmb->add_group_field(
$overlays_fields,
[
'id' => 'image',
'type' => 'file',
'options' => [
'url' => false,
],
'text' => [ 'add_upload_file_text' => esc_html__( 'Add Image', 'rank-math-pro' ) ],
]
);
$cmb->add_group_field(
$overlays_fields,
[
'id' => 'name',
'type' => 'text',
'attributes' => [
'placeholder' => esc_attr__( 'Name*', 'rank-math-pro' ),
],
]
);
$cmb->add_group_field(
$overlays_fields,
[
'id' => 'position',
'type' => 'select',
'options' => $this->get_position_choices(),
'default' => 'bottom_right',
]
);
$cmb->add_field(
[
'id' => 'default_image_overlay',
'type' => 'radio_inline',
'name' => esc_html__( 'Default Thumbnail Watermark', 'rank-math-pro' ),
'desc' => esc_html__( 'Select the default watermark that will be applied if no specific watermark is selected.', 'rank-math-pro' ),
'options' => $overlays,
'default' => '',
'classes' => 'default-overlay-field',
],
++$fields_position
);
}
/**
* Enqueue assets.
*
* @return void
*/
public function enqueue() {
if ( Param::get( 'page' ) !== 'rank-math-options-titles' ) {
return;
}
wp_enqueue_style(
'rank-math-pro-title-options',
RANK_MATH_PRO_URL . 'assets/admin/css/title-options.css',
null,
rank_math_pro()->version
);
wp_enqueue_script( 'rank-math-pro-redirections', RANK_MATH_PRO_URL . 'assets/admin/js/title-options.js', [], RANK_MATH_PRO_VERSION, true );
}
/**
* Get custom overlays.
*
* @return array
*/
private function get_custom_overlays() {
return array_filter(
array_map(
function( $overlay ) {
return empty( $overlay['name'] ) || empty( $overlay['image'] ) ? false : $overlay;
},
(array) Helper::get_settings( 'titles.custom_image_overlays' )
)
);
}
/**
* Get field position by post type.
*
* @param string $post_type Post type.
* @param array $field_ids All field ids.
*
* @return bool
*/
private function get_field_position( $post_type, $field_ids ) {
$field = "pt_{$post_type}_analyze_fields";
if ( 'attachment' === $post_type ) {
$field = 'pt_attachment_bulk_editing';
}
if ( 'web-story' === $post_type ) {
$field = 'pt_web-story_slack_enhanced_sharing';
}
return array_search( $field, array_keys( $field_ids ), true ) + 1;
}
/**
* Is taxonomy allowed
*
* @param string $taxonomy Taxonomy to check.
*
* @return bool
*/
private function is_taxonomy_allowed( $taxonomy ) {
$exclude_taxonomies = [ 'post_format', 'product_shipping_class' ];
if ( Str::starts_with( 'pa_', $taxonomy ) || in_array( $taxonomy, $exclude_taxonomies, true ) ) {
return false;
}
return true;
}
/**
* Do not save if name or image is empty.
*
* @param array $value Field value to save.
* @return array
*/
private function sanitize_overlays( $value ) {
if ( ! is_array( $value ) ) {
return [];
}
foreach ( $value as $key => $overlay ) {
if ( empty( $overlay['image'] ) ) {
unset( $value[ $key ] );
} elseif ( empty( $overlay['name'] ) ) {
Helper::add_notification( esc_html__( 'A Custom Watermark item could not be saved because the name field is empty.', 'rank-math-pro' ), [ 'type' => 'error' ] );
unset( $value[ $key ] );
}
}
return $value;
}
/**
* Get position options.
*
* @return array
*/
private function get_position_choices() {
return [
'top_left' => __( 'Top Left', 'rank-math-pro' ),
'top_center' => __( 'Top Center', 'rank-math-pro' ),
'top_right' => __( 'Top Right', 'rank-math-pro' ),
'middle_left' => __( 'Middle Left', 'rank-math-pro' ),
'middle_center' => __( 'Middle Center', 'rank-math-pro' ),
'middle_right' => __( 'Middle Right', 'rank-math-pro' ),
'bottom_left' => __( 'Bottom Left', 'rank-math-pro' ),
'bottom_center' => __( 'Bottom Center', 'rank-math-pro' ),
'bottom_right' => __( 'Bottom Right', 'rank-math-pro' ),
];
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* Functions and actions related to updates.
*
* @since 2.0.6
* @package RankMathPro
* @subpackage RankMathPro\Core
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro;
use RankMath\Traits\Hooker;
defined( 'ABSPATH' ) || exit;
/**
* Updates class
*/
class Updates {
use Hooker;
/**
* Updates that need to be run
*
* @var array
*/
private static $updates = [
'2.0.6' => 'updates/update-2.0.6.php',
'2.1.0' => 'updates/update-2.1.0.php',
'2.8.1' => 'updates/update-2.8.1.php',
'2.12.0' => 'updates/update-2.12.0.php',
'3.0.17' => 'updates/update-3.0.17.php',
'3.0.26' => 'updates/update-3.0.26.php',
'3.0.32' => 'updates/update-3.0.32.php',
];
/**
* Register hooks.
*/
public function __construct() {
$this->action( 'admin_init', 'do_updates' );
}
/**
* Check if any update is required.
*/
public function do_updates() {
$installed_version = get_option( 'rank_math_pro_version', '1.0.0' );
// Maybe it's the first install.
if ( ! $installed_version ) {
return;
}
if ( version_compare( $installed_version, rank_math_pro()->version, '<' ) ) {
$this->perform_updates();
}
}
/**
* Perform all updates.
*/
public function perform_updates() {
$installed_version = get_option( 'rank_math_pro_version', '1.0.0' );
foreach ( self::$updates as $version => $path ) {
if ( version_compare( $installed_version, $version, '<' ) ) {
include $path;
update_option( 'rank_math_pro_version', $version );
}
}
update_option( 'rank_math_pro_version', rank_math_pro()->version );
}
}

View File

@@ -0,0 +1 @@
<?php // Silence is golden.

View File

@@ -0,0 +1,5 @@
/*!
* Plugin: Rank Math SEO Pro
* URL: https://rankmath.com/wordpress/plugin/seo-suite/
* Name: 404-monitor.css
*/.rank-math-export-404-panel h3{margin-top:0}.rank-math-export-404-panel form .form-field{display:inline-block;margin-bottom:0}.rank-math-export-404-panel form .form-field input[type="submit"]{vertical-align:baseline}.ui-datepicker{padding:0;margin:0;border-radius:0;background-color:#fff;border:1px solid #dfdfdf;border-top:none;box-shadow:0 3px 6px rgba(0,0,0,0.075);min-width:17em;width:auto}.ui-datepicker *{padding:0;font-family:"Open Sans", sans-serif;border-radius:0}.ui-datepicker table{font-size:13px;margin:0;border:none;border-collapse:collapse}.ui-datepicker .ui-widget-header,.ui-datepicker .ui-datepicker-header{background-image:none;border:none;color:#fff;font-weight:normal}.ui-datepicker .ui-datepicker-header .ui-state-hover{background:transparent;border-color:transparent;cursor:pointer}.ui-datepicker .ui-datepicker-title{margin:0;padding:10px 0;color:#fff;font-size:14px;line-height:14px;text-align:center}.ui-datepicker .ui-datepicker-title select{margin-top:-8px;margin-bottom:-8px}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:relative;top:0;height:34px;width:34px}.ui-datepicker .ui-state-hover.ui-datepicker-prev,.ui-datepicker .ui-state-hover.ui-datepicker-next{border:none}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-prev-hover{left:0}.ui-datepicker .ui-datepicker-next,.ui-datepicker .ui-datepicker-next-hover{right:0}.ui-datepicker .ui-datepicker-next span,.ui-datepicker .ui-datepicker-prev span{display:none}.ui-datepicker .ui-datepicker-prev{float:left}.ui-datepicker .ui-datepicker-next{float:right}.ui-datepicker .ui-datepicker-prev:before,.ui-datepicker .ui-datepicker-next:before{font:normal 20px/34px 'dashicons';padding-left:7px;color:#fff;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;width:34px;height:34px}.ui-datepicker .ui-datepicker-prev:before{content:'\f341'}.ui-datepicker .ui-datepicker-next:before{content:'\f345'}.ui-datepicker .ui-datepicker-prev-hover:before,.ui-datepicker .ui-datepicker-next-hover:before{opacity:0.7}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:33%;background:transparent;border-color:transparent;box-shadow:none;color:#fff}.ui-datepicker select.ui-datepicker-month option,.ui-datepicker select.ui-datepicker-year option{color:#333}.ui-datepicker thead{color:#fff;font-weight:600}.ui-datepicker thead th{font-weight:normal}.ui-datepicker th{padding:10px}.ui-datepicker td{padding:0;border:1px solid #f4f4f4}.ui-datepicker td.ui-datepicker-other-month{border:transparent}.ui-datepicker td.ui-datepicker-week-end{background-color:#f4f4f4;border:1px solid #f4f4f4}.ui-datepicker td.ui-datepicker-week-end.ui-datepicker-today{box-shadow:inset 0px 0px 1px 0px rgba(0,0,0,0.1)}.ui-datepicker td.ui-datepicker-today{background-color:#f0f0c0}.ui-datepicker td.ui-datepicker-current-day{background:#bbdd88}.ui-datepicker td .ui-state-default{background:transparent;border:none;text-align:center;text-decoration:none;width:auto;display:block;padding:5px 10px;font-weight:normal;color:#444}.ui-datepicker td.ui-state-disabled .ui-state-default{opacity:0.5}.ui-datepicker .ui-widget-header,.ui-datepicker .ui-datepicker-header{background:#00a0d2}.ui-datepicker thead{background:#32373c}.ui-datepicker td .ui-state-hover,.ui-datepicker td .ui-state-active{background:#0073aa;color:#fff}.ui-datepicker .ui-timepicker-div{font-size:14px}.ui-datepicker .ui-timepicker-div dl{text-align:left;padding:0 .6em}.ui-datepicker .ui-timepicker-div dl dt{float:left;clear:left;padding:0 0 0 5px}.ui-datepicker .ui-timepicker-div dl dd{margin:0 10px 10px 40%}.ui-datepicker .ui-timepicker-div dl dd select{width:100%}.ui-datepicker .ui-timepicker-div+.ui-datepicker-buttonpane{padding:.6em;text-align:left}.ui-datepicker .ui-timepicker-div+.ui-datepicker-buttonpane .button-primary,.ui-datepicker .ui-timepicker-div+.ui-datepicker-buttonpane .button-secondary{padding:0 10px 1px;border-radius:3px;margin:0 .6em .4em .4em}

View File

@@ -0,0 +1 @@
(()=>{"use strict";var e={n:t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return e.d(r,{a:r}),r},d:(t,r)=>{for(var a in r)e.o(r,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:r[a]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=jQuery;var r=e.n(t);r()((function(){r()("#rank_math_export_404_date_from, #rank_math_export_404_date_to").datepicker({dateFormat:"yy-mm-dd"});var e=r()(".rank-math-export-404-panel");r()("a.rank-math-404-monitor-export").on("click",(function(t){t.preventDefault(),e.toggleClass("hidden")}))}))})();

View File

@@ -0,0 +1,258 @@
<?php
/**
* 404 Monitor module.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Param;
use MyThemeShop\Database\Database;
use RankMathPro\Admin\CSV;
defined( 'ABSPATH' ) || exit;
/**
* Monitor class.
*
* @codeCoverageIgnore
*/
class Monitor_Pro extends CSV {
use Hooker;
/**
* Total hits cache.
*
* @var array
*/
private $total_hits_cache = [];
/**
* Constructor.
*/
public function __construct() {
$this->filter( 'rank_math/404_monitor/page_title_actions', 'page_title_actions', 20, 1 );
$this->action( 'rank_math/404_monitor/before_list_table', 'export_panel', 20 );
$this->action( 'admin_enqueue_scripts', 'enqueue', 20 );
$this->action( 'init', 'maybe_export', 20 );
$this->filter( 'rank_math/404_monitor/list_table_columns', 'manage_columns', 20 );
$this->filter( 'rank_math/404_monitor/list_table_column', 'total_hits_column', 20, 3 );
$this->filter( 'rank_math/404_monitor/get_logs_args', 'get_logs_args', 20 );
}
/**
* Add page title action for export.
*
* @param array $actions Original actions.
* @return array
*/
public function page_title_actions( $actions ) {
$actions['export'] = [
'class' => 'page-title-action',
'href' => add_query_arg( 'export-404', '1' ),
'label' => __( 'Export', 'rank-math-pro' ),
];
return $actions;
}
/**
* Output export panel.
*
* @return void
*/
public function export_panel() {
$today = date( 'Y-m-d' );
?>
<div class="rank-math-box rank-math-export-404-panel <?php echo Param::get( 'export-404' ) ? '' : 'hidden'; ?>">
<h3><?php esc_html_e( 'Export 404 Logs', 'rank-math-pro' ); ?></h3>
<p class="description">
<?php esc_html_e( 'Export and download 404 logs from a selected period of time in the form of a CSV file. Leave the from/to fields empty to export all logs.', 'rank-math-pro' ); ?>
</p>
<div class="form-wrap">
<form action="" method="get" autocomplete="off">
<input type="hidden" name="action" value="rank_math_export_404">
<?php wp_nonce_field( 'export_404' ); ?>
<div class="form-field">
<label for="rank_math_export_404_date_from">
<?php esc_html_e( 'From date', 'rank-math-pro' ); ?>
</label>
<input type="text" name="date_from" value="" id="rank_math_export_404_date_from" class="rank-math-datepicker" placeholder="<?php echo esc_attr( $today ); ?>">
</div>
<div class="form-field">
<label for="rank_math_export_404_date_to">
<?php esc_html_e( 'To date', 'rank-math-pro' ); ?>
</label>
<input type="text" name="date_to" value="" id="rank_math_export_404_date_to" class="rank-math-datepicker" placeholder="<?php echo esc_attr( $today ); ?>">
</div>
<div class="rank_math_export_404_submit_wrap form-field">
<input type="submit" value="<?php esc_attr_e( 'Export', 'rank-math-pro' ); ?>" class="button button-primary">
</div>
</form>
</div>
</div>
<?php
}
/**
* Undocumented function
*
* @return void
*/
public function maybe_export() {
if ( Param::get( 'action' ) !== 'rank_math_export_404' ) {
return;
}
if ( ! current_user_can( 'export' ) || ! Helper::has_cap( '404_monitor' ) ) {
// Todo: add error notice instead of wp_die()?
wp_die( esc_html__( 'Sorry, your user does not seem to have the necessary capabilities to export.', 'rank-math-pro' ) );
}
if ( wp_verify_nonce( Param::get( '_nonce' ), 'export_404' ) ) {
// Todo: add error notice instead of wp_die()?
wp_die( esc_html__( 'Nonce error. Please try again.', 'rank-math-pro' ) );
}
$date_from = $this->sanitize_datetime( Param::get( 'date_from' ) );
$date_to = $this->sanitize_datetime( Param::get( 'date_to' ) );
$data = $this->export_items( $date_from, $date_to );
$this->export(
[
'filename' => '404-log',
'columns' => $data['columns'],
'items' => $data['items'],
]
);
die();
}
/**
* Do export.
*
* @param string $time_from Start date (SQL DateTime format).
* @param string $time_to End date (SQL DateTime format).
*
* @return array
*/
private function export_items( $time_from = null, $time_to = null ) {
global $wpdb;
$logs_table = $wpdb->prefix . 'rank_math_404_logs';
$query = "SELECT * FROM {$logs_table} WHERE 1=1";
$where = '';
if ( $time_from ) {
$where .= " AND accessed > '{$time_from} 00:00:01'";
}
if ( $time_to ) {
$where .= " AND accessed < '{$time_to} 23:59:59'";
}
$query .= $where;
$items = $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
if ( empty( $items ) ) {
return [
'columns' => [],
'items' => [],
];
}
$columns = array_keys( $items[0] );
return [
'columns' => $columns,
'items' => $items,
];
}
/**
* Sanitize date field inputs.
*
* @param string $date Date input.
* @return string
*/
public function sanitize_datetime( $date ) {
return preg_replace( '/[^0-9 :-]/', '', $date );
}
/**
* Enqueue styles and scripts.
*
* @param string $hook The current admin page.
*
* @return void
*/
public function enqueue( $hook ) {
if ( 'rank-math_page_rank-math-404-monitor' !== $hook ) {
return;
}
$url = RANK_MATH_PRO_URL . 'includes/modules/404-monitor/assets/';
wp_enqueue_script( 'rank-math-pro-404-monitor', $url . 'js/404-monitor.js', [ 'jquery-ui-core', 'jquery-ui-datepicker' ], RANK_MATH_PRO_VERSION, true );
wp_enqueue_style( 'rank-math-pro-404-monitor', $url . 'css/404-monitor.css', [], RANK_MATH_PRO_VERSION );
}
/**
* Add extra columns for the list table.
*
* @param array $columns Original columns.
* @return array
*/
public function manage_columns( $columns ) {
if ( 'simple' === Helper::get_settings( 'general.404_monitor_mode' ) ) {
return $columns;
}
$columns['total_hits'] = esc_html__( 'Hits', 'rank-math-pro' );
return $columns;
}
/**
* Add content in the extra columns.
*
* @param string $content Original content.
* @param array $item Table item.
* @param string $column Column name.
* @return string
*/
public function total_hits_column( $content, $item, $column ) {
if ( 'total_hits' !== $column ) {
return $content;
}
if ( ! isset( $this->total_hits_cache[ $item['uri'] ] ) ) {
$this->total_hits_cache[ $item['uri'] ] = Database::table( 'rank_math_404_logs' )->selectCount( '*', 'count' )->where( 'uri', $item['uri'] )->getVar();
}
return '<a href="' . add_query_arg( [ 'uri' => $item['uri'] ], '?page=rank-math-404-monitor' ) . '">' . $this->total_hits_cache[ $item['uri'] ] . '</a>';
}
/**
* Change get_logs() args when filtering for a URI.
*
* @param array $args Original args.
* @return array
*/
public function get_logs_args( $args ) {
$uri = Param::get( 'uri' );
if ( ! $uri ) {
return $args;
}
$args['uri'] = $uri;
return $args;
}
}

View File

@@ -0,0 +1,287 @@
<?php
/**
* ACF module.
*
* @since 2.0.9
* @package RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\ACF;
use RankMath\Helper;
use RankMath\Traits\Hooker;
defined( 'ABSPATH' ) || exit;
/**
* ACF class.
*/
class ACF {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
$this->action( 'rank_math/sitemap/urlimages', 'add_acf_images', 10, 2 );
$this->filter( 'rank_math/sitemap/content_before_parse_html_images', 'parse_html_images', 10, 2 );
$this->action( 'rank_math/admin/settings/general', 'acf_sitemap_settings' );
}
/**
* Add new settings.
*
* @param object $cmb CMB2 instance.
*/
public function acf_sitemap_settings( $cmb ) {
$field_ids = wp_list_pluck( $cmb->prop( 'fields' ), 'id' );
$field_position = array_search( 'include_featured_image', array_keys( $field_ids ), true ) + 1;
$cmb->add_field(
[
'id' => 'include_acf_images',
'type' => 'toggle',
'name' => esc_html__( 'Include Images from the ACF Fields.', 'rank-math-pro' ),
'desc' => esc_html__( 'Include images added in the ACF fields.', 'rank-math-pro' ),
'options' => [
'off' => esc_html__( 'Default', 'rank-math-pro' ),
'on' => esc_html__( 'Custom', 'rank-math-pro' ),
],
'default' => 'off',
'dep' => [ [ 'include_images', 'on' ] ],
],
++$field_position
);
}
/**
* Add images from the ACF fields content in the Sitemap.
*
* @param string $content Post content.
* @param int $post_id Post ID.
*/
public function parse_html_images( $content, $post_id ) {
if ( ! Helper::get_settings( 'sitemap.include_acf_images' ) ) {
return $content;
}
$fields = get_field_objects( $post_id );
if ( empty( $fields ) ) {
return $content;
}
foreach ( $fields as $field ) {
if ( empty( $field['value'] ) ) {
continue;
}
if ( in_array( $field['type'], [ 'wysiwyg', 'textarea' ], true ) ) {
$content .= $field['value'];
continue;
}
if ( 'flexible_content' === $field['type'] ) {
$this->get_flexible_content( $content, $field, $post_id );
continue;
}
if ( 'repeater' === $field['type'] || 'group' === $field['type'] ) {
$this->get_sub_fields_content( $content, $field['sub_fields'], $field );
continue;
}
}
return $content;
}
/**
* Filter images to be included for the post in XML sitemap.
*
* @param array $images Array of image items.
* @param int $post_id ID of the post.
*/
public function add_acf_images( $images, $post_id ) {
if ( ! Helper::get_settings( 'sitemap.include_acf_images' ) ) {
return $images;
}
$fields = get_field_objects( $post_id );
if ( empty( $fields ) ) {
return $images;
}
$values = wp_list_pluck( $fields, 'value' );
$this->get_all_images( $images, $values );
return $images;
}
/**
* Get content from flexible_content field.
*
* @param string $content Post content.
* @param array $field Current field data.
* @param int $post_id Post ID.
*/
private function get_flexible_content( &$content, $field, $post_id ) {
if ( empty( $field['layouts'] ) || empty( current( $field['layouts'] ) ) ) {
return;
}
$this->get_sub_fields_content( $content, current( $field['layouts'] )['sub_fields'], $field );
}
/**
* Get content from ACF sub-fields.
*
* @param string $content Post content.
* @param array $sub_fields Array of subfields.
* @param array $field Current field data.
*/
private function get_sub_fields_content( &$content, $sub_fields, $field ) {
foreach ( $sub_fields as $layout ) {
if ( ! in_array( $layout['type'], [ 'wysiwyg', 'textarea' ], true ) ) {
continue;
}
foreach ( $field['value'] as $key => $value ) {
if ( $key === $layout['name'] ) {
$content .= $value;
continue;
}
$content .= is_array( $value ) && ! empty( $value[ $layout['name'] ] ) ? $value[ $layout['name'] ] : '';
}
}
}
/**
* Add Images to XML Sitemap.
*
* @param array $images Array of image items.
* @param array $field_data Current Image array.
* @param string $field_type Is field type gallery.
*/
private function add_images_to_sitemap( &$images, $field_data, $field_type ) {
if ( empty( $field_data ) ) {
return;
}
if ( in_array( $field_type, [ 'group', 'repeater', 'flexible_content' ], true ) ) {
$this->add_images_from_repeater_field( $images, $field_data );
return;
}
if ( in_array( $field_type, [ 'gallery' ], true ) ) {
foreach ( $field_data as $image ) {
$this->add_images_to_sitemap( $images, $image, 'image' );
}
return;
}
if ( 'image' !== $field_type || empty( $field_data ) ) {
return;
}
if ( is_array( $field_data ) && ! empty( $field_data['url'] ) ) {
$images[] = [
'src' => $field_data['url'],
'title' => $field_data['title'],
'alt' => $field_data['alt'],
];
} elseif ( is_int( $field_data ) ) {
$image_url = wp_get_attachment_image_url( $field_data, 'full' );
if ( $image_url ) {
$images[] = [
'src' => $image_url,
'title' => '',
'alt' => '',
];
}
} elseif ( Helper::is_image_url( $field_data ) ) {
$images[] = [
'src' => $field_data,
'title' => '',
'alt' => '',
];
}
}
/**
* Add Images to XML Sitemap from Repeater field.
*
* @param array $images Array of image items.
* @param array $field_data Current Image array.
*/
private function add_images_from_repeater_field( &$images, $field_data ) {
if ( empty( $field_data ) ) {
return;
}
foreach ( $field_data as $data ) {
if ( is_array( $data ) ) {
foreach ( $data as $image ) {
if ( is_array( $image ) ) {
$this->add_images_to_sitemap( $images, $image[0], 'image' );
} else {
$this->add_images_to_sitemap( $images, $image, 'image' );
}
}
} else {
$this->add_images_to_sitemap( $images, $data, 'image' );
}
}
}
/**
* Get all images
*
* @param array $images All images.
* @param array $data All acf field values.
*
* @return array
*/
public function get_all_images( &$images, $data ) {
if ( is_array( $data ) ) {
foreach ( array_values( $data ) as $single_data ) {
if ( is_array( $single_data ) && isset( $single_data['type'] ) && 'image' === $single_data['type'] ) {
$images[] = [
'src' => $single_data['url'],
'title' => $single_data['title'],
'alt' => $single_data['alt'],
];
$single_data = '';
}
$this->get_all_images( $images, $single_data );
}
}
if ( empty( $data ) || is_array( $data ) ) {
return $images;
}
if ( is_int( $data ) ) {
$image_url = wp_get_attachment_image_url( $data, 'full' );
if ( $image_url ) {
$images[] = [
'src' => $image_url,
'title' => '',
'alt' => '',
];
}
}
if ( Helper::is_image_url( $data ) ) {
$images[] = [
'src' => $data,
'title' => '',
'alt' => '',
];
}
return $images;
}
}

View File

@@ -0,0 +1 @@
:root{--rankmath-wp-adminbar-height: 0}@keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(-360deg)}}@keyframes bounce{from{transform:translateY(0px)}to{transform:translateY(-5px)}}@keyframes loading{0%{background-size:20% 50% ,20% 50% ,20% 50%}20%{background-size:20% 20% ,20% 50% ,20% 50%}40%{background-size:20% 100%,20% 20% ,20% 50%}60%{background-size:20% 50% ,20% 100%,20% 20%}80%{background-size:20% 50% ,20% 50% ,20% 100%}100%{background-size:20% 50% ,20% 50% ,20% 50%}}.rank-math-pagespeed-box .rank-math-pagespeed-header{display:flex;flex-flow:row wrap}.rank-math-pagespeed-box .rank-math-pagespeed-header{align-items:center}.rank-math-pagespeed-box .rank-math-pagespeed-header h3{font-size:1rem}.rank-math-pagespeed-box .rank-math-pagespeed-header span{margin-left:auto}.rank-math-pagespeed-box .rank-math-pagespeed-header .button{line-height:1;width:30px;height:30px;padding:0;text-align:center;text-decoration:none;color:#b5bfc9;border:0;fill:#b5bfc9;-webkit-text-stroke:1px white}.rank-math-pagespeed-box .rank-math-pagespeed-header .button svg,.rank-math-pagespeed-box .rank-math-pagespeed-header .button .dashicon{margin-right:0;vertical-align:-2px}.rank-math-pagespeed-box .rank-math-pagespeed-header .button:hover,.rank-math-pagespeed-box .rank-math-pagespeed-header .button:focus,.rank-math-pagespeed-box .rank-math-pagespeed-header .button:active{outline:none;background:transparent;box-shadow:none;fill:#069de3}.rank-math-pagespeed-box .rank-math-pagespeed-header .button.loading{display:inline-block;animation:rm-spin 1s linear infinite}@keyframes rm-spin{0%{transform:rotate(360deg);color:#10AC84;fill:#10AC84}25%{color:#4e8cde;fill:#4e8cde}50%{color:#ed5e5e;fill:#ed5e5e}75%{color:#FF9F43;fill:#FF9F43}100%{transform:rotate(0deg);color:#F368E0;fill:#F368E0}}.rank-math-pagespeed-box .col{margin-top:.825rem;flex:0 0 50%}.rank-math-pagespeed-box .col .rm-icon{font-size:1.25rem;margin-right:8px;vertical-align:middle}.rank-math-pagespeed-box .col strong{font-size:1rem;font-weight:500;margin-right:10px;vertical-align:middle}.rank-math-pagespeed-box .col strong.interactive-good{color:#52a652}.rank-math-pagespeed-box .col strong.interactive-fair{color:#FF9800}.rank-math-pagespeed-box .col strong.interactive-bad{color:#f0776f}.rank-math-pagespeed-box .col small{font-size:14px;font-weight:500;line-height:24px;display:inline-block;width:42px;text-align:center;color:#52a652;border-radius:5px;background:#e9f4e9}.rank-math-pagespeed-box .col small.score-fair{color:#FF9800;background:#ffefd6}.rank-math-pagespeed-box .col small.score-bad{color:#f0776f;background:#fdedec}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,96 @@
<?php
/**
* The Analytics AJAX
*
* @since 1.4.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMathPro\Google\Adsense;
use MyThemeShop\Helpers\Param;
use RankMath\Analytics\Workflow\Base;
use RankMath\Analytics\Workflow\Workflow;
defined( 'ABSPATH' ) || exit;
/**
* Ajax class.
*/
class Ajax {
use \RankMath\Traits\Ajax;
/**
* The Constructor
*/
public function __construct() {
$this->ajax( 'save_adsense_account', 'save_adsense_account' );
$this->ajax( 'check_adsense_request', 'check_adsense_request' );
}
/**
* Check the Google AdSense request.
*/
public function check_adsense_request() {
check_ajax_referer( 'rank-math-ajax-nonce', 'security' );
$this->has_cap_ajax( 'analytics' );
$dates = Base::get_dates();
$success = Adsense::get_adsense(
[
'start_date' => $dates['start_date'],
'end_date' => $dates['end_date'],
]
);
if ( is_wp_error( $success ) ) {
$this->error( esc_html__( 'Data import will not work for this service as sufficient permissions are not given.', 'rank-math-pro' ) );
}
$this->success();
}
/**
* Save adsense profile.
*/
public function save_adsense_account() {
$this->verify_nonce( 'rank-math-ajax-nonce' );
$this->has_cap_ajax( 'analytics' );
$prev = get_option( 'rank_math_google_analytic_options', [] );
$value = get_option( 'rank_math_google_analytic_options', [] );
$value['adsense_id'] = Param::post( 'accountID' );
// Test AdSense connection request.
if ( ! empty( $value['adsense_id'] ) ) {
$dates = Base::get_dates();
$request = Adsense::get_adsense(
[
'account_id' => $value['adsense_id'],
'start_date' => $dates['start_date'],
'end_date' => $dates['end_date'],
]
);
if ( is_wp_error( $request ) ) {
$this->error( esc_html__( 'Data import will not work for this service as sufficient permissions are not given.', 'rank-math' ) );
}
}
update_option( 'rank_math_google_analytic_options', $value );
$days = Param::get( 'days', 90, FILTER_VALIDATE_INT );
Workflow::do_workflow(
'adsense',
$days,
$prev,
$value
);
$this->success();
}
}

View File

@@ -0,0 +1,675 @@
<?php
/**
* Analytics module.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\KB;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
use MyThemeShop\Helpers\Param;
use MyThemeShop\Helpers\DB as DB_Helper;
// Analytics.
use RankMathPro\Google\Adsense;
use RankMath\Google\Permissions;
use RankMath\Google\Authentication;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Analytics\Workflow\Jobs;
use RankMathPro\Analytics\Workflow\Workflow;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
use RankMathPro\Analytics\DB;
defined( 'ABSPATH' ) || exit;
/**
* Analytics class.
*/
class Analytics {
use Hooker;
/**
* Constructor.
*/
public function __construct() {
$this->action( 'rank_math/admin/enqueue_scripts', 'enqueue_analytics' );
$this->action( 'rank_math/analytics/options/console', 'add_country_dropdown3' );
$this->action( 'rank_math/analytics/options/analytics', 'add_country_dropdown2' );
$this->action( 'update_option_rank_math_analytics_last_updated', 'send_summary' );
$this->action( 'rank_math/admin/settings/analytics', 'add_new_settings' );
$this->filter( 'rank_math/analytics/schedule_gap', 'schedule_gap' );
$this->filter( 'rank_math/analytics/fetch_gap', 'fetch_gap' );
$this->filter( 'rank_math/analytics/max_days_allowed', 'data_retention_period' );
$this->filter( 'rank_math/analytics/options/cache_control/description', 'change_description' );
$this->filter( 'rank_math/analytics/check_all_services', 'check_all_services' );
$this->filter( 'rank_math/analytics/user_preference', 'change_user_preference' );
$this->action( 'template_redirect', 'local_js_endpoint' );
$this->filter( 'rank_math/analytics/gtag_config', 'gtag_config' );
$this->filter( 'rank_math/status/rank_math_info', 'google_permission_info' );
$this->filter( 'rank_math/analytics/gtag', 'gtag' );
$this->filter( 'rank_math/analytics/pre_filter_data', 'filter_winning_losing_posts', 10, 3 );
$this->filter( 'rank_math/analytics/pre_filter_data', 'filter_winning_keywords', 10, 3 );
$this->action( 'cmb2_save_options-page_fields_rank-math-options-general_options', 'sync_global_settings', 25, 2 );
$this->filter( 'rank_math/metabox/post/values', 'add_metadata', 10, 2 );
$this->filter( 'rank_math/analytics/date_exists_tables', 'date_exists_tables', 10 );
if ( Helper::has_cap( 'analytics' ) ) {
$this->action( 'rank_math/admin_bar/items', 'admin_bar_items', 11 );
$this->action( 'rank_math_seo_details', 'post_column_search_traffic' );
}
if ( Helper::can_add_frontend_stats() ) {
$this->action( 'wp_enqueue_scripts', 'enqueue' );
}
Posts::get();
Keywords::get();
Jobs::get();
Workflow::get();
new Pageviews();
new Summary();
new Ajax();
new Email_Reports();
new Url_Inspection();
}
/**
* Enqueue Frontend stats script.
*/
public function enqueue() {
if ( ! is_singular() || is_admin() || is_preview() || Helper::is_divi_frontend_editor() ) {
return;
}
$uri = untrailingslashit( plugin_dir_url( __FILE__ ) );
wp_enqueue_style( 'rank-math-analytics-pro-stats', $uri . '/assets/css/admin-bar.css', [ 'rank-math-analytics-stats' ], rank_math_pro()->version );
wp_enqueue_script( 'rank-math-analytics-pro-stats', $uri . '/assets/js/admin-bar.js', [ 'rank-math-analytics-stats' ], rank_math_pro()->version, true );
Helper::add_json( 'dateFormat', get_option( 'date_format' ) );
}
/**
* Add localized data to use in the Post Editor.
*
* @param array $values Aray of localized data.
*
* @return array
*/
public function add_metadata( $values ) {
$values['isAnalyticsConnected'] = \RankMath\Google\Analytics::is_analytics_connected();
return $values;
}
/**
* Change user perference.
*
* @param array $preference Array of preference.
* @return array
*/
public function change_user_preference( $preference ) {
Helper::add_json( 'isAdsenseConnected', ! empty( Adsense::get_adsense_id() ) );
Helper::add_json( 'isLinkModuleActive', Helper::is_module_active( 'link-counter' ) );
Helper::add_json( 'isSchemaModuleActive', Helper::is_module_active( 'rich-snippet' ) );
Helper::add_json( 'isAnalyticsConnected', \RankMath\Google\Analytics::is_analytics_connected() );
Helper::add_json( 'dateFormat', get_option( 'date_format' ) );
$preference['topKeywords']['ctr'] = false;
$preference['topKeywords']['ctr'] = false;
$preference['performance']['clicks'] = false;
return $preference;
}
/**
* Data rentention days.
*
* @return int
*/
public function data_retention_period() {
return 'pro' === Admin_Helper::get_user_plan() ? 180 : 1000;
}
/**
* Data retrival job gap in seconds.
*
* @return int
*/
public function schedule_gap() {
return 10;
}
/**
* Data retrival fetch gap in days.
*
* @return int
*/
public function fetch_gap() {
return 3;
}
/**
* Fetch adsense account.
*
* @param array $result Result array.
* @return array
*/
public function check_all_services( $result ) {
$result['adsenseAccounts'] = Adsense::get_adsense_accounts();
return $result;
}
/**
* Add admin bar item.
*
* @param Admin_Bar_Menu $menu Menu class instance.
*/
public function admin_bar_items( $menu ) {
$post_types = Helper::get_accessible_post_types();
unset( $post_types['attachment'] );
if ( is_singular( $post_types ) && Helper::is_post_indexable( get_the_ID() ) ) {
$menu->add_sub_menu(
'post_analytics',
[
'title' => esc_html__( 'Post Analytics', 'rank-math-pro' ),
'href' => Helper::get_admin_url( 'analytics#/single/' . get_the_ID() ),
'meta' => [ 'title' => esc_html__( 'Analytics Report', 'rank-math-pro' ) ],
'priority' => 20,
]
);
}
}
/**
* Enqueue scripts for the metabox.
*/
public function enqueue_analytics() {
$screen = get_current_screen();
if ( 'rank-math_page_rank-math-analytics' !== $screen->id ) {
return;
}
$url = RANK_MATH_PRO_URL . 'includes/modules/analytics/assets/';
wp_enqueue_style(
'rank-math-pro-analytics',
$url . 'css/stats.css',
null,
rank_math_pro()->version
);
wp_enqueue_script(
'rank-math-pro-analytics',
$url . 'js/stats.js',
[
'wp-components',
'wp-element',
'wp-i18n',
'wp-date',
'wp-html-entities',
'wp-api-fetch',
'rank-math-analytics',
],
rank_math_pro()->version,
true
);
}
/**
* Add country dropdown.
*/
public function add_country_dropdown3() {
$profile = wp_parse_args(
get_option( 'rank_math_google_analytic_profile' ),
[
'profile' => '',
'country' => 'all',
]
);
?>
<div class="cmb-row-col">
<label for="site-console-country"><?php esc_html_e( 'Country', 'rank-math-pro' ); ?></label>
<select class="cmb2_select site-console-country notrack" name="site-console-country" id="site-console-country" disabled="disabled">
<?php foreach ( ProAdminHelper::choices_countries_3() as $code => $label ) : ?>
<option value="<?php echo esc_attr( $code ); ?>"<?php selected( $profile['country'], $code ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php
}
/**
* Add country dropdown.
*/
public function add_country_dropdown2() {
$analytics = $this->get_settings();
?>
<div class="cmb-row-col country-option">
<label for="site-analytics-country"><?php esc_html_e( 'Country', 'rank-math-pro' ); ?></label>
<select class="cmb2_select site-analytics-country notrack" name="site-analytics-country" id="site-analytics-country" disabled="disabled">
<?php foreach ( ProAdminHelper::choices_countries() as $code => $label ) : ?>
<option value="<?php echo esc_attr( $code ); ?>"<?php selected( $analytics['country'], $code ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php
}
/**
* Get Analytics settings.
*
* @return array
*/
public function get_settings() {
return wp_parse_args(
get_option( 'rank_math_google_analytic_options' ),
[
'adsense_id' => '',
'account_id' => '',
'property_id' => '',
'view_id' => '',
'country' => 'all',
'install_code' => false,
'anonymize_ip' => false,
'local_ga_js' => false,
'exclude_loggedin' => false,
]
);
}
/**
* Send analytics summary to RankMath.com.
*/
public function send_summary() {
if ( ! Helper::get_settings( 'general.sync_global_setting' ) ) {
return;
}
$registered = Admin_Helper::get_registration_data();
if ( $registered && isset( $registered['username'] ) && isset( $registered['api_key'] ) ) {
Stats::get()->set_date_range( '-30 days' );
$stats = Stats::get()->get_analytics_summary();
\RankMathPro\Admin\Api::get()->send_summary(
[
'username' => $registered['username'],
'api_key' => $registered['api_key'],
'site_url' => esc_url( home_url() ),
'impressions' => array_values( $stats['impressions'] ),
'clicks' => array_values( $stats['clicks'] ),
'keywords' => array_values( $stats['keywords'] ),
'pageviews' => isset( $stats['pageviews'] ) && is_array( $stats['pageviews'] ) ? array_values( $stats['pageviews'] ) : [],
'adsense' => isset( $stats['adsense'] ) && is_array( $stats['adsense'] ) ? array_values( $stats['adsense'] ) : [],
]
);
}
}
/**
* Change option description.
*/
public function change_description() {
return __( 'Enter the number of days to keep Analytics data in your database. The maximum allowed days are 180. Though, 2x data will be stored in the DB for calculating the difference properly.', 'rank-math-pro' );
}
/**
* Add new settings.
*
* @param object $cmb CMB2 instance.
*/
public function add_new_settings( $cmb ) {
if ( ! Authentication::is_authorized() ) {
return;
}
$type = ! ProAdminHelper::is_business_plan() ? 'hidden' : 'toggle';
$field_ids = wp_list_pluck( $cmb->prop( 'fields' ), 'id' );
$fields_position = array_search( 'console_caching_control', array_keys( $field_ids ), true ) + 1;
$cmb->add_field(
[
'id' => 'sync_global_setting',
'type' => $type,
'name' => esc_html__( 'Monitor SEO Performance', 'rank-math-pro' ),
'desc' => sprintf(
/* translators: Link to kb article */
wp_kses_post( __( 'This option allows you to monitor the SEO performance of all of your sites in one centralized dashboard on RankMath.com, so you can check up on sites at a glance. <a href="%1$s" target="_blank">Learn more</a>.', 'rank-math-pro' ) ),
KB::get( 'help-analytics', 'Options Panel Analytics Tab Monitor Performance' )
),
'default' => 'off',
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'google_updates',
'type' => $type,
'name' => esc_html__( 'Google Core Updates in the Graphs', 'rank-math-pro' ),
'desc' => sprintf(
/* translators: Link to kb article */
__( 'This option allows you to show %s in the Analytics graphs.', 'rank-math-pro' ),
'<a href="' . KB::get( 'google-updates', 'Google Core Updates in the Graphs' ) . '" target="_blank">' . __( 'Google Core Updates', 'rank-math-pro' ) . '</a>'
),
'default' => 'on',
],
++$fields_position
);
}
/**
* Check if certain fields got updated.
*
* @param int $object_id The ID of the current object.
* @param array $updated Array of field ids that were updated.
* Will only include field ids that had values change.
*/
public function sync_global_settings( $object_id, $updated ) {
if ( in_array( 'sync_global_setting', $updated, true ) ) {
\RankMathPro\Admin\Api::get()->sync_setting(
cmb2_get_option( $object_id, 'sync_global_setting' )
);
$this->send_summary();
}
}
/**
* Get local Analytics JS URL if the option is turned on.
*
* @return mixed
*/
public function get_local_gtag_js_url() {
$settings = $this->get_settings();
$validator_key = 'rank_math_local_ga_js_validator_' . md5( $settings['property_id'] );
$validator = get_transient( $validator_key );
if ( ! is_string( $validator ) || empty( $validator ) ) {
$validator = '1';
}
return add_query_arg( 'local_ga_js', $validator, trailingslashit( home_url() ) );
}
/**
* Serve Analytics JS from local cache if the option is turned on.
*
* @return void
*/
public function local_js_endpoint() {
if ( Param::get( 'local_ga_js' ) && $this->get_settings()['local_ga_js'] && $this->get_local_ga_js_contents() ) {
header( 'Content-Type: application/javascript' );
header( 'Cache-Control: max-age=604800, public' );
echo $this->get_local_ga_js_contents(); // phpcs:ignore
exit;
}
}
/**
* Get local cache of GA JS file contents or fetch new data.
*
* @param boolean $force_update Force update transient now.
* @return string
*/
public function get_local_ga_js_contents( $force_update = false ) {
$settings = $this->get_settings();
$cache_key = 'rank_math_local_ga_js_' . md5( $settings['property_id'] );
$validator_key = 'rank_math_local_ga_js_validator_' . md5( $settings['property_id'] );
$validator = md5( $cache_key . time() );
$stored = get_transient( $cache_key );
if ( false !== $stored && ! $force_update ) {
return $stored;
}
$response = wp_remote_get( 'https://www.googletagmanager.com/gtag/js?id=' . $settings['property_id'] );
if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
set_transient( $cache_key, '', 12 * HOUR_IN_SECONDS );
return '';
}
$contents = wp_remote_retrieve_body( $response );
set_transient( $cache_key, $contents, 12 * HOUR_IN_SECONDS );
set_transient( $validator_key, $validator, 12 * HOUR_IN_SECONDS );
return $contents;
}
/**
* Filter gtag.js config array.
*
* @param array $config Config parameters.
* @return array
*/
public function gtag_config( $config ) {
$settings = $this->get_settings();
if ( ! empty( $settings['anonymize_ip'] ) ) {
$config[] = "'anonymize_ip': true";
}
return $config;
}
/**
* Filter function to add Google permissions used in Pro.
*
* @param array $data Array of System status data.
*/
public function google_permission_info( $data ) {
$data['fields']['permissions']['value'] = array_merge(
$data['fields']['permissions']['value'],
[
esc_html__( 'AdSense', 'rank-math-pro' ) => Permissions::get_status_text( Permissions::has_adsense() ),
esc_html__( 'Analytics', 'rank-math-pro' ) => Permissions::get_status_text( Permissions::has_analytics() ),
]
);
ksort( $data['fields']['permissions']['value'] );
return $data;
}
/**
* Filter inline JS & URL for gtag.js.
*
* @param array $gtag_data Array containing URL & inline code for the gtag script.
* @return array
*/
public function gtag( $gtag_data ) {
if ( is_admin() ) {
return $gtag_data;
}
$settings = $this->get_settings();
if ( empty( $settings['install_code'] ) ) {
return $gtag_data;
}
if ( ! empty( $settings['local_ga_js'] ) ) {
$gtag_data['url'] = $this->get_local_gtag_js_url();
}
return $gtag_data;
}
/**
* Filter winning and losing posts if needed.
*
* @param null $null Null.
* @param array $data Analytics data array.
* @param array $args Query arguments.
*
* @return mixed
*/
public function filter_winning_losing_posts( $null, $data, $args ) {
$order_by_field = $args['orderBy'];
$type = $args['type'];
$objects = $args['objects'];
if ( ! in_array( $type, [ 'win', 'lose' ], true ) ) {
return $null;
}
// Filter array by $type value.
$order_by_position = in_array( $order_by_field, [ 'diffPosition', 'position' ], true ) ? true : false;
if ( ( 'win' === $type && $order_by_position ) || ( 'lose' === $type && ! $order_by_position ) ) {
$data = array_filter(
$data,
function( $row ) use ( $order_by_field, $objects ) {
if ( $objects ) {
// Show Winning posts if difference is 80 or less.
return $row[ $order_by_field ] < 0 && $row[ $order_by_field ] > -80;
}
return $row[ $order_by_field ] < 0;
}
);
} elseif ( ( 'lose' === $type && $order_by_position ) || ( 'win' === $type && ! $order_by_position ) ) {
$data = array_filter(
$data,
function( $row ) use ( $order_by_field ) {
return $row[ $order_by_field ] > 0;
}
);
}
$data = $this->finalize_filtered_data( $data, $args );
return $data;
}
/**
* Filter winning keywords if needed.
*
* @param null $null Null.
* @param array $data Analytics data array.
* @param array $args Query arguments.
*
* @return mixed
*/
public function filter_winning_keywords( $null, $data, $args ) {
$order_by_field = $args['orderBy'];
$dimension = $args['dimension'];
if ( 'query' !== $dimension || 'diffPosition' !== $order_by_field || 'ASC' !== $args['order'] ) {
return $null;
}
// Filter array by $type value.
$data = array_filter(
$data,
function( $row ) use ( $order_by_field ) {
return $row[ $order_by_field ] < 0 && $row[ $order_by_field ] > -80;
}
);
$data = $this->finalize_filtered_data( $data, $args );
return $data;
}
/**
* Sort & limit keywords according to the args.
*
* @param array $data Data rows.
* @param array $args Query args.
*
* @return array
*/
private function finalize_filtered_data( $data, $args ) {
if ( ! empty( $args['order'] ) ) {
$sort_base_arr = array_column( $data, $args['orderBy'], $args['dimension'] );
array_multisort( $sort_base_arr, 'ASC' === $args['order'] ? SORT_ASC : SORT_DESC, $data );
}
$data = array_slice( $data, $args['offset'], $args['perpage'], true );
return $data;
}
/**
* Add search traffic value in seo detail of post lists
*
* @param int $post_id object Id.
*/
public function post_column_search_traffic( $post_id ) {
if ( ! Authentication::is_authorized() ) {
return;
}
$analytics = get_option( 'rank_math_google_analytic_options' );
$analytics_connected = ! empty( $analytics ) && ! empty( $analytics['view_id'] );
static $traffic_data;
if ( null === $traffic_data ) {
$post_ids = $this->get_queried_post_ids();
if ( empty( $post_ids ) ) {
$traffic_data = [];
return;
}
$traffic_data = $analytics_connected ? Pageviews::get_traffic_by_object_ids( $post_ids ) : Pageviews::get_impressions_by_object_ids( $post_ids );
}
if ( ! isset( $traffic_data[ $post_id ] ) ) {
return;
}
?>
<span class="rank-math-column-display rank-math-search-traffic">
<strong>
<?php
$analytics_connected
? esc_html_e( 'Search Traffic:', 'rank-math-pro' )
: esc_html_e( 'Search Impression:', 'rank-math-pro' );
?>
</strong>
<?php echo esc_html( number_format( $traffic_data[ $post_id ] ) ); ?>
</span>
<?php
}
/**
* Extend the date_exists() function to include the additional tables.
*
* @param string $tables Tables.
* @return string
*/
public function date_exists_tables( $tables ) {
$tables['analytics'] = DB_Helper::check_table_exists( 'rank_math_analytics_ga' ) ? 'rank_math_analytics_ga' : '';
$tables['adsense'] = DB_Helper::check_table_exists( 'rank_math_analytics_adsense' ) ? 'rank_math_analytics_adsense' : '';
return $tables;
}
/**
* Get queried post ids.
*/
private function get_queried_post_ids() {
global $wp_query;
if ( empty( $wp_query->posts ) ) {
return false;
}
$post_ids = array_filter(
array_map(
function( $post ) {
return isset( $post->ID ) ? $post->ID : '';
},
$wp_query->posts
)
);
return $post_ids;
}
}

View File

@@ -0,0 +1,651 @@
<?php
/**
* The Analytics module database operations
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\Helper;
use RankMath\Admin\Admin_Helper;
use RankMath\Google\Analytics as Analytics_Free;
use RankMath\Analytics\Stats;
use RankMathPro\Google\Adsense;
use RankMathPro\Analytics\Keywords;
use MyThemeShop\Helpers\Str;
use MyThemeShop\Database\Database;
defined( 'ABSPATH' ) || exit;
/**
* DB class.
*/
class DB {
/**
* Get any table.
*
* @param string $table_name Table name.
*
* @return \MyThemeShop\Database\Query_Builder
*/
public static function table( $table_name ) {
return Database::table( $table_name );
}
/**
* Get console data table.
*
* @return \MyThemeShop\Database\Query_Builder
*/
public static function analytics() {
return Database::table( 'rank_math_analytics_gsc' );
}
/**
* Get analytics data table.
*
* @return \MyThemeShop\Database\Query_Builder
*/
public static function traffic() {
return Database::table( 'rank_math_analytics_ga' );
}
/**
* Get adsense data table.
*
* @return \MyThemeShop\Database\Query_Builder
*/
public static function adsense() {
return Database::table( 'rank_math_analytics_adsense' );
}
/**
* Get objects table.
*
* @return \MyThemeShop\Database\Query_Builder
*/
public static function objects() {
return Database::table( 'rank_math_analytics_objects' );
}
/**
* Get inspections table.
*
* @return \MyThemeShop\Database\Query_Builder
*/
public static function inspections() {
return Database::table( 'rank_math_analytics_inspections' );
}
/**
* Get links table.
*
* @return \MyThemeShop\Database\Query_Builder
*/
public static function links() {
return Database::table( 'rank_math_internal_meta' );
}
/**
* Get keywords table.
*
* @return \MyThemeShop\Database\Query_Builder
*/
public static function keywords() {
return Database::table( 'rank_math_analytics_keyword_manager' );
}
/**
* Delete console and analytics data.
*
* @param int $days Decide whether to delete all or delete 90 days data.
*/
public static function delete_by_days( $days ) {
if ( -1 === $days ) {
self::traffic()->truncate();
self::analytics()->truncate();
} else {
$start = date_i18n( 'Y-m-d H:i:s', strtotime( '-1 days' ) );
$end = date_i18n( 'Y-m-d H:i:s', strtotime( '-' . $days . ' days' ) );
self::traffic()->whereBetween( 'created', [ $end, $start ] )->delete();
self::analytics()->whereBetween( 'created', [ $end, $start ] )->delete();
}
self::purge_cache();
return true;
}
/**
* Delete record for comparison.
*/
public static function delete_data_log() {
$days = Helper::get_settings( 'general.console_caching_control', 90 );
$start = date_i18n( 'Y-m-d H:i:s', strtotime( '-' . ( $days * 2 ) . ' days' ) );
self::traffic()->where( 'created', '<', $start )->delete();
self::analytics()->where( 'created', '<', $start )->delete();
}
/**
* Purge SC transient
*/
public static function purge_cache() {
$table = Database::table( 'options' );
$table->whereLike( 'option_name', 'rank_math_analytics_data_info' )->delete();
$table->whereLike( 'option_name', 'tracked_keywords_summary' )->delete();
$table->whereLike( 'option_name', 'top_keywords' )->delete();
$table->whereLike( 'option_name', 'top_keywords_graph' )->delete();
$table->whereLike( 'option_name', 'winning_keywords' )->delete();
$table->whereLike( 'option_name', 'losing_keywords' )->delete();
$table->whereLike( 'option_name', 'posts_summary' )->delete();
$table->whereLike( 'option_name', 'winning_posts' )->delete();
$table->whereLike( 'option_name', 'losing_posts' )->delete();
wp_cache_flush();
}
/**
* Get search console table info (for pro version only).
*
* @return array
*/
public static function info() {
global $wpdb;
$key = 'rank_math_analytics_data_info';
$data = get_transient( $key );
if ( false !== $data ) {
return $data;
}
$days = 0;
$rows = self::get_total_rows();
$size = $wpdb->get_var( 'SELECT SUM((data_length + index_length)) AS size FROM information_schema.TABLES WHERE table_schema="' . $wpdb->dbname . '" AND table_name IN ( ' . '"' . $wpdb->prefix . 'rank_math_analytics_ga", "' . $wpdb->prefix . 'rank_math_analytics_adsense"' . ' )' ); // phpcs:ignore
$data = compact( 'days', 'rows', 'size' );
set_transient( $key, $data, DAY_IN_SECONDS );
return $data;
}
/**
* Get total row count of analytics and adsense tables
*
* @return int total row count
*/
public static function get_total_rows() {
$rows = 0;
if ( Analytics_Free::is_analytics_connected() ) {
$rows += self::table( 'rank_math_analytics_ga' )
->selectCount( 'id' )
->getVar();
}
if ( Adsense::is_adsense_connected() ) {
$rows += self::table( 'rank_math_analytics_adsense' )
->selectCount( 'id' )
->getVar();
}
return $rows;
}
/**
* Has data pulled.
*
* @return boolean
*/
public static function has_data() {
static $rank_math_gsc_has_data;
if ( isset( $rank_math_gsc_has_data ) ) {
return $rank_math_gsc_has_data;
}
$id = self::objects()
->select( 'id' )
->limit( 1 )
->getVar();
$rank_math_gsc_has_data = $id > 0 ? true : false;
return $rank_math_gsc_has_data;
}
/**
* Add a new record into objects table.
*
* @param array $args Values to insert.
*
* @return bool|int
*/
public static function add_object( $args = [] ) {
if ( empty( $args ) ) {
return false;
}
$args = wp_parse_args(
$args,
[
'created' => current_time( 'mysql' ),
'page' => '',
'object_type' => 'post',
'object_subtype' => 'post',
'object_id' => 0,
'primary_key' => '',
'seo_score' => 0,
'page_score' => 0,
'is_indexable' => false,
'schemas_in_use' => '',
]
);
return self::objects()->insert( $args, [ '%s', '%s', '%s', '%s', '%d', '%s', '%d', '%d', '%d', '%s' ] );
}
/**
* Add/Update a record into/from objects table.
*
* @param array $args Values to update.
*
* @return bool|int
*/
public static function update_object( $args = [] ) {
if ( empty( $args ) ) {
return false;
}
// If object exists, try to update.
$old_id = absint( $args['id'] );
if ( ! empty( $old_id ) ) {
unset( $args['id'] );
$updated = self::objects()->set( $args )
->where( 'id', $old_id )
->where( 'object_id', absint( $args['object_id'] ) )
->update();
if ( ! empty( $updated ) ) {
return $old_id;
}
}
// In case of new object or failed to update, try to add.
return self::add_object( $args );
}
/**
* Add console records.
*
* @param string $date Date of creation.
* @param array $rows Data rows to insert.
*/
public static function add_query_page_bulk( $date, $rows ) {
$chunks = array_chunk( $rows, 50 );
foreach ( $chunks as $chunk ) {
self::bulk_insert_query_page_data( $date . ' 00:00:00', $chunk );
}
}
/**
* Bulk inserts records into a table using WPDB. All rows must contain the same keys.
*
* @param string $date Date.
* @param array $rows Rows to insert.
*/
public static function bulk_insert_query_page_data( $date, $rows ) {
global $wpdb;
$data = [];
$placeholders = [];
$columns = [
'created',
'query',
'page',
'clicks',
'impressions',
'position',
'ctr',
];
$columns = '`' . implode( '`, `', $columns ) . '`';
$placeholder = [
'%s',
'%s',
'%s',
'%d',
'%d',
'%d',
'%d',
];
// Start building SQL, initialise data and placeholder arrays.
$sql = "INSERT INTO `{$wpdb->prefix}rank_math_analytics_gsc` ( $columns ) VALUES\n";
// Build placeholders for each row, and add values to data array.
foreach ( $rows as $row ) {
if (
$row['position'] > self::get_position_filter() ||
Str::contains( '?', $row['page'] )
) {
continue;
}
$data[] = $date;
$data[] = $row['query'];
$data[] = str_replace( Helper::get_home_url(), '', self::remove_hash( $row['page'] ) );
$data[] = $row['clicks'];
$data[] = $row['impressions'];
$data[] = $row['position'];
$data[] = $row['ctr'];
$placeholders[] = '(' . implode( ', ', $placeholder ) . ')';
}
// Don't run insert with empty dataset, return 0 since no rows affected.
if ( empty( $data ) ) {
return 0;
}
// Stitch all rows together.
$sql .= implode( ",\n", $placeholders );
// Run the query. Returns number of affected rows.
return $wpdb->query( $wpdb->prepare( $sql, $data ) ); // phpcs:ignore
}
/**
* Add analytic records.
*
* @param string $date Date of creation.
* @param array $rows Data rows to insert.
*/
public static function add_analytics_bulk( $date, $rows ) {
$chunks = array_chunk( $rows, 50 );
foreach ( $chunks as $chunk ) {
self::bulk_insert_analytics_data( $date . ' 00:00:00', $chunk );
}
}
/**
* Bulk inserts records into a table using WPDB. All rows must contain the same keys.
*
* @param string $date Date.
* @param array $rows Rows to insert.
*/
public static function bulk_insert_analytics_data( $date, $rows ) {
global $wpdb;
$data = [];
$placeholders = [];
$columns = [
'created',
'page',
'pageviews',
'visitors',
];
$columns = '`' . implode( '`, `', $columns ) . '`';
$placeholder = [
'%s',
'%s',
'%d',
'%d',
];
// Start building SQL, initialise data and placeholder arrays.
$sql = "INSERT INTO `{$wpdb->prefix}rank_math_analytics_ga` ( $columns ) VALUES\n";
// Build placeholders for each row, and add values to data array.
foreach ( $rows as $row ) {
$page = '';
$pageviews = '';
$visitors = '';
if ( ! isset( $row['dimensionValues'] ) ) {
if ( empty( $row['dimensions'][1] ) || Str::contains( '?', $row['dimensions'][1] ) ) {
continue;
}
$page = $row['dimensions'][2] . $row['dimensions'][1];
$pageviews = $row['metrics'][0]['values'][0];
$visitors = $row['metrics'][0]['values'][1];
} else {
if ( empty( $row['dimensionValues'][1]['value'] ) || Str::contains( '?', $row['dimensionValues'][1]['value'] ) ) {
continue;
}
$page = $row['dimensionValues'][0]['value'] . $row['dimensionValues'][1]['value'];
$pageviews = $row['metricValues'][0]['value'];
$visitors = $row['metricValues'][1]['value'];
}
if ( $page && $pageviews && $visitors ) {
$page = ( is_ssl() ? 'https' : 'http' ) . '://' . $page;
$data[] = $date;
$data[] = Stats::get_relative_url( self::remove_hash( $page ) );
$data[] = $pageviews;
$data[] = $visitors;
$placeholders[] = '(' . implode( ', ', $placeholder ) . ')';
}
}
if ( empty( $placeholders ) ) {
return 0;
}
// Stitch all rows together.
$sql .= implode( ",\n", $placeholders );
// Run the query. Returns number of affected rows.
return $wpdb->query( $wpdb->prepare( $sql, $data ) ); // phpcs:ignore
}
/**
* Remove hash part from Url.
*
* @param string $url Url to process.
* @return string
*/
public static function remove_hash( $url ) {
if ( ! Str::contains( '#', $url ) ) {
return $url;
}
$url = \explode( '#', $url );
return $url[0];
}
/**
* Add adsense records.
*
* @param array $rows Data rows to insert.
*/
public static function add_adsense( $rows ) {
if ( ! \MyThemeShop\Helpers\DB::check_table_exists( 'rank_math_analytics_adsense' ) ) {
return;
}
foreach ( $rows as $row ) {
$date = $row['cells'][0]['value'];
$earnings = floatval( $row['cells'][1]['value'] );
self::adsense()
->insert(
[
'created' => $date . ' 00:00:00',
'earnings' => $earnings,
],
[ '%s', '%f' ]
);
}
}
/**
* Get position filter.
*
* @return int
*/
private static function get_position_filter() {
$number = apply_filters( 'rank_math/analytics/position_limit', false );
if ( false === $number ) {
return 100;
}
return $number;
}
/**
* Bulk inserts records into a keyword table using WPDB. All rows must contain the same keys.
*
* @param array $rows Rows to insert.
*/
public static function bulk_insert_query_focus_keyword_data( $rows ) {
$registered = Admin_Helper::get_registration_data();
if ( ! $registered || empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
return false;
}
// Check remain keywords count can be added.
$total_keywords = Keywords::get()->get_tracked_keywords_count();
$new_keywords = Keywords::get()->extract_addable_track_keyword( implode( ',', $rows ) );
$keywords_count = count( $new_keywords );
if ( $keywords_count <= 0 ) {
return false;
}
$summary = Keywords::get()->get_tracked_keywords_quota();
$remain = $summary['available'] - $total_keywords;
if ( $remain <= 0 ) {
return false;
}
// Add remaining limit keywords.
$new_keywords = array_slice( $new_keywords, 0, $remain );
$data = [];
$placeholders = [];
$columns = [
'keyword',
'collection',
'is_active',
];
$columns = '`' . implode( '`, `', $columns ) . '`';
$placeholder = [
'%s',
'%s',
'%s',
];
// Start building SQL, initialise data and placeholder arrays.
global $wpdb;
$sql = "INSERT INTO `{$wpdb->prefix}rank_math_analytics_keyword_manager` ( $columns ) VALUES\n";
// Build placeholders for each row, and add values to data array.
foreach ( $new_keywords as $new_keyword ) {
$data[] = $new_keyword;
$data[] = 'uncategorized';
$data[] = 1;
$placeholders[] = '(' . implode( ', ', $placeholder ) . ')';
}
// Stitch all rows together.
$sql .= implode( ",\n", $placeholders );
// Run the query. Returns number of affected rows.
$count = $wpdb->query( $wpdb->prepare( $sql, $data ) ); // phpcs:ignore
$total_keywords = Keywords::get()->get_tracked_keywords_count();
$response = \RankMathPro\Admin\Api::get()->keywords_info( $registered['username'], $registered['api_key'], $total_keywords );
if ( $response ) {
update_option( 'rank_math_keyword_quota', $response );
}
return $count;
}
/**
* Get stats from DB for "Presence on Google" widget:
* All unique coverage_state values and their counts.
*/
public static function get_presence_stats() {
$results = self::inspections()
->select(
[
'coverage_state',
'COUNT(*)' => 'count',
]
)
->groupBy( 'coverage_state' )
->orderBy( 'count', 'DESC' )
->get();
$results = array_map(
'absint',
array_combine(
array_column( $results, 'coverage_state' ),
array_column( $results, 'count' )
)
);
return $results;
}
/**
* Get stats from DB for "Top Statuses" widget.
*/
public static function get_status_stats() {
$statuses = [
'VERDICT_UNSPECIFIED',
'PASS',
'PARTIAL',
'FAIL',
'NEUTRAL',
];
// Get all unique index_verdict values and their counts.
$index_statuses = self::inspections()
->select(
[
'index_verdict',
'COUNT(*)' => 'count',
]
)
->groupBy( 'index_verdict' )
->orderBy( 'count', 'DESC' )
->get();
$results = array_fill_keys( $statuses, 0 );
foreach ( $index_statuses as $status ) {
if ( empty( $status->index_verdict ) ) {
continue;
}
$results[ $status->index_verdict ] += $status->count;
}
return $results;
}
/**
* Get stats from DB for "Top Statuses" widget.
*/
public static function get_index_verdict( $page ) {
$verdict = self::inspections()
->select()
->where( 'page', '=', $page )
->one();
return (array) $verdict;
}
}

View File

@@ -0,0 +1,542 @@
<?php
/**
* Pro SEO Reports in Email.
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\KB;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
use RankMath\Admin\Admin_Helper;
use RankMath\Google\Authentication;
use RankMath\Analytics\Email_Reports as Email_Reports_Base;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
use RankMathPro\Analytics\Keywords;
use RankMathPro\Analytics\Posts;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* DB class.
*/
class Email_Reports {
use Hooker;
/**
* Path to the views folder.
*
* @var string
*/
public $views_path = '';
/**
* URL of the module's assets folder.
*
* @var string
*/
public $assets_url = '';
/**
* The constructor.
*/
public function __construct() {
$this->hooks();
}
/**
* Add filter & action hooks.
*
* @return void
*/
public function hooks() {
$this->views_path = dirname( __FILE__ ) . '/views/email-reports/';
$this->assets_url = plugin_dir_url( __FILE__ ) . 'assets/';
// CMB hooks.
$this->action( 'rank_math/admin/settings/analytics', 'add_options' );
// WP hooks.
$this->filter( 'admin_post_rank_math_save_wizard', 'save_wizard' );
// Rank Math hooks.
$this->filter( 'rank_math/analytics/email_report_template_paths', 'add_template_path' );
$this->filter( 'rank_math/analytics/email_report_variables', 'add_variables' );
$this->filter( 'rank_math/analytics/email_report_parameters', 'email_parameters' );
$this->filter( 'rank_math/analytics/email_report_image_atts', 'replace_logo', 10, 2 );
$this->filter( 'rank_math/analytics/email_report_periods', 'frequency_periods' );
$this->action( 'rank_math/analytics/options/wizard_after_email_report', 'wizard_options' );
}
/**
* Output CSS for required for the Pro reports.
*
* @return void
*/
public function add_pro_css() {
$this->template_part( 'pro-style' );
}
/**
* Replace logo image in template.
*
* @param array $atts All original attributes.
* @param string $url Image URL or identifier.
*
* @return array
*/
public function replace_logo( $atts, $url ) {
if ( 'report-logo.png' !== $url ) {
return $atts;
}
$atts['src'] = '###LOGO_URL###';
$atts['alt'] = '###LOGO_ALT###';
return $atts;
}
/**
* Add Pro variables.
*
* @param array $variables Original variables.
* @return array
*/
public function add_variables( $variables ) {
$variables['pro_assets_url'] = $this->assets_url;
$variables['logo_url'] = Email_Reports_Base::get_setting( 'logo', $this->get_logo_url_default() );
$variables['logo_alt'] = __( 'Logo', 'rank-math-pro' );
$image_id = Email_Reports_Base::get_setting( 'logo_id', 0 );
if ( $image_id ) {
$alt = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
if ( $alt ) {
$variables['logo_alt'] = $alt;
}
}
$variables['header_background'] = Email_Reports_Base::get_setting( 'header_background', 'linear-gradient(90deg, rgba(112,83,181,1) 0%, rgba(73,153,210,1) 100%)' );
$variables['top_html'] = wp_kses_post( wpautop( Email_Reports_Base::get_setting( 'top_text', '' ) ) );
$variables['footer_html'] = wp_kses_post( Email_Reports_Base::get_setting( 'footer_text', $this->get_default_footer_text() ) );
$variables['custom_css'] = Email_Reports_Base::get_setting( 'custom_css', '' );
$variables['logo_link'] = Email_Reports_Base::get_setting( 'logo_link', KB::get( 'email-reports', 'PRO Email Report Logo' ) );
// Get Pro stats.
$period = Email_Reports_Base::get_period_from_frequency();
Stats::get()->set_date_range( "-{$period} days" );
$keywords = Keywords::get();
if ( Email_Reports_Base::get_setting( 'tracked_keywords', false ) ) {
$variables['winning_keywords'] = $keywords->get_tracked_winning_keywords();
$variables['losing_keywords'] = $keywords->get_tracked_losing_keywords();
} else {
$variables['winning_keywords'] = $keywords->get_winning_keywords();
$variables['losing_keywords'] = $keywords->get_losing_keywords();
}
$posts = Posts::get();
$variables['winning_posts'] = $posts->get_winning_posts();
$variables['losing_posts'] = $posts->get_losing_posts();
return $variables;
}
/**
* Add Email Report options.
*
* @param object $cmb CMB object.
*/
public function add_options( $cmb ) {
if ( ! Authentication::is_authorized() || Email_Reports_Base::are_fields_hidden() ) {
return;
}
$is_business = ProAdminHelper::is_business_plan();
// Add Frequency options.
$frequency_field = $cmb->get_field( 'console_email_frequency' );
// Early bail if the console_email_frequency field does not exist.
if ( empty( $frequency_field ) ) {
return;
}
$frequency_field->args['options']['every_15_days'] = esc_html__( 'Every 15 Days', 'rank-math-pro' );
$field_ids = wp_list_pluck( $cmb->prop( 'fields' ), 'id' );
$fields_position = array_search( 'console_email_frequency', array_keys( $field_ids ), true ) + 1;
if ( $is_business ) {
$frequency_field->args['options']['weekly'] = esc_html__( 'Every 7 Days', 'rank-math-pro' );
} else {
/**
* This field is repeated further down, to insert it in the correct
* position when the account type is Business.
*/
$cmb->add_field(
[
'id' => 'console_email_tracked_keywords',
'type' => 'toggle',
'name' => __( 'Include Only Tracked Keywords', 'rank-math-pro' ),
'description' => __( 'When enabled, the Winning Keywords section will only show Tracked Keywords.', 'rank-math-pro' ),
'default' => 'off',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
return;
}
// Business options from here on.
$cmb->add_field(
[
'id' => 'console_email_send_to',
'type' => 'text',
'name' => __( 'Report Email Address', 'rank-math-pro' ),
'description' => __( 'Address where the reports will be sent. You can add multiple recipients separated with commas.', 'rank-math-pro' ),
'default' => Admin_Helper::get_registration_data()['email'],
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_subject',
'type' => 'text',
'name' => __( 'Report Email Subject', 'rank-math-pro' ),
'description' => __( 'Subject of the report emails.', 'rank-math-pro' ),
'default' => $this->get_subject_default(),
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_logo',
'type' => 'file',
'name' => __( 'Report Logo', 'rank-math-pro' ),
'description' => __( 'Logo appearing in the header part of the report.', 'rank-math-pro' ),
'default' => $this->get_logo_url_default(),
'options' => [ 'url' => false ],
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_logo_link',
'type' => 'text',
'name' => __( 'Logo Link', 'rank-math-pro' ),
'description' => __( 'URL where the logo link should point to.', 'rank-math-pro' ),
'default' => KB::get( 'email-reports-logo', 'PRO Email Report Logo' ),
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_header_background',
'type' => 'text',
'name' => __( 'Report Header Background', 'rank-math-pro' ),
'description' => __( 'Color hex code or any other valid value for the <code>background:</code> CSS property.', 'rank-math-pro' ),
'default' => $this->get_header_bg_default(),
'dep' => [ [ 'console_email_reports', 'on' ] ],
// Instant preview.
'after_field' => $this->get_bg_preview(),
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_link_full_report',
'type' => 'toggle',
'name' => __( 'Link to Full Report', 'rank-math-pro' ),
'description' => __( 'Select whether to include a link to the Full Report admin page in the email or not.', 'rank-math-pro' ),
'default' => 'on',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_top_text',
'type' => 'textarea_small',
'name' => __( 'Report Top Text', 'rank-math-pro' ),
'description' => __( 'Text or basic HTML to insert below the title.', 'rank-math-pro' ),
'default' => '',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_sections',
'type' => 'multicheck',
'name' => esc_html__( 'Include Sections', 'rank-math-pro' ),
'desc' => esc_html__( 'Select which tables to show in the report.', 'rank-math-pro' ),
'options' => [
'summary' => __( 'Basic Summary', 'rank-math-pro' ),
'positions' => __( 'Positions Summary', 'rank-math-pro' ),
'winning_posts' => __( 'Top Winning Posts', 'rank-math-pro' ),
'losing_posts' => __( 'Top Losing Posts', 'rank-math-pro' ),
'winning_keywords' => __( 'Top Winning Keywords', 'rank-math-pro' ),
'losing_keywords' => __( 'Top Losing Keywords', 'rank-math-pro' ),
],
'default' => [ 'summary', 'positions', 'winning_posts', 'winning_keywords', 'losing_keywords' ],
'select_all_button' => true,
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
/**
* This field is also added for non-business accounts at the beginning
* of this function.
*/
$cmb->add_field(
[
'id' => 'console_email_tracked_keywords',
'type' => 'toggle',
'name' => __( 'Include Only Tracked Keywords', 'rank-math-pro' ),
'description' => __( 'When enabled, the Winning Keywords and Losing Keywords sections will only show Tracked Keywords.', 'rank-math-pro' ),
'default' => 'off',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_footer_text',
'type' => 'textarea_small',
'name' => __( 'Report Footer Text', 'rank-math-pro' ),
'description' => __( 'Text or basic HTML to insert in the footer area.', 'rank-math-pro' ),
'default' => $this->get_default_footer_text(),
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_custom_css',
'type' => 'textarea_small',
'name' => __( 'Additional CSS code', 'rank-math-pro' ),
'description' => __( 'Additional CSS code to customize the appearance of the reports. Insert the CSS code directly, without the wrapping style tag. Please note that the CSS support is limited in email clients and the appearance may vary greatly.', 'rank-math-pro' ),
'default' => '',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
}
/**
* Get default value for footer text option.
*
* @return string
*/
public function get_default_footer_text() {
return join(
' ',
[
// Translators: placeholder is a link to the homepage.
sprintf( esc_html__( 'This email was sent to you as a registered member of %s.', 'rank-math-pro' ), '<a href="###SITE_URL###">###SITE_URL_SIMPLE###</a>' ),
// Translators: placeholder is a link to the settings, with "click here" as the anchor text.
sprintf( esc_html__( 'To update your email preferences, %s. ###ADDRESS###', 'rank-math-pro' ), '<a href="###SETTINGS_URL###">' . esc_html__( 'click here', 'rank-math-pro' ) . '</a>' ),
]
);
}
/**
* Change email parameters if needed.
*
* @param array $email Parameters array.
* @return array
*/
public function email_parameters( $email ) {
$email['to'] = Email_Reports_Base::get_setting( 'send_to', Admin_Helper::get_registration_data()['email'] );
$email['subject'] = Email_Reports_Base::get_setting( 'subject', $this->get_subject_default() );
return $email;
}
/**
* Get 'value' & 'diff' for the stat template part.
*
* @param mixed $data Stats data.
* @param string $item Item we want to extract.
* @return array
*/
public static function get_stats_val( $data, $item ) {
$value = isset( $data[ $item ]['total'] ) ? $data[ $item ]['total'] : 0;
$diff = isset( $data[ $item ]['difference'] ) ? $data[ $item ]['difference'] : 0;
return compact( 'value', 'diff' );
}
/**
* Output additional options in the Setup Wizard.
*
* @return void
*/
public function wizard_options() {
if ( ! ProAdminHelper::is_business_plan() ) {
return;
}
?>
<div class="cmb-row cmb-type-toggle cmb2-id-console-email-send-to" data-fieldtype="toggle">
<div class="cmb-th">
<label for="console_email_send"><?php esc_html_e( 'Report Email Address', 'rank-math-pro' ); ?></label>
</div>
<div class="cmb-td">
<input type="text" class="regular-text" name="console_email_send_to" id="console_email_send_to" value="<?php echo esc_attr( Helper::get_settings( 'general.console_email_send_to' ) ); ?>" data-hash="42cpi4bihms0">
<p class="cmb2-metabox-description"><?php esc_html_e( 'Address where the reports will be sent. You can add multiple recipients separated with commas.', 'rank-math-pro' ); ?></p>
<div class="rank-math-cmb-dependency hidden" data-relation="or"><span class="hidden" data-field="console_email_reports" data-comparison="=" data-value="on"></span></div>
</div>
</div>
<?php
}
/**
* Save additional wizard options.
*
* @return bool
*/
public function save_wizard() {
$referer = Param::post( '_wp_http_referer' );
if ( empty( $_POST ) ) {
return wp_safe_redirect( $referer );
}
check_admin_referer( 'rank-math-wizard', 'security' );
if ( ! Helper::has_cap( 'general' ) ) {
return false;
}
$send_to = Param::post( 'console_email_send_to' );
if ( ! $send_to ) {
return true;
}
$settings = rank_math()->settings->all_raw();
$settings['general']['console_email_send_to'] = $send_to;
Helper::update_all_settings( $settings['general'], null, null );
return true;
}
/**
* Add element and script for background preview.
*
* @return string
*/
public function get_bg_preview() {
$script = '
<script>
jQuery( function() {
jQuery( "#console_email_header_background" ).on( "change", function() {
jQuery( ".rank-math-preview-bg" ).css( "background", jQuery( this ).val() );
} );
} );
</script>
';
return '<div class="rank-math-preview-bg" data-title="' . esc_attr( __( 'Preview', 'rank-math-pro' ) ) . '" style="background: ' . esc_attr( Helper::get_settings( 'general.console_email_header_background', $this->get_header_bg_default() ) ) . '"></div>' . $script;
}
/**
* Get default value for the Header Background option.
*
* @return string
*/
public function get_header_bg_default() {
return 'linear-gradient(90deg, #724BB7 0%, #4098D7 100%)';
}
/**
* Get default value for the Logo URL option.
*
* @return string
*/
public function get_logo_url_default() {
$url = \rank_math()->plugin_url() . 'includes/modules/analytics/assets/img/';
return $url . 'report-logo.png';
}
/**
* Get default value for the Subject option.
*
* @return string
*/
public function get_subject_default() {
return sprintf(
// Translators: placeholder is the site URL.
__( 'Rank Math [SEO Report] - %s', 'rank-math-pro' ),
explode( '://', get_home_url() )[1]
);
}
/**
* Shorten a URL, like http://example-url...long-page/
*
* @param string $url URL to shorten.
* @param integer $max Max length in characters.
* @return string
*/
public static function shorten_url( $url, $max = 16 ) {
$length = strlen( $url );
if ( $length <= $max + 3 ) {
return $url;
}
return substr_replace( $url, '...', $max / 2, $length - $max );
}
/**
* Add pro template path to paths.
*
* @param string[] $paths Original paths.
* @return string[]
*/
public function add_template_path( $paths ) {
$paths[] = $this->views_path;
return $paths;
}
/**
* Add day numbers for new frequencies.
*
* @param array $periods Original periods.
* @return array
*/
public function frequency_periods( $periods ) {
$periods['every_15_days'] = 15;
$periods['weekly'] = 7;
return $periods;
}
}

View File

@@ -0,0 +1,752 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use WP_REST_Request;
use RankMath\Traits\Cache;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
use RankMath\Helper;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* Keywords class.
*/
class Keywords {
use Hooker, Cache;
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Keywords
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Keywords ) ) {
$instance = new Keywords();
$instance->setup();
}
return $instance;
}
/**
* Initialize filter.
*/
public function setup() {
$this->filter( 'rank_math/analytics/keywords', 'add_keyword_position_graph' );
$this->filter( 'rank_math/analytics/keywords_overview', 'add_winning_losing_data' );
$this->action( 'save_post', 'add_post_focus_keyword' );
$this->action( 'init', 'get_post_type_list', 99 );
}
/**
* Get accessible post type lists to auto add the focus keywords.
*/
public function get_post_type_list() {
if ( 'rank-math-analytics' !== Param::get( 'page' ) ) {
return;
}
$post_types = array_map(
function( $post_type ) {
return 'attachment' === $post_type ? false : Helper::get_post_type_label( $post_type );
},
Helper::get_accessible_post_types()
);
Helper::add_json( 'postTypes', array_filter( $post_types ) );
Helper::add_json( 'autoAddFK', Helper::get_settings( 'general.auto_add_focus_keywords', [] ) );
}
/**
* Get keywords position data to show it in the graph.
*
* @param array $rows Rows.
* @return array
*/
public function add_keyword_position_graph( $rows ) {
$history = $this->get_graph_data_for_keywords( \array_keys( $rows ) );
$rows = Stats::get()->set_query_position( $rows, $history );
return $rows;
}
/**
* Get winning and losing keywords data.
*
* @param array $data Data.
* @return array
*/
public function add_winning_losing_data( $data ) {
$data['winningKeywords'] = $this->get_winning_keywords();
$data['losingKeywords'] = $this->get_losing_keywords();
if ( empty( $data['winningKeywords'] ) ) {
$data['winningKeywords']['response'] = 'No Data';
}
if ( empty( $data['losingKeywords'] ) ) {
$data['losingKeywords']['response'] = 'No Data';
}
return $data;
}
/**
* Extract keywords that can be added by removing the empty and the duplicate keywords.
*
* @param string $keywords Comma Separated Keyword List.
*
* @return array Keywords that can be added.
*/
public function extract_addable_track_keyword( $keywords ) {
global $wpdb;
// Split keywords.
$keywords_to_add = array_filter( array_map( 'trim', explode( ',', $keywords ) ) );
$keywords_to_check = array_filter( array_map( 'mb_strtolower', explode( ',', $keywords ) ) );
// Check if keywords already exists.
$keywords_joined = "'" . join( "', '", array_map( 'esc_sql', $keywords_to_add ) ) . "'";
$query = "SELECT keyword FROM {$wpdb->prefix}rank_math_analytics_keyword_manager as km WHERE km.keyword IN ( $keywords_joined )";
$data = $wpdb->get_results( $query ); // phpcs:ignore
// Filter out non-existing keywords.
foreach ( $data as $row ) {
$key = array_search( mb_strtolower( $row->keyword ), $keywords_to_check, true );
if ( false !== $key ) {
unset( $keywords_to_add[ $key ] );
}
}
return $keywords_to_add;
}
/**
* Add keyword to Rank Tracker.
*
* @param array $keywords Keyword List.
*/
public function add_track_keyword( $keywords ) {
foreach ( $keywords as $add_keyword ) {
DB::keywords()->insert(
[
'keyword' => $add_keyword,
'collection' => 'uncategorized',
'is_active' => true,
],
[ '%s', '%s', '%d' ]
);
}
delete_transient( Stats::get()->get_cache_key( 'tracked_keywords_summary', Stats::get()->days . 'days' ) );
}
/**
* Remove a keyword from Rank Tracker.
*
* @param string $keyword Keyword to remove.
*/
public function remove_track_keyword( $keyword ) {
DB::keywords()->where( 'keyword', $keyword )
->delete();
delete_transient( Stats::get()->get_cache_key( 'tracked_keywords_summary', Stats::get()->days . 'days' ) );
}
/**
* Delete all tracked keywords.
*/
public function delete_all_tracked_keywords() {
DB::keywords()->delete();
delete_transient( Stats::get()->get_cache_key( 'tracked_keywords_summary', Stats::get()->days . 'days' ) );
}
/**
* Get tracked keywords count.
*
* @return int Total keywords count
*/
public function get_tracked_keywords_count() {
$total = DB::keywords()
->selectCount( 'DISTINCT(keyword)', 'total' )
->where( 'is_active', 1 )
->getVar();
return (int) $total;
}
/**
* Get keywords quota.
*
* @return array Keywords usage info.
*/
public function get_tracked_keywords_quota() {
$quota = (array) get_option(
'rank_math_keyword_quota',
[
'taken' => 0,
'available' => 0,
]
);
return $quota;
}
/**
* Get tracked keywords summary.
*
* @return array Keywords usage info.
*/
public function get_tracked_keywords_summary() {
$cache_key = 'tracked_keywords_summary';
$cache_group = 'tracked_keywords_summary';
$summary = $this->get_cache( $cache_key, $cache_group );
if ( empty( $summary ) ) {
$summary = $this->get_tracked_keywords_quota();
$summary['total'] = $this->get_tracked_keywords_count();
$this->set_cache( $cache_key, $summary, $cache_group, DAY_IN_SECONDS );
}
return $summary;
}
/**
* Get winning tracked keywords.
*
* @return array Top 5 winning tracked keywords data.
*/
public function get_tracked_winning_keywords() {
return $this->get_tracked_keywords(
[
'offset' => 0,
'perpage' => 5,
'where' => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) < 0',
]
);
}
/**
* Get losing tracked keywords.
*
* @return array Top 5 losing tracked keywords data.
*/
public function get_tracked_losing_keywords() {
return $this->get_tracked_keywords(
[
'order' => 'DESC',
'offset' => 0,
'perpage' => 5,
'where' => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) > 0',
]
);
}
/**
* Get tracked keywords rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return array Tracked keywords data.
*/
public function get_tracked_keywords_rows( WP_REST_Request $request ) {
$per_page = 25;
$cache_args = $request->get_params();
$cache_args['per_page'] = $per_page;
$cache_group = 'rank_math_rest_tracked_keywords_rows';
$cache_key = $this->generate_hash( $cache_args );
$result = $this->get_cache( $cache_key, $cache_group );
if ( ! empty( $result ) ) {
return $result;
}
$page = ! empty( $request->get_param( 'page' ) ) ? $request->get_param( 'page' ) : 1;
$orderby = ! empty( $request->get_param( 'orderby' ) ) ? $request->get_param( 'orderby' ) : 'default';
$order = ! empty( $request->get_param( 'order' ) ) ? strtoupper( $request->get_param( 'order' ) ) : 'DESC';
$keyword = ! empty( $request->get_param( 'search' ) ) ? filter_var( urldecode( $request->get_param( 'search' ) ), FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_BACKTICK ) : '';
$offset = ( $page - 1 ) * $per_page;
$args = wp_parse_args(
[
'dimension' => 'query',
'limit' => "LIMIT {$offset}, {$per_page}",
'keyword' => $keyword,
]
);
switch ( $orderby ) {
case 'impressions':
case 'clicks':
case 'ctr':
case 'position':
$args['orderBy'] = $orderby;
$args['order'] = $order;
break;
case 'query':
$args['orderBy'] = 'keyword';
$args['order'] = $order;
break;
}
$data = $this->get_tracked_keywords_data( $args );
$data = Stats::get()->set_dimension_as_key( $data );
$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
$data = Stats::get()->set_query_position( $data, $history );
if ( 'default' === $orderby ) {
uasort(
$data,
function( $a, $b ) use ( $orderby ) {
if ( false === array_key_exists( 'position', $a ) ) {
$a['position'] = [ 'total' => '0' ];
}
if ( false === array_key_exists( 'position', $b ) ) {
$b['position'] = [ 'total' => '0' ];
}
if ( 0 === intval( $b['position']['total'] ) ) {
return 0;
}
return $a['position']['total'] > $b['position']['total'];
}
);
}
$result['rowsData'] = $data;
// get total rows by search.
$args = wp_parse_args(
[
'dimension' => 'query',
'limit' => 'LIMIT 10000',
'keyword' => $keyword,
]
);
if ( empty( $data ) ) {
$result['response'] = 'No Data';
} else {
$search_data = $this->get_tracked_keywords_data( $args );
$result['total'] = count( $search_data );
$this->set_cache( $cache_key, $result, $cache_group, DAY_IN_SECONDS );
}
return $result;
}
/**
* Get keyword rows from keyword manager table.
*
* @param array $args Array of arguments.
* @return array
*/
public function get_tracked_keywords_data( $args = [] ) {
global $wpdb;
Helper::enable_big_selects_for_queries();
$args = wp_parse_args(
$args,
[
'dimension' => 'query',
'order' => 'ASC',
'orderBy' => 'diffPosition1',
'objects' => false,
'where' => '',
'sub_where' => '',
'dates' => ' AND created BETWEEN %s AND %s',
'limit' => 'LIMIT 5',
'keyword' => '',
]
);
$where = $args['where'];
$limit = $args['limit'];
$dimension = $args['dimension'];
$sub_where = $args['sub_where'];
$dates = $args['dates'];
$keyword = trim( $args['keyword'] );
$order = sprintf( 'ORDER BY %s %s', $args['orderBy'], $args['order'] );
$dates_query = sprintf( " AND created BETWEEN '%s' AND '%s' ", Stats::get()->start_date, Stats::get()->end_date );
// Step1. Get most recent data row id for each keyword.
// phpcs:disable
$where_like_keyword = $wpdb->prepare( ' WHERE keyword LIKE %s', '%' . $wpdb->esc_like( $keyword ) . '%' );
if ( empty( $keyword ) ) {
$where_like_keyword = '';
}
$query = "SELECT MAX(id) as id FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1 {$dates_query} AND {$dimension} IN ( SELECT keyword from {$wpdb->prefix}rank_math_analytics_keyword_manager {$where_like_keyword} GROUP BY keyword ) GROUP BY {$dimension}";
$ids = $wpdb->get_results( $query );
// phpcs:enable
// Step2. Get id list from above result.
$ids = wp_list_pluck( $ids, 'id' );
$ids_where = " AND id IN ('" . join( "', '", $ids ) . "')";
// Step3. Get most recent data row id for each keyword (for comparison).
// phpcs:disable
$dates_query = sprintf( " AND created BETWEEN '%s' AND '%s' ", Stats::get()->compare_start_date, Stats::get()->compare_end_date );
$query = "SELECT MAX(id) as id FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1 {$dates_query} AND {$dimension} IN ( SELECT keyword from {$wpdb->prefix}rank_math_analytics_keyword_manager {$where_like_keyword} GROUP BY keyword ) GROUP BY {$dimension}";
$old_ids = $wpdb->get_results( $query );
// Step4. Get id list from above result.
$old_ids = wp_list_pluck( $old_ids, 'id' );
$old_ids_where = " AND id IN ('" . join( "', '", $old_ids ) . "')";
// Step5. Get most performing keywords first based on id list from above.
$where_like_keyword1 = $wpdb->prepare( ' WHERE km.keyword LIKE %s', '%' . $wpdb->esc_like( $keyword ) . '%' );
if ( empty( $keyword ) ) {
$where_like_keyword1 = '';
}
$positions = $wpdb->get_results(
"SELECT DISTINCT(km.keyword) as {$dimension}, COALESCE(t.position, 0) as position, COALESCE(t.diffPosition, 0) as diffPosition, COALESCE(t.diffPosition, 100) as diffPosition1, COALESCE(t.impressions, 0) as impressions, COALESCE(t.diffImpressions, 0) as diffImpressions, COALESCE(t.clicks, 0) as clicks, COALESCE(t.diffClicks, 0) as diffClicks, COALESCE(t.ctr, 0) as ctr, COALESCE(t.diffCtr, 0) as diffCtr
FROM {$wpdb->prefix}rank_math_analytics_keyword_manager km
LEFT JOIN (
SELECT
t1.{$dimension} as {$dimension}, ROUND( t1.position, 0 ) as position, ROUND( t1.impressions, 0 ) as impressions, ROUND( t1.clicks, 0 ) as clicks, ROUND( t1.ctr, 0 ) as ctr,
COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) as diffPosition,
COALESCE( ROUND( t1.impressions - COALESCE( t2.impressions, 100 ), 0 ), 0 ) as diffImpressions,
COALESCE( ROUND( t1.clicks - COALESCE( t2.clicks, 100 ), 0 ), 0 ) as diffClicks,
COALESCE( ROUND( t1.ctr - COALESCE( t2.ctr, 100 ), 0 ), 0 ) as diffCtr
FROM
(SELECT a.{$dimension}, a.position, a.impressions,a.clicks,a.ctr FROM {$wpdb->prefix}rank_math_analytics_gsc AS a
WHERE 1 = 1{$ids_where}) AS t1
LEFT JOIN
(SELECT a.{$dimension}, a.position, a.impressions,a.clicks,a.ctr FROM {$wpdb->prefix}rank_math_analytics_gsc AS a
WHERE 1 = 1{$old_ids_where}) AS t2
ON t1.{$dimension} = t2.{$dimension}) AS t on t.{$dimension} = km.keyword
{$where_like_keyword1}
{$where}
{$order}
{$limit}",
ARRAY_A
);
// phpcs:enable
// Step6. Get keywords list from above results.
$keywords = array_column( $positions, 'query' );
$keywords = array_map( 'esc_sql', $keywords );
$keywords = array_map( 'strtolower', $keywords );
$keywords = '(\'' . join( '\', \'', $keywords ) . '\')';
// step7. Get other metrics data.
$query = $wpdb->prepare(
"SELECT t1.{$dimension} as {$dimension}, t1.clicks, t1.impressions, t1.ctr,
COALESCE( t1.clicks - t2.clicks, 0 ) as diffClicks,
COALESCE( t1.impressions - t2.impressions, 0 ) as diffImpressions,
COALESCE( t1.ctr - t2.ctr, 0 ) as diffCtr
FROM
( SELECT {$dimension}, SUM( clicks ) as clicks, SUM(impressions) as impressions, AVG(position) as position, AVG(ctr) as ctr FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1{$dates} AND {$dimension} IN {$keywords} GROUP BY {$dimension}) as t1
LEFT JOIN
( SELECT {$dimension}, SUM( clicks ) as clicks, SUM(impressions) as impressions, AVG(position) as position, AVG(ctr) as ctr FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1{$dates} AND {$dimension} IN {$keywords} GROUP BY {$dimension}) as t2
ON t1.query = t2.query",
Stats::get()->start_date,
Stats::get()->end_date,
Stats::get()->compare_start_date,
Stats::get()->compare_end_date
);
$metrics = $wpdb->get_results( $query, ARRAY_A );
// Step8. Merge above two results.
$positions = Stats::get()->set_dimension_as_key( $positions, $dimension );
$metrics = Stats::get()->set_dimension_as_key( $metrics, $dimension );
$data = Stats::get()->get_merged_metrics( $positions, $metrics );
// Step9. Construct return data.
foreach ( $data as $keyword => $row ) {
$data[ $keyword ]['graph'] = [];
$data[ $keyword ]['clicks'] = [
'total' => (int) $data[ $keyword ]['clicks'],
'difference' => (int) $data[ $keyword ]['diffClicks'],
];
$data[ $keyword ]['impressions'] = [
'total' => (int) $data[ $keyword ]['impressions'],
'difference' => (int) $data[ $keyword ]['diffImpressions'],
];
$data[ $keyword ]['position'] = [
'total' => (float) $data[ $keyword ]['position'],
'difference' => (float) $data[ $keyword ]['diffPosition'],
];
$data[ $keyword ]['ctr'] = [
'total' => (float) $data[ $keyword ]['ctr'],
'difference' => (float) $data[ $keyword ]['diffCtr'],
];
unset(
$data[ $keyword ]['diffClicks'],
$data[ $keyword ]['diffImpressions'],
$data[ $keyword ]['diffPosition'],
$data[ $keyword ]['diffCtr']
);
}
return $data;
}
/**
* Get tracked keywords.
*
* @param array $args Array of arguments.
* @return array
*/
public function get_tracked_keywords( $args = [] ) {
global $wpdb;
$args = wp_parse_args(
$args,
[
'dimension' => 'query',
'order' => 'ASC',
'orderBy' => 'diffPosition',
'offset' => 0,
'perpage' => 20000,
'sub_where' => " AND query IN ( SELECT keyword from {$wpdb->prefix}rank_math_analytics_keyword_manager )",
]
);
$data = Stats::get()->get_analytics_data( $args );
$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
$data = Stats::get()->set_query_position( $data, $history );
// Add remaining keywords.
if ( 5 !== $args['perpage'] ) {
$rows = DB::keywords()->get();
foreach ( $rows as $row ) {
if ( ! isset( $data[ $row->keyword ] ) ) {
$data[ $row->keyword ] = [
'query' => $row->keyword,
'graph' => [],
'clicks' => [
'total' => 0,
'difference' => 0,
],
'impressions' => [
'total' => 0,
'difference' => 0,
],
'position' => [
'total' => 0,
'difference' => 0,
],
'ctr' => [
'total' => 0,
'difference' => 0,
],
'pageviews' => [
'total' => 0,
'difference' => 0,
],
];
}
}
}
return $data;
}
/**
* Get most recent day's keywords.
*
* @return array
*/
public function get_recent_keywords() {
global $wpdb;
$query = $wpdb->prepare(
"SELECT query
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE DATE(created) = (SELECT MAX(DATE(created)) FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE created BETWEEN %s AND %s)
GROUP BY query",
Stats::get()->start_date,
Stats::get()->end_date
);
$data = $wpdb->get_results( $query ); // phpcs:ignore
return $data;
}
/**
* Get top 5 winning keywords.
*
* @return array
*/
public function get_winning_keywords() {
$cache_key = Stats::get()->get_cache_key( 'winning_keywords', Stats::get()->days . 'days' );
$cache = get_transient( $cache_key );
if ( false !== $cache ) {
return $cache;
}
// Get most recent day's keywords only.
$keywords = $this->get_recent_keywords();
$keywords = wp_list_pluck( $keywords, 'query' );
$keywords = array_map( 'strtolower', $keywords );
$data = Stats::get()->get_analytics_data(
[
'order' => 'ASC',
'dimension' => 'query',
'where' => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) < 0',
]
);
$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
$data = Stats::get()->set_query_position( $data, $history );
set_transient( $cache_key, $data, DAY_IN_SECONDS );
return $data;
}
/**
* Get top 5 losing keywords.
*
* @return array
*/
public function get_losing_keywords() {
$cache_key = Stats::get()->get_cache_key( 'losing_keywords', Stats::get()->days . 'days' );
$cache = get_transient( $cache_key );
if ( false !== $cache ) {
return $cache;
}
// Get most recent day's keywords only.
$keywords = $this->get_recent_keywords();
$keywords = wp_list_pluck( $keywords, 'query' );
$keywords = array_map( 'strtolower', $keywords );
$data = Stats::get()->get_analytics_data(
[
'dimension' => 'query',
'where' => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) > 0',
]
);
$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
$data = Stats::get()->set_query_position( $data, $history );
set_transient( $cache_key, $data, DAY_IN_SECONDS );
return $data;
}
/**
* Get keywords graph data.
*
* @param array $keywords Keywords to get data for.
*
* @return array
*/
public function get_graph_data_for_keywords( $keywords ) {
global $wpdb;
$intervals = Stats::get()->get_intervals();
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
$keywords = \array_map( 'esc_sql', $keywords );
$keywords = '(\'' . join( '\', \'', $keywords ) . '\')';
$query = $wpdb->prepare(
"SELECT a.query, a.position, t.date, t.range_group
FROM {$wpdb->prefix}rank_math_analytics_gsc AS a
INNER JOIN
(SELECT query, DATE_FORMAT(created, '%%Y-%%m-%%d') as date, MAX(id) as id, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE created BETWEEN %s AND %s
AND query IN {$keywords}
GROUP BY query, range_group
ORDER BY created ASC) AS t ON a.id = t.id
",
Stats::get()->start_date,
Stats::get()->end_date
);
$data = $wpdb->get_results( $query );
// phpcs:enable
$data = Stats::get()->filter_graph_rows( $data );
return array_map( [ Stats::get(), 'normalize_graph_rows' ], $data );
}
/**
* Get pages by keyword.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_keyword_pages( WP_REST_Request $request ) {
global $wpdb;
$query = $wpdb->prepare(
"SELECT DISTINCT g.page
FROM {$wpdb->prefix}rank_math_analytics_gsc as g
WHERE g.query = %s AND g.created BETWEEN %s AND %s
ORDER BY g.created DESC
LIMIT 5",
$request->get_param( 'query' ),
Stats::get()->start_date,
Stats::get()->end_date
);
$data = $wpdb->get_results( $query ); // phpcs:ignore
$pages = wp_list_pluck( $data, 'page' );
$console = Stats::get()->get_analytics_data(
[
'objects' => true,
'pageview' => true,
'sub_where' => " AND page IN ('" . join( "', '", $pages ) . "')",
]
);
return $console;
}
/**
* Add focus keywords to Rank Tracker.
*
* @param int $post_id Post ID.
* @return mixed
*/
public function add_post_focus_keyword( $post_id ) {
if ( wp_is_post_revision( $post_id ) ) {
return;
}
$auto_add_fks = Helper::get_settings( 'general.auto_add_focus_keywords', [] );
if (
empty( $auto_add_fks['enable_auto_import'] ) ||
empty( $auto_add_fks['post_types'] ) ||
! in_array( get_post_type( $post_id ), $auto_add_fks['post_types'], true )
) {
return;
}
$focus_keyword = Helper::get_post_meta( 'focus_keyword', $post_id );
if ( empty( $focus_keyword ) ) {
return;
}
$keywords_data = [];
$keywords = explode( ',', $focus_keyword );
if ( ! empty( $auto_add_fks['secondary_keyword'] ) ) {
$keywords_data = $keywords;
} else {
$keywords_data[] = current( $keywords );
}
DB::bulk_insert_query_focus_keyword_data( $keywords_data );
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
/**
* Links class.
*/
class Links {
/**
* Get links by post.
*
* @param array $objects Array of ids.
* @return array
*/
public static function get_links_by_objects( $objects ) {
if ( empty( $objects ) || empty( $objects['rows'] ) ) {
return [];
}
if ( ! Helper::is_module_active( 'link-counter' ) ) {
return $objects;
}
$ids = wp_list_pluck( $objects['rows'], 'object_id' );
$links = DB::links()
->whereIn( 'object_id', \array_unique( $ids ) )
->get( ARRAY_A );
$ids = array_flip( $ids );
foreach ( $links as $link ) {
$post_id = $link['object_id'];
$object_id = $ids[ $post_id ];
$objects['rows'][ $object_id ]['links'] = [
'internal' => $link['internal_link_count'],
'external' => $link['external_link_count'],
'incoming' => $link['incoming_link_count'],
];
}
return $objects;
}
}

View File

@@ -0,0 +1,201 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\Analytics\Stats;
use MyThemeShop\Helpers\DB as DB_Helper;
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
/**
* Pageviews class.
*/
class Pageviews {
/**
* Get page views for pages.
*
* @param array $args Array of urls.
*
* @return array
*/
public static function get_pageviews( $args = [] ) {
global $wpdb;
$args = wp_parse_args(
$args,
[
'order' => 'DESC',
'orderBy' => 't1.pageviews',
'where' => '',
'sub_where' => '',
'dates' => ' AND created BETWEEN %s AND %s',
'limit' => '',
'pages' => '',
]
);
if ( empty( $args['pages'] ) ) {
return [
'rows' => [],
'rowsFound' => 0,
];
}
$args['pages'] = ' AND page IN (\'' . join( '\', \'', $args['pages'] ) . '\')';
$pages = $args['pages'];
$where = $args['where'];
$limit = $args['limit'];
$dates = $args['dates'];
$sub_where = $args['sub_where'];
$order = sprintf( 'ORDER BY %s %s', $args['orderBy'], $args['order'] );
// phpcs:disable
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT SQL_CALC_FOUND_ROWS t1.page as page, COALESCE( t1.pageviews, 0 ) as pageviews, COALESCE( t1.pageviews - t2.pageviews, 0 ) as difference
FROM ( SELECT page, SUM(pageviews) as pageviews FROM {$wpdb->prefix}rank_math_analytics_ga WHERE 1=1{$pages}{$dates}{$sub_where} GROUP BY page ) as t1
LEFT JOIN ( SELECT page, SUM(pageviews) as pageviews FROM {$wpdb->prefix}rank_math_analytics_ga WHERE 1=1{$pages}{$dates}{$sub_where} GROUP BY page ) as t2
ON t1.page = t2.page
{$where}
{$order}
{$limit}",
Stats::get()->start_date,
Stats::get()->end_date,
Stats::get()->compare_start_date,
Stats::get()->compare_end_date
),
ARRAY_A
);
$rowsFound = $wpdb->get_var( 'SELECT FOUND_ROWS()' );
// phpcs:enable
return \compact( 'rows', 'rowsFound' );
}
/**
* Get page views for pages.
*
* @param array $args Array of urls.
*
* @return array
*/
public static function get_pageviews_with_object( $args = [] ) {
global $wpdb;
Helper::enable_big_selects_for_queries();
$args = wp_parse_args(
$args,
[
'order' => 'DESC',
'dates' => ' AND created BETWEEN %s AND %s',
'limit' => '',
'sub_where' => '',
]
);
$order = $args['order'];
$limit = $args['limit'];
$dates = $args['dates'];
$subwhere = $args['sub_where'];
// phpcs:disable
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT SQL_CALC_FOUND_ROWS o.*, COALESCE( traffic.pageviews, 0 ) as pageviews, COALESCE( traffic.difference, 0 ) as difference
FROM {$wpdb->prefix}rank_math_analytics_objects as o
LEFT JOIN (SELECT t1.page as page, COALESCE( t1.pageviews, 0 ) as pageviews, COALESCE( t1.pageviews - t2.pageviews, 0 ) as difference
FROM
( SELECT page, SUM(pageviews) as pageviews FROM {$wpdb->prefix}rank_math_analytics_ga WHERE 1=1{$dates} GROUP BY page ) as t1
LEFT JOIN
( SELECT page, SUM(pageviews) as pageviews FROM {$wpdb->prefix}rank_math_analytics_ga WHERE 1=1{$dates} GROUP BY page ) as t2
ON t1.page = t2.page ) traffic ON o.page = traffic.page
WHERE o.is_indexable = '1'{$subwhere}
ORDER BY pageviews {$order}
{$limit}",
Stats::get()->start_date,
Stats::get()->end_date,
Stats::get()->compare_start_date,
Stats::get()->compare_end_date
),
ARRAY_A
);
$rowsFound = $wpdb->get_var( 'SELECT FOUND_ROWS()' );
// phpcs:enable
return \compact( 'rows', 'rowsFound' );
}
/**
* Get pageviews for single post by post Id.
*
* @param array $post_ids Post IDs.
*
* @return array
*/
public static function get_traffic_by_object_ids( $post_ids ) {
if ( ! DB_Helper::check_table_exists( 'rank_math_analytics_ga' ) ) {
return [];
}
global $wpdb;
$placeholder = implode( ', ', array_fill( 0, count( $post_ids ), '%d' ) );
// phpcs:disable
$data = $wpdb->get_results(
$wpdb->prepare(
"SELECT t2.object_id, SUM(t1.pageviews) AS traffic FROM {$wpdb->prefix}rank_math_analytics_ga AS t1
Left JOIN {$wpdb->prefix}rank_math_analytics_objects AS t2 ON t1.page=t2.page
WHERE t2.object_id IN ( {$placeholder} ) and t1.created BETWEEN Now() - interval 36 day and Now() - interval 3 day
GROUP BY t2.object_id",
$post_ids
),
ARRAY_A
);
// phpcs:enable
return array_combine( array_column( $data, 'object_id' ), array_column( $data, 'traffic' ) );
}
/**
* Get pageviews for single post by post Id.
*
* @param array $post_ids Post IDs.
*
* @return array
*/
public static function get_impressions_by_object_ids( $post_ids ) {
if ( ! DB_Helper::check_table_exists( 'rank_math_analytics_gsc' ) ) {
return [];
}
global $wpdb;
$placeholder = implode( ', ', array_fill( 0, count( $post_ids ), '%d' ) );
// phpcs:disable
$data = $wpdb->get_results(
$wpdb->prepare(
"SELECT t2.object_id, SUM(impressions) AS traffic FROM {$wpdb->prefix}rank_math_analytics_gsc AS t1
Left JOIN {$wpdb->prefix}rank_math_analytics_objects AS t2 ON t1.page=t2.page
WHERE t2.object_id IN ( {$placeholder} ) and t1.created BETWEEN Now() - interval 36 day and Now() - interval 3 day
GROUP BY t2.object_id",
$post_ids
),
ARRAY_A
);
// phpcs:enable
return array_combine( array_column( $data, 'object_id' ), array_column( $data, 'traffic' ) );
}
}

View File

@@ -0,0 +1,754 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMath
* @subpackage RankMath\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use stdClass;
use WP_Error;
use WP_REST_Request;
use RankMath\Traits\Cache;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
defined( 'ABSPATH' ) || exit;
/**
* Posts class.
*/
class Posts {
use Hooker, Cache;
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Posts
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Posts ) ) {
$instance = new Posts();
$instance->setup();
}
return $instance;
}
/**
* Constructor.
*/
public function setup() {
$this->filter( 'rank_math/analytics/single/report', 'add_badges', 10, 1 );
$this->filter( 'rank_math/analytics/single/report', 'add_backlinks', 10, 1 );
$this->filter( 'rank_math/analytics/single/report', 'add_ranking_keywords', 10, 1 );
$this->filter( 'rank_math/analytics/single/report', 'get_graph_data_for_post', 10, 1 );
$this->filter( 'rank_math/analytics/post_data', 'sort_new_data', 10, 2 );
$this->filter( 'rank_math/analytics/get_objects_by_score_args', 'get_objects_by_score_args', 10, 2 );
$this->filter( 'rank_math/analytics/get_posts_rows_by_objects', 'get_posts_rows_by_objects', 10, 2 );
}
/**
* Get posts by objects.
*
* @param boolean $result Check.
* @param WP_REST_Request $request Full details about the request.
* @return $args for order and orderby.
*/
public function get_objects_by_score_args( $result, WP_REST_Request $request ) {
$orderby = $request->get_param( 'orderby' );
$is_valid_order_param = in_array( $orderby, [ 'title', 'seo_score' ], true );
$orderby = $is_valid_order_param ? $orderby : 'created';
$order = strtoupper( $request->get_param( 'order' ) );
$args['orderBy'] = $orderby;
$args['order'] = $order;
return $args;
}
/**
* Change user perference.
*
* @param array $data array.
* @param WP_REST_Request $request post object.
* @return array $data sorted array.
*/
public function sort_new_data( $data, WP_REST_Request $request ) {
$id = $request->get_param( 'id' );
$orderby = $request->get_param( 'orderby' );
$order = strtoupper( $request->get_param( 'order' ) );
if ( 'query' !== $orderby ) {
$data['rankingKeywords'] = $this->ranking_keyword_array_sort( $data['rankingKeywords'], $order, $orderby );
}
if ( 'query' === $orderby ) {
if ( 'DESC' === $order ) {
uasort(
$data['rankingKeywords'],
function( $a, $b ) use ( $orderby ) {
return strtolower( $a[ $orderby ] ) < strtolower( $b[ $orderby ] );
}
);
}
if ( 'ASC' === $order ) {
uasort(
$data['rankingKeywords'],
function( $a, $b ) use ( $orderby ) {
return strtolower( $a[ $orderby ] ) > strtolower( $b[ $orderby ] );
}
);
}
}
return $data;
}
/**
* Sort array for ranking keyword by order and orderby
*
* @param array $arr array.
*
* @param Variable $arr_order is order direction.
*
* @param Variable $arr_orderby is key for sort.
*/
public function ranking_keyword_array_sort( $arr, $arr_order, $arr_orderby ) {
if ( 'DESC' === $arr_order ) {
uasort(
$arr,
function( $a, $b ) use ( $arr_orderby ) {
return $a[ $arr_orderby ]['total'] < $b[ $arr_orderby ]['total'];
}
);
}
if ( 'ASC' === $arr_order ) {
uasort(
$arr,
function( $a, $b ) use ( $arr_orderby ) {
return $a[ $arr_orderby ]['total'] > $b[ $arr_orderby ]['total'];
}
);
}
return $arr;
}
/**
* Get posts by objects.
*
* @param boolean $result Check.
* @param WP_REST_Request $request Full details about the request.
* @return array Posts rows.
*/
public function get_posts_rows_by_objects( $result, WP_REST_Request $request ) {
$orderby = $request->get_param( 'orderby' );
$order = strtoupper( $request->get_param( 'order' ) );
$objects = Stats::get()->get_objects_by_score( $request );
$objects = Links::get_links_by_objects( $objects );
$pages = isset( $objects['rows'] ) ? \array_keys( $objects['rows'] ) : [];
$pageviews = Pageviews::get_pageviews( [ 'pages' => $pages ] );
$pageviews = Stats::get()->set_page_as_key( $pageviews['rows'] );
$console = Stats::get()->get_analytics_data(
[
'orderBy' => 'diffImpressions',
'pageview' => true,
'offset' => 0, // Here offset should always zero.
'perpage' => ! empty( $objects['rowsFound'] ) ? $objects['rowsFound'] : 0,
'sub_where' => " AND page IN ('" . join( "', '", $pages ) . "')",
]
);
$new_rows = [];
if ( ! empty( $objects['rows'] ) ) {
foreach ( $objects['rows'] as $object ) {
$page = $object['page'];
if ( isset( $pageviews[ $page ] ) ) {
$object['pageviews'] = [
'total' => (int) $pageviews[ $page ]['pageviews'],
'difference' => (int) $pageviews[ $page ]['difference'],
];
}
if ( isset( $console[ $page ] ) ) {
$object = \array_merge( $console[ $page ], $object );
}
if ( ! isset( $object['links'] ) ) {
$object['links'] = new stdClass();
}
$new_rows[ $page ] = $object;
}
}
$history = $this->get_graph_data_for_pages( $pages );
$new_rows = Stats::get()->set_page_position_graph( $new_rows, $history );
if ( in_array( $orderby, [ 'position', 'clicks', 'pageviews', 'impressions' ], true ) ) {
$new_rows = $this->analytics_array_sort( $new_rows, $order, $orderby );
}
if ( empty( $new_rows ) ) {
$new_rows['response'] = 'No Data';
}
return [
'rows' => $new_rows,
'rowsFound' => ! empty( $objects['rowsFound'] ) ? $objects['rowsFound'] : 0,
];
}
/**
* Sort array by order and orderby
*
* @param array $arr array.
*
* @param Variable $arr_order is order direction.
*
* @param Variable $arr_orderby is key for sort.
*
* @return $arr sorted array
*/
public function analytics_array_sort( $arr, $arr_order, $arr_orderby ) {
if ( 'DESC' === $arr_order ) {
uasort(
$arr,
function( $a, $b ) use ( $arr_orderby ) {
if ( false === array_key_exists( $arr_orderby, $a ) ) {
$a[ $arr_orderby ] = [ 'total' => '0' ];
}
if ( false === array_key_exists( $arr_orderby, $b ) ) {
$b[ $arr_orderby ] = [ 'total' => '0' ];
}
return $a[ $arr_orderby ]['total'] < $b[ $arr_orderby ]['total'];
}
);
}
if ( 'ASC' === $arr_order ) {
uasort(
$arr,
function( $a, $b ) use ( $arr_orderby ) {
if ( false === array_key_exists( $arr_orderby, $a ) ) {
$a[ $arr_orderby ] = [ 'total' => '0' ];
}
if ( false === array_key_exists( $arr_orderby, $b ) ) {
$b[ $arr_orderby ] = [ 'total' => '0' ];
}
return $a[ $arr_orderby ]['total'] > $b[ $arr_orderby ]['total'];
}
);
}
return $arr;
}
/**
* Get ranking keywords data and append it to existing post data.
*
* @param object $post Post object.
* @return object
*/
public function add_ranking_keywords( $post ) {
$page = $post->page;
$data = Stats::get()->get_analytics_data(
[
'dimension' => 'query',
'offset' => 0,
'perpage' => 20,
'orderBy' => 'impressions',
'sub_where' => "AND page = '{$page}'",
]
);
$history = Keywords::get()->get_graph_data_for_keywords( \array_keys( $data ) );
$post->rankingKeywords = Stats::get()->set_query_position( $data, $history ); // phpcs:ignore
return $post;
}
/**
* Append backlinks data into existing post data.
*
* @param object $post Post object.
* @return object
*/
public function add_backlinks( $post ) {
$post->backlinks = [
'total' => 0,
'previous' => 0,
'difference' => 0,
];
return $post;
}
/**
* Append badges data into existing post data.
*
* @param object $post Post object.
* @return object
*/
public function add_badges( $post ) {
$post->badges = [
'clicks' => $this->get_position_for_badges( 'clicks', $post->page ),
'traffic' => $this->get_position_for_badges( 'traffic', $post->page ),
'keywords' => $this->get_position_for_badges( 'query', $post->page ),
'impressions' => $this->get_position_for_badges( 'impressions', $post->page ),
];
return $post;
}
/**
* Get position for badges.
*
* @param string $column Column name.
* @param string $page Page url.
* @return integer
*/
public function get_position_for_badges( $column, $page ) {
$start = date( 'Y-m-d H:i:s', strtotime( '-30 days ', Stats::get()->end ) );
if ( 'traffic' === $column ) {
$rows = DB::traffic()
->select( 'page' )
->selectSum( 'pageviews', 'pageviews' )
->whereBetween( 'created', [ $start, Stats::get()->end_date ] )
->groupBy( 'page' )
->orderBy( 'pageviews', 'DESC' )
->limit( 5 );
} else {
$rows = DB::analytics()
->select( 'page' )
->whereBetween( 'created', [ $start, Stats::get()->end_date ] )
->groupBy( 'page' )
->orderBy( $column, 'DESC' )
->limit( 5 );
}
if ( 'impressions' === $column || 'click' === $column ) {
$rows->selectSum( $column, $column );
}
if ( 'query' === $column ) {
$rows->selectCount( 'DISTINCT(query)', 'keywords' );
}
$rows = $rows->get( ARRAY_A );
foreach ( $rows as $index => $row ) {
if ( $page === $row['page'] ) {
return $index + 1;
}
}
return 99;
}
/**
* Append analytics graph data into existing post data.
*
* @param object $post Post object.
* @return object
*/
public function get_graph_data_for_post( $post ) {
global $wpdb;
// Step1. Get splitted date intervals for graph within selected date range.
$data = new stdClass();
$page = $post->page;
$intervals = Stats::get()->get_intervals();
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
// Step2. Get analytics data summary for each splitted date intervals.
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, SUM( clicks ) as clicks, SUM(impressions) as impressions, ROUND( AVG(ctr), 2 ) as ctr, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE created BETWEEN %s AND %s AND page LIKE '%{$page}'
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$metrics = $wpdb->get_results( $query );
// Step3. Get position data summary for each splitted date intervals.
$query = $wpdb->prepare(
"SELECT page, MAX(CONCAT(t.uid, ':', t.range_group)) as range_group FROM
(SELECT page, MAX(CONCAT(page, ':', DATE(created), ':', LPAD((100 - position), 3, '0'))) as uid, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE created BETWEEN %s AND %s AND page LIKE '%{$page}'
GROUP BY range_group, DATE(created)
ORDER BY DATE(created) DESC) AS t
GROUP BY t.range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$positions = $wpdb->get_results( $query );
$positions = Stats::get()->extract_data_from_mixed( $positions, 'range_group', ':', [ 'range_group', 'position', 'date' ] );
// Step4. Get keywords count for each splitted date intervals.
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, COUNT(DISTINCT(query)) as keywords, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE created BETWEEN %s AND %s AND page LIKE '%{$page}'
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$keywords = $wpdb->get_results( $query );
// phpcs:enable
// Step5. Filter graph data.
$metrics = Stats::get()->filter_graph_rows( $metrics );
$positions = Stats::get()->filter_graph_rows( $positions );
$keywords = Stats::get()->filter_graph_rows( $keywords );
// Step6. Convert types.
$metrics = array_map( [ Stats::get(), 'normalize_graph_rows' ], $metrics );
$positions = array_map( [ Stats::get(), 'normalize_graph_rows' ], $positions );
$keywords = array_map( [ Stats::get(), 'normalize_graph_rows' ], $keywords );
// Step7. Merge all analytics data.
$data = Stats::get()->get_date_array(
$intervals['dates'],
[
'clicks' => [],
'impressions' => [],
'position' => [],
'ctr' => [],
'keywords' => [],
'pageviews' => [],
]
);
$data = Stats::get()->get_merge_data_graph( $metrics, $data, $intervals['map'] );
$data = Stats::get()->get_merge_data_graph( $positions, $data, $intervals['map'] );
$data = Stats::get()->get_merge_data_graph( $keywords, $data, $intervals['map'] );
// Step8. Get traffic data in case analytics is connected for each splitted data intervals.
if ( \RankMath\Google\Analytics::is_analytics_connected() ) {
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, SUM( pageviews ) as pageviews, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_ga
WHERE created BETWEEN %s AND %s AND page LIKE '%{$page}'
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$traffic = $wpdb->get_results( $query );
// Filter graph data.
$traffic = Stats::get()->filter_graph_rows( $traffic );
// Convert types.
$traffic = array_map( [ Stats::get(), 'normalize_graph_rows' ], $traffic );
$data = Stats::get()->get_merge_data_graph( $traffic, $data, $intervals['map'] );
}
$data = Stats::get()->get_graph_data_flat( $data );
// Step9. Append graph data into existing post data.
$post->graph = array_values( $data );
return $post;
}
/**
* Get posts rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return array Posts rows.
*/
public function get_posts_rows( WP_REST_Request $request ) {
$per_page = 25;
$cache_args = $request->get_params();
$cache_args['per_page'] = $per_page;
$cache_group = 'rank_math_rest_posts_rows';
$cache_key = $this->generate_hash( $cache_args );
$data = $this->get_cache( $cache_key, $cache_group );
if ( ! empty( $data ) ) {
return $data;
}
// Pagination.
$offset = ( $request->get_param( 'page' ) - 1 ) * $per_page;
$orderby = $request->get_param( 'orderby' );
$post_type = sanitize_key( $request->get_param( 'postType' ) );
$order = $request->get_param( 'order' );
$order = in_array( $order, [ 'asc', 'desc' ], true ) ? $order : 'desc';
$order = strtoupper( $order );
$post_type_clause = $post_type ? " AND o.object_subtype = '{$post_type}'" : '';
if ( 'pageviews' === $orderby ) {
// Get posts order by pageviews.
$t_data = Pageviews::get_pageviews_with_object(
[
'order' => $order,
'limit' => "LIMIT {$offset}, {$per_page}",
'sub_where' => $post_type_clause,
]
);
$pageviews = Stats::get()->set_page_as_key( $t_data['rows'] );
$pages = \array_keys( $pageviews );
$pages = array_map( 'esc_sql', $pages );
$console = Stats::get()->get_analytics_data(
[
'offset' => 0, // Should set as 0.
'perpage' => $per_page,
'objects' => false,
'sub_where' => " AND page IN ('" . join( "', '", $pages ) . "')",
]
);
$data['rowsFound'] = $this->rows_found();
foreach ( $pageviews as $page => &$pageview ) {
$pageview['pageviews'] = [
'total' => (int) $pageview['pageviews'],
'difference' => (int) $pageview['difference'],
];
if ( isset( $console[ $page ] ) ) {
unset( $console[ $page ]['pageviews'] );
$pageview = \array_merge( $pageview, $console[ $page ] );
}
}
$history = $this->get_graph_data_for_pages( $pages );
$pageviews = Stats::get()->set_page_position_graph( $pageviews, $history );
$data['rows'] = $pageviews;
} else {
// Get posts order by impressions.
$t_data = DB::objects()
->select( [ 'page', 'title', 'object_id' ] )
->where( 'is_indexable', 1 );
if ( 'title' === $orderby ) {
$t_data->orderBy( $orderby, $order )
->limit( $per_page, $offset );
}
$t_data = $t_data->get( ARRAY_A );
$pages = Stats::get()->set_page_as_key( $t_data );
$params = \array_keys( $pages );
$params = array_map( 'esc_sql', $params );
$args = [
'dimension' => 'page',
'offset' => 0,
'perpage' => 20000,
'sub_where' => " AND page IN ('" . join( "', '", $params ) . "')",
];
if ( 'title' !== $orderby ) {
$args['orderBy'] = $orderby;
$args['order'] = $order;
}
$rows = Stats::get()->get_analytics_data( $args );
if ( 'title' !== $orderby ) {
foreach ( $pages as $page => $row ) {
if ( ! isset( $rows[ $page ] ) ) {
$rows[ $page ] = $row;
} else {
$rows[ $page ] = \array_merge( $rows[ $page ], $row );
}
}
$history = $this->get_graph_data_for_pages( $params );
$data['rows'] = Stats::get()->set_page_position_graph( $rows, $history );
$data['rowsFound'] = count( $pages );
// Filter array by $offset, $perpage value.
$data['rows'] = array_slice( $data['rows'], $offset, $per_page, true );
} else {
foreach ( $pages as $page => &$row ) {
if ( isset( $rows[ $page ] ) ) {
$row = \array_merge( $row, $rows[ $page ] );
}
}
$history = $this->get_graph_data_for_pages( $params );
$data['rows'] = Stats::get()->set_page_position_graph( $pages, $history );
$data['rowsFound'] = $this->rows_found();
}
// Get fetched page info again.
$pages = Stats::get()->set_page_as_key( $data['rows'] );
$params = \array_keys( $pages );
$params = array_map( 'esc_sql', $params );
// Get pageviews info.
$pageviews = Pageviews::get_pageviews_with_object(
[
'limit' => "LIMIT 0, {$per_page}",
'sub_where' => " AND o.page IN ('" . join( "', '", $params ) . "')" . $post_type_clause,
]
);
$pageviews = Stats::get()->set_page_as_key( $pageviews['rows'] );
// Merge pageview info into main data.
foreach ( $data['rows'] as $page => &$row ) {
if ( isset( $pageviews[ $page ] ) ) {
$pageview = [
'pageviews' => [
'total' => (int) $pageviews[ $page ]['pageviews'],
'difference' => (int) $pageviews[ $page ]['difference'],
],
];
$row = \array_merge( $row, $pageview );
}
}
}
if ( empty( $data ) ) {
$data['response'] = 'No Data';
} else {
$this->set_cache( $cache_key, $data, $cache_group, DAY_IN_SECONDS );
}
return $data;
}
/**
* Get top 5 winning posts.
*
* @return array
*/
public function get_winning_posts() {
global $wpdb;
$cache_key = Stats::get()->get_cache_key( 'winning_posts', Stats::get()->days . 'days' );
$cache = get_transient( $cache_key );
if ( false !== $cache ) {
return $cache;
}
$rows = Stats::get()->get_analytics_data(
[
'order' => 'ASC',
'objects' => true,
'pageview' => true,
'offset' => 0,
'perpage' => 5,
'type' => 'win',
]
);
$history = $this->get_graph_data_for_pages( \array_keys( $rows ) );
$rows = Stats::get()->set_page_position_graph( $rows, $history );
if ( empty( $rows ) ) {
$rows['response'] = 'No Data';
}
set_transient( $cache_key, $rows, DAY_IN_SECONDS );
return $rows;
}
/**
* Get top 5 losing posts.
*
* @return object
*/
public function get_losing_posts() {
global $wpdb;
$cache_key = Stats::get()->get_cache_key( 'losing_posts', Stats::get()->days . 'days' );
$cache = get_transient( $cache_key );
if ( false !== $cache ) {
return $cache;
}
$rows = Stats::get()->get_analytics_data(
[
'objects' => true,
'pageview' => true,
'offset' => 0,
'perpage' => 5,
'type' => 'lose',
]
);
$history = $this->get_graph_data_for_pages( \array_keys( $rows ) );
$rows = Stats::get()->set_page_position_graph( $rows, $history );
if ( empty( $rows ) ) {
$rows['response'] = 'No Data';
}
set_transient( $cache_key, $rows, DAY_IN_SECONDS );
return $rows;
}
/**
* Get graph data for pages.
*
* @param array $pages Pages to get data for.
*
* @return array
*/
public function get_graph_data_for_pages( $pages ) {
global $wpdb;
$intervals = Stats::get()->get_intervals();
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
$pages = \array_map( 'esc_sql', $pages );
$pages = '(\'' . join( '\', \'', $pages ) . '\')';
$query = $wpdb->prepare(
"SELECT page, date, MAX(CONCAT(t.uid, ':', t.range_group)) as range_group FROM
( SELECT page, DATE_FORMAT( created,'%%Y-%%m-%%d') as date, MAX( CONCAT( page, ':', DATE( created ), ':', LPAD( ( 100 - position ), 3, '0' ) ) ) as uid, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE page IN {$pages} AND created BETWEEN %s AND %s
GROUP BY page, range_group, DATE(created)
ORDER BY page ASC, DATE(created) DESC) AS t
GROUP BY t.page, t.range_group
ORDER BY date ASC",
Stats::get()->start_date,
Stats::get()->end_date
);
$data = $wpdb->get_results( $query );
$data = Stats::get()->extract_data_from_mixed( $data, 'range_group', ':', [ 'range_group', 'position' ] );
$data = Stats::get()->filter_graph_rows( $data );
return array_map( [ Stats::get(), 'normalize_graph_rows' ], $data );
}
/**
* Count indexable pages.
*
* @return mixed
*/
private function rows_found() {
return DB::objects()
->selectCount( 'page' )
->where( 'is_indexable', 1 )
->getVar();
}
}

View File

@@ -0,0 +1,535 @@
<?php
/**
* The Global functionality of the plugin.
*
* Defines the functionality loaded on admin.
*
* @since 1.0.15
* @package RankMathPro
* @subpackage RankMathPro\Rest
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use WP_Error;
use WP_REST_Server;
use RankMath\Helper;
use WP_REST_Request;
use WP_REST_Controller;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Google\PageSpeed;
use RankMath\SEO_Analysis\SEO_Analyzer;
use RankMathPro\Analytics\DB;
use MyThemeShop\Helpers\DB as DB_Helper;
defined( 'ABSPATH' ) || exit;
/**
* Rest class.
*/
class Rest extends WP_REST_Controller {
/**
* Constructor.
*/
public function __construct() {
$this->namespace = \RankMath\Rest\Rest_Helper::BASE . '/an';
}
/**
* Registers the routes for the objects of the controller.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/getKeywordPages',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ Keywords::get(), 'get_keyword_pages' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/postsOverview',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_posts_overview' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/getTrackedKeywords',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_tracked_keywords' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/getTrackedKeywordsRows',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_tracked_keywords_rows' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/getTrackedKeywordSummary',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_tracked_keyword_summary' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/trackedKeywordsOverview',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_tracked_keywords_overview' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/addTrackKeyword',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'add_track_keyword' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/autoAddFocusKeywords',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'auto_add_focus_keywords' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/removeTrackKeyword',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'remove_track_keyword' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/deleteTrackedKeywords',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'delete_all_tracked_keywords' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/getPagespeed',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'get_pagespeed' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/postsRows',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ Posts::get(), 'get_posts_rows' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/inspectionStats',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_inspection_stats' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
}
/**
* Determines if the current user can manage analytics.
*
* @return true
*/
public function has_permission() {
return current_user_can( 'rank_math_analytics' );
}
/**
* Get top 5 winning and losing posts rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_posts_overview( WP_REST_Request $request ) {
return rest_ensure_response(
[
'winningPosts' => Posts::get()->get_winning_posts(),
'losingPosts' => Posts::get()->get_losing_posts(),
]
);
}
/**
* Get tracked keywords rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_tracked_keywords( WP_REST_Request $request ) {
return rest_ensure_response(
[ 'rows' => Keywords::get()->get_tracked_keywords() ]
);
}
/**
* Get tracked keywords rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return array
*/
public function get_tracked_keywords_rows( WP_REST_Request $request ) {
return Keywords::get()->get_tracked_keywords_rows( $request );
}
/**
* Get tracked keywords summary.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_tracked_keyword_summary( WP_REST_Request $request ) {
\RankMathPro\Admin\Api::get()->get_settings();
return rest_ensure_response( Keywords::get()->get_tracked_keywords_summary() );
}
/**
* Get top 5 winning and losing tracked keywords overview.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_tracked_keywords_overview( WP_REST_Request $request ) {
return rest_ensure_response(
[
'winningKeywords' => Keywords::get()->get_tracked_winning_keywords(),
'losingKeywords' => Keywords::get()->get_tracked_losing_keywords(),
]
);
}
/**
* Add track keyword to DB.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function auto_add_focus_keywords( WP_REST_Request $request ) {
$data = $request->get_param( 'data' );
$secondary_keyword = ! empty( $data['secondary_keyword'] );
$post_types = ! empty( $data['post_types'] ) ? $data['post_types'] : [];
$all_opts = rank_math()->settings->all_raw();
$general = $all_opts['general'];
$general['auto_add_focus_keywords'] = $data;
Helper::update_all_settings( $general, null, null );
if ( empty( $post_types ) ) {
return false;
}
global $wpdb;
$focus_keywords = $wpdb->get_col(
"SELECT {$wpdb->postmeta}.meta_value FROM {$wpdb->posts} INNER JOIN {$wpdb->postmeta}
ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id
WHERE 1=1
AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_types ) ) . "')
AND {$wpdb->posts}.post_status = 'publish'
AND {$wpdb->postmeta}.meta_key = 'rank_math_focus_keyword'
"
);
$keywords_data = [];
foreach ( $focus_keywords as $focus_keyword ) {
$keywords = explode( ',', mb_strtolower( $focus_keyword ) );
if ( $secondary_keyword ) {
$keywords_data = array_merge( $keywords, $keywords_data );
} else {
$keywords_data[] = current( $keywords );
}
}
if ( empty( $keywords_data ) ) {
return false;
}
return DB::bulk_insert_query_focus_keyword_data( array_unique( $keywords_data ) );
}
/**
* Add track keyword to DB.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function add_track_keyword( WP_REST_Request $request ) {
$keywords = $request->get_param( 'keyword' );
$keywords = mb_strtolower( filter_var( $keywords, FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES ) );
if ( empty( $keywords ) ) {
return new WP_Error(
'param_value_empty',
esc_html__( 'Sorry, no keyword found.', 'rank-math-pro' )
);
}
$keywords = html_entity_decode( $keywords );
// Check remain keywords count can be added.
$total_keywords = Keywords::get()->get_tracked_keywords_count();
$new_keywords = Keywords::get()->extract_addable_track_keyword( $keywords );
$keywords_count = count( $new_keywords );
if ( $keywords_count <= 0 ) {
return false;
}
$summary = Keywords::get()->get_tracked_keywords_quota();
$remain = $summary['available'] - $total_keywords;
if ( $remain <= 0 ) {
return false;
}
// Add keywords.
Keywords::get()->add_track_keyword( $new_keywords );
$registered = Admin_Helper::get_registration_data();
if ( ! $registered || empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
return false;
}
// Send total keywords count to RankMath.
$total_keywords = Keywords::get()->get_tracked_keywords_count();
$response = \RankMathPro\Admin\Api::get()->keywords_info( $registered['username'], $registered['api_key'], $total_keywords );
if ( $response ) {
update_option( 'rank_math_keyword_quota', $response );
}
return true;
}
/**
* Remove track keyword from DB.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function remove_track_keyword( WP_REST_Request $request ) {
$keyword = $request->get_param( 'keyword' );
if ( empty( $keyword ) ) {
return new WP_Error(
'param_value_empty',
esc_html__( 'Sorry, no keyword found.', 'rank-math-pro' )
);
}
// Remove keyword.
Keywords::get()->remove_track_keyword( $keyword );
$registered = Admin_Helper::get_registration_data();
if ( ! $registered || empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
return false;
}
// Send total keywords count to RankMath.
$total_keywords = Keywords::get()->get_tracked_keywords_count();
$response = \RankMathPro\Admin\Api::get()->keywords_info( $registered['username'], $registered['api_key'], $total_keywords );
if ( $response ) {
update_option( 'rank_math_keyword_quota', $response );
}
return true;
}
/**
* Delete all the manually tracked keywords.
*/
public function delete_all_tracked_keywords() {
// Delete all keywords.
Keywords::get()->delete_all_tracked_keywords();
$registered = Admin_Helper::get_registration_data();
if ( empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
return false;
}
// Send total keywords count as 0 to RankMath.
$response = \RankMathPro\Admin\Api::get()->keywords_info( $registered['username'], $registered['api_key'], 0 );
if ( $response ) {
update_option( 'rank_math_keyword_quota', $response );
}
return true;
}
/**
* Check if keyword can be added.
*
* @param string $keywords Comma separated keywords.
* @return bool True if remain keyword count is larger than zero.
*/
private function can_add_keyword( $keywords = '' ) {
// Check remain keywords count can be added by supposing current keyword is added.
$total_keywords = Keywords::get()->get_tracked_keywords_count();
$new_keywords = Keywords::get()->extract_addable_track_keyword( $keywords );
$keywords_count = count( $new_keywords );
$summary = Keywords::get()->get_tracked_keywords_quota();
$remain = $summary['available'] - $total_keywords - $keywords_count;
return $remain >= 0;
}
/**
* Get page speed data.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return array|bool Pagespeed info on success, false on failure.
*/
public function get_pagespeed( WP_REST_Request $request ) {
$id = $request->get_param( 'id' );
if ( empty( $id ) ) {
return new WP_Error(
'param_value_empty',
esc_html__( 'Sorry, no record id found.', 'rank-math-pro' )
);
}
$post_id = $request->get_param( 'objectID' );
if ( empty( $id ) ) {
return new WP_Error(
'param_value_empty',
esc_html__( 'Sorry, no post id found.', 'rank-math-pro' )
);
}
if ( Helper::is_localhost() ) {
return [
'page_score' => 0,
'desktop_interactive' => 0,
'desktop_pagescore' => 0,
'mobile_interactive' => 0,
'mobile_pagescore' => 0,
'pagespeed_refreshed' => current_time( 'mysql' ),
];
}
$url = get_permalink( $post_id );
$pre = apply_filters( 'rank_math/analytics/pre_pagespeed', false, $post_id, $request );
if ( false !== $pre ) {
return $pre;
}
$force = \boolval( $request->get_param( 'force' ) );
$is_admin_bar = \boolval( $request->get_param( 'isAdminBar' ) );
if ( $force || ( ! $is_admin_bar && $this->should_update_pagespeed( $id ) ) ) {
// Page Score.
$analyzer = new SEO_Analyzer();
$analyzer->set_url();
$score = $analyzer->get_page_score( $url );
$update = [];
if ( $score > 0 ) {
$update['page_score'] = $score;
}
// PageSpeed desktop.
$desktop = PageSpeed::get_pagespeed( $url, 'desktop' );
if ( ! empty( $desktop ) ) {
$update = \array_merge( $update, $desktop );
$update['pagespeed_refreshed'] = current_time( 'mysql' );
}
// PageSpeed mobile.
$mobile = PageSpeed::get_pagespeed( $url, 'mobile' );
if ( ! empty( $mobile ) ) {
$update = \array_merge( $update, $mobile );
$update['pagespeed_refreshed'] = current_time( 'mysql' );
}
}
if ( ! empty( $update ) ) {
$update['id'] = $id;
$update['object_id'] = $post_id;
DB::update_object( $update );
}
return empty( $update ) ? false : $update;
}
/**
* Should update pagespeed record.
*
* @param int $id Database row id.
* @return bool
*/
private function should_update_pagespeed( $id ) {
$record = DB::objects()->where( 'id', $id )->one();
return \time() > ( \strtotime( $record->pagespeed_refreshed ) + ( DAY_IN_SECONDS * 7 ) );
}
/**
* Get inspection stats.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_inspection_stats() {
// Early Bail!!
if ( ! DB_Helper::check_table_exists( 'rank_math_analytics_inspections' ) ) {
return [
'presence' => [],
'status' => [],
];
}
return rest_ensure_response(
[
'presence' => Url_Inspection::get_presence_stats(),
'status' => Url_Inspection::get_status_stats(),
]
);
}
}

View File

@@ -0,0 +1,285 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
use MyThemeShop\Helpers\DB as DB_Helper;
defined( 'ABSPATH' ) || exit;
/**
* Summary class.
*/
class Summary {
use Hooker;
/**
* Constructor.
*/
public function __construct() {
if ( \RankMathPro\Google\Adsense::is_adsense_connected() ) {
$this->filter( 'rank_math/analytics/summary', 'get_adsense_summary' );
}
if ( \RankMath\Google\Analytics::is_analytics_connected() ) {
$this->filter( 'rank_math/analytics/summary', 'get_pageviews_summary' );
$this->filter( 'rank_math/analytics/get_widget', 'get_pageviews_summary' );
}
$this->filter( 'rank_math/analytics/summary', 'get_clicks_summary' );
$this->filter( 'rank_math/analytics/summary', 'get_g_update_summary' );
$this->filter( 'rank_math/analytics/posts_summary', 'get_posts_summary', 10, 3 );
$this->filter( 'rank_math/analytics/analytics_summary_graph', 'get_analytics_summary_graph', 10, 2 );
$this->filter( 'rank_math/analytics/analytics_tables_info', 'get_analytics_tables_info' );
}
/**
* Get posts summary.
*
* @param object $summary Posts summary.
* @param string $post_type Post type.
* @param string $query Query to get the summary data.
* @return object
*/
public function get_posts_summary( $summary, $post_type, $query ) {
if ( empty( $summary ) ) {
return $summary;
}
if ( $post_type && is_string( $post_type ) ) {
global $wpdb;
$query->leftJoin( $wpdb->prefix . 'rank_math_analytics_objects', $wpdb->prefix . 'rank_math_analytics_gsc.page', $wpdb->prefix . 'rank_math_analytics_objects.page' );
$query->where( $wpdb->prefix . 'rank_math_analytics_objects.object_subtype', sanitize_key( $post_type ) );
$summary = (object) $query->one();
}
$summary->pageviews = DB::traffic()
->selectSum( 'pageviews', 'pageviews' )
->whereBetween( 'created', [ Stats::get()->start_date, Stats::get()->end_date ] )
->getVar();
return $summary;
}
/**
* Get pageviews summary.
*
* @param object $stats Stats holder.
* @return object
*/
public function get_pageviews_summary( $stats ) {
$pageviews = DB::traffic()
->selectSum( 'pageviews', 'pageviews' )
->whereBetween( 'created', [ Stats::get()->start_date, Stats::get()->end_date ] )
->getVar();
$old_pageviews = DB::traffic()
->selectSum( 'pageviews', 'pageviews' )
->whereBetween( 'created', [ Stats::get()->compare_start_date, Stats::get()->compare_end_date ] )
->getVar();
$stats->pageviews = [
'total' => (int) $pageviews,
'previous' => (int) $old_pageviews,
'difference' => (int) $pageviews - (int) $old_pageviews,
];
return $stats;
}
/**
* Get adsense summary.
*
* @param object $stats Stats holder.
* @return object
*/
public function get_adsense_summary( $stats ) {
$stats->adsense = [
'total' => 0,
'previous' => 0,
'difference' => 0,
];
if ( DB_Helper::check_table_exists( 'rank_math_analytics_adsense' ) ) {
$earnings = DB::adsense()
->selectSum( 'earnings', 'earnings' )
->whereBetween( 'created', [ Stats::get()->start_date, Stats::get()->end_date ] )
->getVar();
$old_earnings = DB::adsense()
->selectSum( 'earnings', 'earnings' )
->whereBetween( 'created', [ Stats::get()->compare_start_date, Stats::get()->compare_end_date ] )
->getVar();
$stats->adsense = [
'total' => (int) $earnings,
'previous' => (int) $old_earnings,
'difference' => (int) $earnings - (int) $old_earnings,
];
}
return $stats;
}
/**
* Get analytics and adsense graph data.
*
* @param object $data Graph data.
* @param array $intervals Date intervals.
* @return array
*/
public function get_analytics_summary_graph( $data, $intervals ) {
global $wpdb;
if ( \RankMath\Google\Analytics::is_analytics_connected() ) {
$data->traffic = $this->get_traffic_graph( $intervals );
// Convert types.
$data->traffic = array_map( [ Stats::get(), 'normalize_graph_rows' ], $data->traffic );
// Merge for performance.
$data->merged = Stats::get()->get_merge_data_graph( $data->traffic, $data->merged, $intervals['map'] );
}
if ( \RankMathPro\Google\Adsense::is_adsense_connected() ) {
$data->adsense = $this->get_adsense_graph( $intervals );
// Convert types.
$data->adsense = array_map( [ Stats::get(), 'normalize_graph_rows' ], $data->adsense );
// Merge for performance.
$data->merged = Stats::get()->get_merge_data_graph( $data->adsense, $data->merged, $intervals['map'] );
}
return $data;
}
/**
* Get analytics graph data.
*
* @param array $intervals Date intervals.
* @return array
*/
public function get_traffic_graph( $intervals ) {
global $wpdb;
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, SUM(pageviews) as pageviews, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_ga
WHERE created BETWEEN %s AND %s
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$traffic_data = $wpdb->get_results( $query );
// phpcs:enable
return $traffic_data;
}
/**
* Get adsense graph data.
*
* @param array $intervals Date intervals.
* @return array
*/
public function get_adsense_graph( $intervals ) {
global $wpdb;
$adsense_data = [];
if ( DB_Helper::check_table_exists( 'rank_math_analytics_adsense' ) ) {
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, SUM(earnings) as earnings, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_adsense
WHERE created BETWEEN %s AND %s
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$adsense_data = $wpdb->get_results( $query );
// phpcs:enable
}
return $adsense_data;
}
/**
* Get clicks summary.
*
* @param object $stats Stats holder.
* @return object
*/
public function get_clicks_summary( $stats ) {
$clicks = DB::analytics()
->selectSum( 'clicks', 'clicks' )
->whereBetween( 'created', [ Stats::get()->start_date, Stats::get()->end_date ] )
->getVar();
$old_clicks = DB::analytics()
->selectSum( 'clicks', 'clicks' )
->whereBetween( 'created', [ Stats::get()->compare_start_date, Stats::get()->compare_end_date ] )
->getVar();
$stats->clicks = [
'total' => (int) $clicks,
'previous' => (int) $old_clicks,
'difference' => $clicks - $old_clicks,
];
return $stats;
}
/**
* Get google update summary.
*
* @param object $stats Stats holder.
* @return object
*/
public function get_g_update_summary( $stats ) {
if ( ! Helper::get_settings( 'general.google_updates' ) && ProAdminHelper::is_business_plan() ) {
$stats->graph->g_updates = null;
return $stats;
}
$stored = get_site_option( 'rank_math_pro_google_updates' );
$g_updates = json_decode( $stored );
$stats->graph->g_updates = $g_updates;
return $stats;
}
/**
* Get analytics tables info
*
* @param array $data Analytics tables info.
* @return array
*/
public function get_analytics_tables_info( $data ) {
$pro_data = DB::info();
$days = $data['days'] + $pro_data['days'];
$rows = $data['rows'] + $pro_data['rows'];
$size = $data['size'] + $pro_data['size'];
$data = compact( 'days', 'rows', 'size' );
return $data;
}
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* URL Inspection features.
*
* @since 3.0.8
* @package RankMathPro
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMathPro\Analytics\DB;
/**
* Url_Inspection class.
*/
class Url_Inspection {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
$this->filter( 'rank_math/analytics/url_inspection_map_properties', 'map_inspection_properties', 10, 2 );
$this->action( 'rank_math/analytics/get_inspections_query', 'add_filter_params', 10, 2 );
$this->action( 'rank_math/analytics/get_inspections_count_query', 'add_filter_params', 10, 2 );
$this->filter( 'rank_math/analytics/post_data', 'add_index_verdict_data', 10, 2 );
// Enqueue.
$this->action( 'rank_math/admin/enqueue_scripts', 'enqueue_scripts' );
}
/**
* Filter to alter the where clause used in the get_inspections function.
*
* @param string $where WHERE clause.
* @param array $params Parameters.
*
* @return string
*/
public function add_filter_params( $query, $params ) {
if ( empty( $params['indexingFilter'] ) ) {
return;
}
$table = DB::inspections()->table;
$query->where( "$table.coverage_state", $params['indexingFilter'] );
}
/**
* Map properties in the API result to columns in the database.
*
* @param array $normalized Normalized data.
* @param array $incoming Incoming data from the API.
*
* @return array
*/
public function map_inspection_properties( $normalized, $incoming ) {
$handler = \RankMath\Google\Url_Inspection::get();
$handler->assign_inspection_value( $incoming, 'richResultsResult.detectedItems', 'rich_results_items', $normalized );
$handler->assign_inspection_value( $incoming, 'indexStatusResult.lastCrawlTime', 'last_crawl_time', $normalized );
// Store the raw response, too.
$normalized['raw_api_response'] = wp_json_encode( $incoming );
return $normalized;
}
/**
* Get stats for "Presence on Google" widget.
*/
public static function get_presence_stats() {
return DB::get_presence_stats();
}
/**
* Get stats for "Top Statuses" widget.
*/
public static function get_status_stats() {
return DB::get_status_stats();
}
/**
* Change user perference.
*
* @param array $data array.
* @param WP_REST_Request $request post object.
* @return array $data sorted array.
*/
public function add_index_verdict_data( $data, \WP_REST_Request $request ) {
if ( ! Helper::can_add_index_status() ) {
return $data;
}
$data['indexStatus'] = DB::get_index_verdict( $data['page'] );
return $data;
}
/**
* Enqueue scripts.
*/
public function enqueue_scripts() {
$screen = get_current_screen();
if ( 'rank-math_page_rank-math-analytics' !== $screen->id ) {
return;
}
$submit_url = add_query_arg(
[
'page' => 'instant-indexing',
'tab' => 'console',
'apiaction' => 'update',
'_wpnonce' => wp_create_nonce( 'giapi-action' ),
'apipostid[]' => '',
],
admin_url( 'admin.php' )
);
$settings = get_option( 'rank-math-options-instant-indexing', [] );
Helper::add_json(
'instantIndexingSupport',
[
'isPluginActive' => is_plugin_active( 'fast-indexing-api/instant-indexing.php' ),
'isGoogleConfigured' => ! empty( $settings['json_key'] ),
'submitUrl' => $submit_url,
]
);
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* Google AdSense.
*
* @since 1.0.34
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Google;
use RankMath\Google\Api;
use RankMath\Helpers\Security;
use RankMath\Analytics\Workflow\Base;
use WP_Error;
defined( 'ABSPATH' ) || exit;
/**
* AdSense class.
*/
class Adsense {
/**
* Get adsense accounts.
*
* @return array
*/
public static function get_adsense_accounts() {
$accounts = [];
$response = Api::get()->http_get( 'https://adsense.googleapis.com/v2/accounts' );
if (
! Api::get()->is_success() ||
isset( $response->error ) ||
! isset( $response['accounts'] ) ||
! is_array( $response['accounts'] )
) {
return $accounts;
}
foreach ( $response['accounts'] as $account ) {
$accounts[ $account['name'] ] = [
'name' => $account['displayName'],
];
}
return $accounts;
}
/**
* Query adsense data from google client api.
*
* @param string $start_date Start date.
* @param string $end_date End date.
*
* @return array
*/
public static function get_adsense( $options = [] ) {
$account_id = isset( $options['account_id'] ) ? $options['account_id'] : self::get_adsense_id();
$start_date = isset( $options['start_date'] ) ? $options['start_date'] : '';
$end_date = isset( $options['end_date'] ) ? $options['end_date'] : '';
if ( ! $account_id || ! $start_date || ! $end_date ) {
return false;
}
$request = Security::add_query_arg_raw(
[
'startDate.year' => gmdate( 'Y', strtotime( $start_date ) ),
'startDate.month' => gmdate( 'n', strtotime( $start_date ) ),
'startDate.day' => gmdate( 'j', strtotime( $start_date ) ),
'endDate.year' => gmdate( 'Y', strtotime( $end_date ) ),
'endDate.month' => gmdate( 'n', strtotime( $end_date ) ),
'endDate.day' => gmdate( 'j', strtotime( $end_date ) ),
'dimensions' => 'DATE',
'currencyCode' => 'USD',
'metrics' => 'ESTIMATED_EARNINGS',
],
'https://adsense.googleapis.com/v2/' . $account_id . '/reports:generate'
);
$workflow = 'adsense';
Api::get()->set_workflow( $workflow );
$response = Api::get()->http_get( $request );
Api::get()->log_failed_request( $response, $workflow, $start_date, func_get_args() );
if ( ! Api::get()->is_success() ) {
return new WP_Error( 'adsense_api_fail', 'Google AdSense API request failed.' );
}
if ( ! isset( $response['rows'] ) ) {
return false;
}
return $response['rows'];
}
/**
* Get adsense id.
*
* @return string
*/
public static function get_adsense_id() {
static $rank_math_adsense_id;
if ( is_null( $rank_math_adsense_id ) ) {
$options = get_option( 'rank_math_google_analytic_options' );
$rank_math_adsense_id = ! empty( $options['adsense_id'] ) ? $options['adsense_id'] : false;
}
return $rank_math_adsense_id;
}
/**
* Is adsense connected.
*
* @return boolean
*/
public static function is_adsense_connected() {
$account = wp_parse_args(
get_option( 'rank_math_google_analytic_options' ),
[ 'adsense_id' => '' ]
);
return ! empty( $account['adsense_id'] );
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Google PageSpeed.
*
* @since 1.0.34
* @package RankMath
* @subpackage RankMath\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Google;
use RankMath\Google\Api;
use RankMath\Helpers\Security;
defined( 'ABSPATH' ) || exit;
/**
* PageSpeed class.
*/
class PageSpeed {
/**
* Get pagespeed score info.
*
* @param string $url Url to get pagespeed for.
* @param string $strategy Data for desktop or mobile.
*
* @return array
*/
public static function get_pagespeed( $url, $strategy = 'desktop' ) {
$response = Api::get()->http_get( 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed?category=PERFORMANCE&url=' . \rawurlencode( $url ) . '&strategy=' . \strtoupper( $strategy ), [], 30 );
if ( ! Api::get()->is_success() ) {
return false;
}
return [
$strategy . '_interactive' => round( \floatval( $response['lighthouseResult']['audits']['interactive']['displayValue'] ), 0 ),
$strategy . '_pagescore' => round( $response['lighthouseResult']['categories']['performance']['score'] * 100, 0 ),
];
}
}

View File

@@ -0,0 +1 @@
<?php // Silence is golden.

View File

@@ -0,0 +1,44 @@
<?php
/**
* Analytics Report header template.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-info">
<tr>
<td>
<h1><?php esc_html_e( 'SEO Report of Your Website', 'rank-math-pro' ); ?></h1>
###TOP_HTML###
<h2 class="report-date">###START_DATE### - ###END_DATE###</h2>
<a href="###SITE_URL###" target="_blank" class="site-url">###SITE_URL_SIMPLE###</a>
</td>
<?php if ( $this->get_setting( 'link_full_report', true ) ) : ?>
<td class="full-report-link">
<a href="###REPORT_URL###" target="_blank" class="full-report-link">
<?php esc_html_e( 'FULL REPORT', 'rank-math-pro' ); ?>
<?php $this->image( 'report-icon-external.png', 12, 12, __( 'External Link Icon', 'rank-math-pro' ) ); ?>
</a>
</td>
<?php endif; ?>
</tr>
</table>
<?php if ( $this->get_variable( 'stats_invalid_data' ) ) { ?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-error">
<tr>
<td>
<h2><?php esc_html_e( 'Uh-oh', 'rank-math-pro' ); ?></h2>
<p><em><?php esc_html_e( 'It seems that there are no stats to show right now.', 'rank-math-pro' ); ?></em></p>
<?php // Translators: placeholders are anchor opening and closing tags. ?>
<p><?php printf( esc_html__( 'If you can see the site data in your Search Console and Analytics accounts, but not here, then %1$s try reconnecting your account %2$s and make sure that the correct properties are selected in the %1$s Analytics Settings%2$s.', 'rank-math-pro' ), '<a href="' . Helper::get_admin_url( 'options-general#setting-panel-analytics' ) . '">', '</a>' ); ?></p>
</td>
</tr>
</table>
<?php } ?>

View File

@@ -0,0 +1,69 @@
<?php
/**
* Analytics Report email styling.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
defined( 'ABSPATH' ) || exit;
?>
<style>
.header {
background: ###HEADER_BACKGROUND###;
}
tr.keywords-table-spacer {
height: 14px;
}
table.stats-table tr.table-heading {
background: #243B53;
color: #fff;
font-weight: 500;
}
.stats-table {
overflow: hidden;
}
.stats-table td {
padding: 10px;
}
.stats-table tr:nth-child(2n+1) {
background: #F0F4F8;
}
.stats-table .stat-value {
font-size: 16px;
}
.stats-table .stat-diff, .stats-table .diff-sign {
font-size: 14px;
}
.report-heading {
margin: 40px 0 20px 0;
}
span.post-title, span.post-url {
display: block;
max-width: 250px;
}
.stats-table a {
color: #3f77d6;
font-size: 15px;
font-weight: 600;
}
span.post-url {
color: #92949f;
font-size: 14px;
font-weight: normal;
}
###CUSTOM_CSS###
</style>

View File

@@ -0,0 +1,45 @@
<?php
/**
* Analytics Report email template.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
defined( 'ABSPATH' ) || exit;
$sections = $this->get_setting( 'sections', [ 'summary', 'positions', 'winning_posts', 'winning_keywords', 'losing_keywords' ] );
$analytics = get_option( 'rank_math_google_analytic_options' );
$is_analytics_connected = ! empty( $analytics ) && ! empty( $analytics['view_id'] );
// Header & optional sections.
$this->template_part( 'header' );
$this->template_part( 'header-after' );
foreach ( $sections as $section ) {
$template = str_replace( '_', '-', $section );
$this->template_part( "sections/{$template}", [ 'analytics_connected' => $is_analytics_connected ] );
}
// phpcs:enable
?>
<?php if ( $this->get_setting( 'link_full_report', true ) ) : ?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="details-button">
<tr class="button">
<td align="left">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left" style="padding-bottom: 0;">
<a href="###REPORT_URL###" target="_blank"><?php esc_html_e( 'VIEW DETAILED ANALYTICS', 'rank-math-pro' ); ?></a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<?php endif; ?>
<?php $this->template_part( 'footer' ); ?>

View File

@@ -0,0 +1,65 @@
<?php
/**
* Analytics Report Losing Keywords.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMathPro\Analytics\Email_Reports;
use MyThemeShop\Helpers\Str;
defined( 'ABSPATH' ) || exit;
$keywords = (array) $this->get_variable( 'losing_keywords' );
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-heading">
<tr>
<td>
<h2><?php esc_html_e( 'Top Losing Keywords', 'rank-math-pro' ); ?></h2>
</td>
</tr>
</table>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="keywords-table-wrapper">
<tr>
<td>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="keywords-table stats-table losing-keywords">
<tr class="table-heading">
<td class="col-1">
<?php esc_html_e( 'Keywords', 'rank-math-pro' ); ?>
</td>
<td class="col-2">
<?php esc_html_e( 'Impressions', 'rank-math-pro' ); ?>
</td>
<td class="col-3">
<?php esc_html_e( 'Position', 'rank-math-pro' ); ?>
</td>
</tr>
<?php foreach ( $keywords as $keyword => $data ) : ?>
<?php if ( ! is_array( $data ) ) { continue; } ?>
<tr>
<td style="width:280px;box-sizing:border-box;">
<span title="<?php echo esc_html( $keyword ); ?>"><?php echo esc_html( Str::truncate( $keyword, 36, '...' ) ); ?></span>
</td>
<td>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'impressions' ) ); ?>
</td>
<td>
<?php $this->template_part( 'stat', array_merge( Email_Reports::get_stats_val( $data, 'position' ), [ 'invert' => true ] ) ); ?>
</td>
</tr>
<?php endforeach; ?>
<?php if ( empty( $keywords ) ) : ?>
<tr>
<td colspan="3">
<?php esc_html_e( 'No data to show.', 'rank-math-pro' ); ?>
</td>
</tr>
<?php endif; ?>
</table>
</td>
</tr>
</table>

View File

@@ -0,0 +1,73 @@
<?php
/**
* Analytics Report Losing Posts.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMathPro\Analytics\Email_Reports;
use MyThemeShop\Helpers\Str;
defined( 'ABSPATH' ) || exit;
$posts = (array) $this->get_variable( 'losing_posts' );
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-heading">
<tr>
<td>
<h2><?php esc_html_e( 'Top Losing Posts', 'rank-math-pro' ); ?></h2>
</td>
</tr>
</table>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="traffic-table stats-table">
<tr class="table-heading">
<td class="col-1">
<?php esc_html_e( 'Post', 'rank-math-pro' ); ?>
</td>
<?php if ( ! empty( $analytics_connected ) ) : ?>
<td class="col-2">
<?php esc_html_e( 'Search Traffic', 'rank-math-pro' ); ?>
</td>
<?php else : ?>
<td class="col-2">
<?php esc_html_e( 'Impressions', 'rank-math-pro' ); ?>
</td>
<?php endif; ?>
<td class="col-3">
<?php esc_html_e( 'Position', 'rank-math-pro' ); ?>
</td>
</tr>
<?php foreach ( $posts as $post_url => $data ) : // phpcs:disable ?>
<?php if ( ! is_array( $data ) ) { continue; } ?>
<tr>
<td>
<a href="###SITE_URL###<?php echo esc_attr( $post_url ); ?>" target="_blank">
<span class="post-title"><?php echo esc_html( Str::truncate( ( ! empty( $data['title'] ) ? $data['title'] : $data['page'] ), 55, '...' ) ); ?></span>
<span class="post-url"><?php echo esc_html( Email_Reports::shorten_url( $post_url, 30, '...' ) ); ?></span>
</a>
</td>
<td>
<?php if ( ! empty( $analytics_connected ) ) : ?>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'pageviews' ) ); ?>
<?php else : ?>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'impressions' ) ); ?>
<?php endif; ?>
</td>
<td>
<?php $this->template_part( 'stat', array_merge( Email_Reports::get_stats_val( $data, 'position' ), [ 'invert' => true ] ) ); ?>
</td>
</tr>
<?php endforeach; ?>
<?php if ( empty( $posts ) ) : ?>
<tr>
<td colspan="3">
<?php esc_html_e( 'No data to show.', 'rank-math-pro' ); ?>
</td>
</tr>
<?php endif; ?>
</table>

View File

@@ -0,0 +1,117 @@
<?php
/**
* Analytics Report summary table template.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
defined( 'ABSPATH' ) || exit;
$analytics = get_option( 'rank_math_google_analytic_options' );
$is_analytics_connected = ! empty( $analytics ) && ! empty( $analytics['view_id'] );
?>
<?php if ( $this->get_variable( 'stats_invalid_data' ) ) { ?>
<?php return; ?>
<?php } ?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="stats">
<tr>
<?php if ( $is_analytics_connected ) : ?>
<td class="col-1">
<h3><?php esc_html_e( 'Search Traffic', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_traffic' ),
'diff' => $this->get_variable( 'stats_traffic_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'traffic' ),
]
);
?>
</td>
<?php else : ?>
<td class="col-1">
<h3><?php esc_html_e( 'Total Impressions', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_impressions' ),
'diff' => $this->get_variable( 'stats_impressions_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'impressions' ),
]
);
?>
</td>
<?php endif; ?>
<?php if ( ! $is_analytics_connected ) : ?>
<td class="col-2">
<h3><?php esc_html_e( 'Total Clicks', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_clicks' ),
'diff' => $this->get_variable( 'stats_clicks_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'clicks' ),
]
);
?>
</td>
<?php else : ?>
<td class="col-2">
<h3><?php esc_html_e( 'Total Impressions', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_impressions' ),
'diff' => $this->get_variable( 'stats_impressions_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'impressions' ),
]
);
?>
</td>
<?php endif; ?>
</tr>
<tr>
<td class="col-1">
<h3><?php esc_html_e( 'Total Keywords', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_keywords' ),
'diff' => $this->get_variable( 'stats_keywords_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'keywords' ),
]
);
?>
</td>
<td class="col-2">
<h3><?php esc_html_e( 'Average Position', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_position' ),
'diff' => $this->get_variable( 'stats_position_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'position' ),
'graph_modifier' => -100,
'human_number' => false,
]
);
?>
</td>
</tr>
</table>

View File

@@ -0,0 +1,65 @@
<?php
/**
* Analytics Report Winning Keywords.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMathPro\Analytics\Email_Reports;
use MyThemeShop\Helpers\Str;
defined( 'ABSPATH' ) || exit;
$keywords = (array) $this->get_variable( 'winning_keywords' );
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-heading">
<tr>
<td>
<h2><?php esc_html_e( 'Top Winning Keywords', 'rank-math-pro' ); ?></h2>
</td>
</tr>
</table>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="keywords-table-wrapper">
<tr>
<td>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="keywords-table stats-table winning-keywords">
<tr class="table-heading">
<td class="col-1">
<?php esc_html_e( 'Keywords', 'rank-math-pro' ); ?>
</td>
<td class="col-2">
<?php esc_html_e( 'Impressions', 'rank-math-pro' ); ?>
</td>
<td class="col-3">
<?php esc_html_e( 'Position', 'rank-math-pro' ); ?>
</td>
</tr>
<?php foreach ( $keywords as $keyword => $data ) : ?>
<?php if ( ! is_array( $data ) ) { continue; } ?>
<tr>
<td style="width:280px;box-sizing:border-box;">
<span title="<?php echo esc_html( $keyword ); ?>"><?php echo esc_html( Str::truncate( $keyword, 36, '...' ) ); ?></span>
</td>
<td>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'impressions' ) ); ?>
</td>
<td>
<?php $this->template_part( 'stat', array_merge( Email_Reports::get_stats_val( $data, 'position' ), [ 'invert' => true ] ) ); ?>
</td>
</tr>
<?php endforeach; ?>
<?php if ( empty( $keywords ) ) : ?>
<tr>
<td colspan="3">
<?php esc_html_e( 'No data to show.', 'rank-math-pro' ); ?>
</td>
</tr>
<?php endif; ?>
</table>
</td>
</tr>
</table>

View File

@@ -0,0 +1,73 @@
<?php
/**
* Analytics Report Winning Posts.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMathPro\Analytics\Email_Reports;
use MyThemeShop\Helpers\Str;
defined( 'ABSPATH' ) || exit;
$posts = (array) $this->get_variable( 'winning_posts' );
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-heading">
<tr>
<td>
<h2><?php esc_html_e( 'Top Winning Posts', 'rank-math-pro' ); ?></h2>
</td>
</tr>
</table>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="traffic-table stats-table">
<tr class="table-heading">
<td class="col-1">
<?php esc_html_e( 'Post', 'rank-math-pro' ); ?>
</td>
<?php if ( ! empty( $analytics_connected ) ) : ?>
<td class="col-2">
<?php esc_html_e( 'Search Traffic', 'rank-math-pro' ); ?>
</td>
<?php else : ?>
<td class="col-2">
<?php esc_html_e( 'Impressions', 'rank-math-pro' ); ?>
</td>
<?php endif; ?>
<td class="col-3">
<?php esc_html_e( 'Position', 'rank-math-pro' ); ?>
</td>
</tr>
<?php foreach ( $posts as $post_url => $data ) : // phpcs:disable ?>
<?php if ( ! is_array( $data ) ) { continue; } ?>
<tr>
<td>
<a href="###SITE_URL###<?php echo esc_attr( $post_url ); ?>" target="_blank">
<span class="post-title"><?php echo esc_html( Str::truncate( ( ! empty( $data['title'] ) ? $data['title'] : $data['page'] ), 55, '...' ) ); ?></span>
<span class="post-url"><?php echo esc_html( Email_Reports::shorten_url( $post_url, 30, '...' ) ); ?></span>
</a>
</td>
<td>
<?php if ( ! empty( $analytics_connected ) ) : ?>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'pageviews' ) ); ?>
<?php else : ?>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'impressions' ) ); ?>
<?php endif; ?>
</td>
<td>
<?php $this->template_part( 'stat', array_merge( Email_Reports::get_stats_val( $data, 'position' ), [ 'invert' => true ] ) ); ?>
</td>
</tr>
<?php endforeach; ?>
<?php if ( empty( $posts ) ) : ?>
<tr>
<td colspan="3">
<?php esc_html_e( 'No data to show.', 'rank-math-pro' ); ?>
</td>
</tr>
<?php endif; ?>
</table>

View File

@@ -0,0 +1,93 @@
<?php
/**
* Google Adsense.
*
* @since 1.0.49
* @package RankMathPro
* @subpackage RankMathPro\Adsense
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use Exception;
use MyThemeShop\Helpers\DB;
use RankMath\Analytics\Workflow\Base;
use function as_unschedule_all_actions;
defined( 'ABSPATH' ) || exit;
/**
* Adsense class.
*/
class Adsense extends Base {
/**
* Constructor.
*/
public function __construct() {
// If adsense is not connected, no need to proceed.
if ( ! \RankMathPro\Google\Adsense::is_adsense_connected() ) {
return;
}
$this->action( 'rank_math/analytics/workflow/adsense', 'kill_jobs', 5, 0 );
$this->action( 'rank_math/analytics/workflow/create_tables', 'create_tables' );
$this->action( 'rank_math/analytics/workflow/adsense', 'create_tables', 6, 0 );
$this->action( 'rank_math/analytics/workflow/adsense', 'create_data_jobs', 10, 3 );
}
/**
* Kill jobs.
*
* Stop processing queue items, clear cronjob and delete all batches.
*/
public function kill_jobs() {
as_unschedule_all_actions( 'rank_math/analytics/get_adsense_data' );
}
/**
* Create tables.
*/
public function create_tables() {
global $wpdb;
$collate = $wpdb->get_charset_collate();
$table = 'rank_math_analytics_adsense';
// Early Bail!!
if ( DB::check_table_exists( $table ) ) {
return;
}
$schema = "CREATE TABLE {$wpdb->prefix}{$table} (
id bigint(20) unsigned NOT NULL auto_increment,
created timestamp NOT NULL,
earnings double NOT NULL default 0,
PRIMARY KEY (id)
) $collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
try {
dbDelta( $schema );
} catch ( Exception $e ) { // phpcs:ignore
// Will log.
}
}
/**
* Create jobs to fetch data.
*
* @param integer $days Number of days to fetch from past.
* @param string $prev Previous saved value.
* @param string $new New posted value.
*/
public function create_data_jobs( $days, $prev, $new ) {
// If saved and new profile are same.
if ( ! $this->is_profile_updated( 'adsense_id', $prev, $new ) ) {
return;
}
$this->schedule_single_action( $days, 'adsense' );
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* Google Analytics.
*
* @since 1.0.49
* @package RankMathPro
* @subpackage RankMathPro\Analytics
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use Exception;
use MyThemeShop\Helpers\DB;
use RankMath\Analytics\Workflow\Base;
use function as_unschedule_all_actions;
defined( 'ABSPATH' ) || exit;
/**
* Analytics class.
*/
class Analytics extends Base {
/**
* Constructor.
*/
public function __construct() {
// If analytics is not connected, no need to proceed.
if ( ! \RankMath\Google\Analytics::is_analytics_connected() ) {
return;
}
$this->action( 'rank_math/analytics/workflow/analytics', 'kill_jobs', 5, 0 );
$this->action( 'rank_math/analytics/workflow/create_tables', 'create_tables' );
$this->action( 'rank_math/analytics/workflow/analytics', 'create_tables', 6, 0 );
$this->action( 'rank_math/analytics/workflow/analytics', 'create_data_jobs', 10, 3 );
}
/**
* Kill jobs.
*
* Stop processing queue items, clear cronjob and delete all batches.
*/
public function kill_jobs() {
as_unschedule_all_actions( 'rank_math/analytics/get_analytics_data' );
}
/**
* Create tables.
*/
public function create_tables() {
global $wpdb;
$collate = $wpdb->get_charset_collate();
$table = 'rank_math_analytics_ga';
// Early Bail!!
if ( DB::check_table_exists( $table ) ) {
return;
}
$schema = "CREATE TABLE {$wpdb->prefix}{$table} (
id bigint(20) unsigned NOT NULL auto_increment,
page varchar(500) NOT NULL,
created timestamp NOT NULL,
pageviews mediumint(6) NOT NULL,
visitors mediumint(6) NOT NULL,
PRIMARY KEY (id),
KEY analytics_object_analytics (page(190))
) $collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
try {
dbDelta( $schema );
} catch ( Exception $e ) { // phpcs:ignore
// Will log.
}
}
/**
* Create jobs to fetch data.
*
* @param integer $days Number of days to fetch from past.
* @param string $prev Previous saved value.
* @param string $new New posted value.
*/
public function create_data_jobs( $days, $prev, $new ) {
// If saved and new profile are same.
if ( ! $this->is_profile_updated( 'view_id', $prev, $new ) ) {
return;
}
// Fetch now.
$this->schedule_single_action( $days, 'analytics' );
}
}

View File

@@ -0,0 +1,325 @@
<?php
/**
* Jobs.
*
* @since 1.0.54
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use DateTime;
use Exception;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMathPro\Analytics\DB;
use RankMath\Analytics\Workflow\Base;
use RankMath\Analytics\DB as AnalyticsDB;
use RankMathPro\Google\Adsense;
use RankMath\Google\Analytics;
use RankMath\Analytics\Workflow\Jobs as AnalyticsJobs;
defined( 'ABSPATH' ) || exit;
/**
* Jobs class.
*/
class Jobs {
use Hooker;
/**
* Is an Analytics account connected?
*
* @var boolean
*/
private $analytics_connected = false;
/**
* Is an AdSense account connected?
*
* @var boolean
*/
private $adsense_connected = false;
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Jobs
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Jobs ) ) {
$instance = new Jobs();
$instance->hooks();
}
return $instance;
}
/**
* Hooks.
*/
public function hooks() {
$this->analytics_connected = Analytics::is_analytics_connected();
$this->adsense_connected = \RankMathPro\Google\Adsense::is_adsense_connected();
// Check missing data for analytics and adsense.
$this->action( 'rank_math/analytics/data_fetch', 'data_fetch' );
// Data Fetcher.
if ( $this->adsense_connected ) {
$this->filter( 'rank_math/analytics/get_adsense_days', 'get_adsense_days' );
$this->action( 'rank_math/analytics/get_adsense_data', 'get_adsense_data', 10, 2 );
}
if ( $this->analytics_connected ) {
$this->action( 'rank_math/analytics/get_analytics_days', 'get_analytics_days' );
$this->action( 'rank_math/analytics/get_analytics_data', 'get_analytics_data' );
$this->action( 'rank_math/analytics/handle_analytics_response', 'handle_analytics_response' );
$this->action( 'rank_math/analytics/clear_cache', 'clear_cache' );
}
// Cache.
$this->action( 'rank_math/analytics/purge_cache', 'purge_cache' );
$this->action( 'rank_math/analytics/delete_by_days', 'delete_by_days' );
$this->action( 'rank_math/analytics/delete_data_log', 'delete_data_log' );
}
/**
* Check missing data for analytics and adsense. Perform this task periodically.
*/
public function data_fetch() {
if ( $this->analytics_connected ) {
AnalyticsJobs::get()->check_for_missing_dates( 'analytics' );
}
if ( $this->adsense_connected ) {
AnalyticsJobs::get()->check_for_missing_dates( 'adsense' );
}
}
/**
* Set the analytics start and end dates.
*/
public function get_analytics_days( $args = [] ) {
$rows = Analytics::get_analytics(
[
'start_date' => $args['start_date'],
'end_date' => $args['end_date'],
],
true
);
if ( is_wp_error( $rows ) || empty( $rows ) ) {
return [];
}
$empty_dates = get_option( 'rank_math_analytics_empty_dates', [] );
$dates = [];
foreach ( $rows as $row ) {
$date = '';
// GA4
if ( isset( $row['dimensionValues'] ) ) {
$date = $row['dimensionValues'][0]['value'];
} elseif ( isset( $row['dimensions'] ) ) {
$date = $row['dimensions'][0];
}
if ( ! empty( $date ) ) {
$date = substr( $date, 0, 4 ) . '-' . substr( $date, 4, 2 ) . '-' . substr( $date, 6, 2 );
if ( ! AnalyticsDB::date_exists( $date, 'analytics' ) && ! in_array( $date, $empty_dates, true ) ) {
$dates[] = [
'start_date' => $date,
'end_date' => $date,
];
}
}
}
return $dates;
}
/**
* Get analytics data and save it into database.
*
* @param string $date Date to fetch data for.
*/
public function get_analytics_data( $date ) {
$rows = Analytics::get_analytics(
[
'start_date' => $date,
'end_date' => $date,
]
);
if ( is_wp_error( $rows ) || empty( $rows ) ) {
return [];
}
try {
DB::add_analytics_bulk( $date, $rows );
return $rows;
} catch ( Exception $e ) {} // phpcs:ignore
}
/**
* Set the AdSense start and end dates.
*/
public function get_adsense_days( $args = [] ) {
$dates = [];
$begin = new DateTime( $args['start_date'] );
$end = new DateTime( $args['end_date'] );
$missing_dates = [];
for ( $i = $end; $i >= $begin; $i->modify( '-1 day' ) ) {
$date = $i->format( 'Y-m-d' );
if ( ! AnalyticsDB::date_exists( $date, 'adsense' ) ) {
$missing_dates[] = $date;
}
}
if ( empty( $missing_dates ) ) {
$dates[] = [
'start_date' => $args['start_date'],
'end_date' => $args['end_date'],
];
return $dates;
}
// Request for one date range because its not large data to send individual request for each date.
$dates[] = [
'start_date' => $missing_dates[ count( $missing_dates ) - 1 ],
'end_date' => $missing_dates[0],
];
return $dates;
}
/**
* Get adsense data and save it into database.
*
* @param string $start_date The start date to fetch.
* @param string $end_date The end date to fetch.
*/
public function get_adsense_data( $start_date = '', $end_date = '' ) {
$rows = Adsense::get_adsense(
[
'start_date' => $start_date,
'end_date' => $end_date,
]
);
if ( is_wp_error( $rows ) || empty( $rows ) ) {
return [];
}
try {
DB::add_adsense( $rows );
return $rows;
} catch ( Exception $e ) {} // phpcs:ignore
}
/**
* Handlle analytics response.
*
* @param array $data API request and response data.
*/
public function handle_analytics_response( $data = [] ) {
if ( 200 !== $data['code'] ) {
return;
}
if ( isset( $data['formatted_response']['rows'] ) && ! empty( $data['formatted_response']['rows'] ) ) {
return;
}
$dates = get_option( 'rank_math_analytics_empty_dates', [] );
if ( ! $dates ) {
$dates = [];
}
$dates[] = $data['args']['dateRanges'][0]['startDate'];
$dates[] = $data['args']['dateRanges'][0]['endDate'];
$dates = array_unique( $dates );
update_option( 'rank_math_analytics_empty_dates', $dates );
}
/**
* Clear cache.
*/
public function clear_cache() {
global $wpdb;
// Delete all useless data from analytics data table.
$wpdb->get_results( "DELETE FROM {$wpdb->prefix}rank_math_analytics_ga WHERE page NOT IN ( SELECT page from {$wpdb->prefix}rank_math_analytics_objects )" );
}
/**
* Purge cache.
*
* @param object $table Table insance.
*/
public function purge_cache( $table ) {
$table->whereLike( 'option_name', 'losing_posts' )->delete();
$table->whereLike( 'option_name', 'winning_posts' )->delete();
$table->whereLike( 'option_name', 'losing_keywords' )->delete();
$table->whereLike( 'option_name', 'winning_keywords' )->delete();
$table->whereLike( 'option_name', 'tracked_keywords_summary' )->delete();
}
/**
* Delete analytics and adsense data by days.
*
* @param int $days Decide whether to delete all or delete 90 days data.
*/
public function delete_by_days( $days ) {
if ( -1 === $days ) {
if ( $this->analytics_connected ) {
DB::traffic()->truncate();
}
if ( $this->adsense_connected ) {
DB::adsense()->truncate();
}
return;
}
$start = date_i18n( 'Y-m-d H:i:s', strtotime( '-1 days' ) );
$end = date_i18n( 'Y-m-d H:i:s', strtotime( '-' . $days . ' days' ) );
if ( $this->analytics_connected ) {
DB::traffic()->whereBetween( 'created', [ $end, $start ] )->delete();
}
if ( $this->adsense_connected ) {
DB::adsense()->whereBetween( 'created', [ $end, $start ] )->delete();
}
}
/**
* Delete record for comparison.
*
* @param string $start Start date.
*/
public function delete_data_log( $start ) {
if ( $this->analytics_connected ) {
DB::traffic()->where( 'created', '<', $start )->delete();
}
if ( $this->adsense_connected ) {
DB::adsense()->where( 'created', '<', $start )->delete();
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* Install Keyword manager.
*
* @since 1.0.49
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use Exception;
use MyThemeShop\Helpers\DB;
use RankMath\Analytics\Workflow\Base;
defined( 'ABSPATH' ) || exit;
/**
* Keywords class.
*/
class Keywords extends Base {
/**
* Constructor.
*/
public function __construct() {
$done = \boolval( get_option( 'rank_math_analytics_pro_installed' ) );
if ( $done ) {
return;
}
$this->create_keywords_tables();
update_option( 'rank_math_analytics_pro_installed', true );
}
/**
* Create keywords tables.
*/
public function create_keywords_tables() {
global $wpdb;
$collate = $wpdb->get_charset_collate();
$table = 'rank_math_analytics_keyword_manager';
// Early Bail!!
if ( DB::check_table_exists( $table ) ) {
return;
}
$schema = "CREATE TABLE {$wpdb->prefix}{$table} (
id bigint(20) unsigned NOT NULL auto_increment,
keyword varchar(1000) NOT NULL,
collection varchar(200) NULL,
is_active tinyint(1) NOT NULL default 1,
PRIMARY KEY (id)
) $collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
try {
dbDelta( $schema );
} catch ( Exception $e ) { // phpcs:ignore
// Will log.
}
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* Workflow.
*
* @since 1.0.54
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use RankMath\Traits\Hooker;
use function as_enqueue_async_action;
use function as_unschedule_all_actions;
defined( 'ABSPATH' ) || exit;
/**
* Workflow class.
*/
class Workflow {
use Hooker;
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Workflow
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Workflow ) ) {
$instance = new Workflow();
$instance->hooks();
}
return $instance;
}
/**
* Hooks.
*/
public function hooks() {
// Common.
$this->action( 'rank_math/analytics/workflow', 'maybe_first_install', 5, 0 );
$this->action( 'rank_math/analytics/workflow/create_tables', 'create_tables_only', 5 );
// Services.
$this->action( 'rank_math/analytics/workflow/analytics', 'init_analytics_workflow', 5, 0 );
$this->action( 'rank_math/analytics/workflow/adsense', 'init_adsense_workflow', 5, 0 );
}
/**
* Maybe first install.
*/
public function maybe_first_install() {
new \RankMathPro\Analytics\Workflow\Keywords();
}
/**
* Init Analytics workflow
*/
public function init_analytics_workflow() {
new \RankMathPro\Analytics\Workflow\Analytics();
}
/**
* Init Adsense workflow
*/
public function init_adsense_workflow() {
new \RankMathPro\Analytics\Workflow\Adsense();
}
/**
* Create tables only.
*/
public function create_tables_only() {
new \RankMathPro\Analytics\Workflow\Analytics();
new \RankMathPro\Analytics\Workflow\Adsense();
( new \RankMathPro\Analytics\Workflow\Keywords() )->create_keywords_tables();
}
}

View File

@@ -0,0 +1 @@
(()=>{"use strict";var a={n:t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return a.d(e,{a:e}),e},d:(t,e)=>{for(var r in e)a.o(e,r)&&!a.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},o:(a,t)=>Object.prototype.hasOwnProperty.call(a,t)};const t=jQuery;var e;(e=a.n(t)())((function(){e(".rank-math-mark-solved a").on("click",(function(a){a.preventDefault();var t=e(this),r=t.data("is-solved");return e.ajax({url:rankMath.ajaxurl,type:"POST",dataType:"json",data:{action:"rank_math_mark_answer_solved",security:rankMath.security,topic:t.data("topic-id"),reply:t.data("id"),isSolved:r}}).done((function(){if(r)return t.text(t.data("unsolved-text")),void e(".rank-math-mark-solved.rank-math-hidden").removeClass("rank-math-hidden");e(".rank-math-mark-solved").addClass("rank-math-hidden"),t.parent().removeClass("rank-math-hidden"),t.text(t.data("solved-text"))})),!1}))}))})();

View File

@@ -0,0 +1,182 @@
<?php
/**
* BBPress module.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro;
use RankMath\Traits\Hooker;
use RankMath\Traits\Ajax;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* BBPress class.
*
* @codeCoverageIgnore
*/
class BBPress {
use Hooker, Ajax;
/**
* Post meta key for solved answers.
*
* @var string
*/
public $meta_key = '';
/**
* Constructor.
*/
public function __construct() {
$this->meta_key = 'rank_math_bbpress_solved_answer';
$this->action( 'wp', 'hooks' );
$this->ajax( 'mark_answer_solved', 'mark_answer_solved' );
$this->action( 'rank_math/json_ld', 'add_qa_schema', 99 );
}
/**
* Init hooks.
*/
public function hooks() {
if ( ! is_singular( 'topic' ) || ! current_user_can( 'moderate', get_the_ID() ) ) {
return;
}
$this->action( 'bbp_get_reply_content', 'add_solved_answer_button', 9, 2 );
$this->action( 'wp_enqueue_scripts', 'enqueue' );
$this->action( 'wp_footer', 'add_css' );
}
/**
* Enqueue Script required by plugin.
*/
public function enqueue() {
wp_enqueue_script( 'rank-math-bbpress', RANK_MATH_PRO_URL . 'includes/modules/bbPress/assets/js/bbpress.js', [ 'jquery' ], RANK_MATH_PRO_VERSION, true );
}
/**
* Add Mark Reply as Solved button to the Reply content.
*
* @param string $content Original reply content.
* @param int $reply_id Reply ID.
*
* @return string $content New content.
*/
public function add_solved_answer_button( $content, $reply_id ) {
$reply = bbp_get_reply( $reply_id );
if ( empty( $reply ) ) {
return $content;
}
$topic_id = bbp_get_reply_topic_id();
$answered = (int) get_post_meta( $topic_id, $this->meta_key, true );
$solved_text = __( 'Mark Unsolved.', 'rank-math-pro' );
$unsolved_text = __( 'Mark Solved.', 'rank-math-pro' );
$is_solved = $reply_id === $answered;
$class = metadata_exists( 'post', $topic_id, $this->meta_key ) && ! $is_solved ? 'rank-math-hidden' : '';
$text = $is_solved ? $solved_text : $unsolved_text;
$content .= '
<div class="rank-math-mark-solved ' . esc_attr( $class ) . '">
<a
href="#"
data-id="' . esc_attr( $reply_id ) . '"
data-topic-id="' . esc_attr( $topic_id ) . '"
data-solved-text="' . esc_attr( $solved_text ) . '"
data-unsolved-text="' . esc_attr( $unsolved_text ) . '"
data-is-solved="' . $is_solved . '">'
. apply_filters( 'rank_math/bbpress/solved_text', $text, $is_solved )
. '</a>
</div>';
return $content;
}
/**
* AJAX function to mark answer as solved.
*/
public function mark_answer_solved() {
check_ajax_referer( 'rank-math-ajax-nonce', 'security' );
$topic = Param::post( 'topic' );
if ( ! current_user_can( 'moderate', $topic ) ) {
return false;
}
$is_solved = Param::post( 'isSolved' );
if ( $is_solved ) {
return delete_post_meta( $topic, $this->meta_key );
}
$reply = Param::post( 'reply' );
return update_post_meta( $topic, $this->meta_key, $reply );
}
/**
* Add QA Schema Data.
*
* @param array $data Array of json-ld data.
* @return array
*/
public function add_qa_schema( $data ) {
if ( ! is_singular( 'topic' ) ) {
return $data;
}
global $post;
$approved_answer = get_post_meta( $post->ID, $this->meta_key, true );
if ( ! $approved_answer ) {
return $data;
}
$data[] = [
'@type' => 'QAPage',
'mainEntity' => [
'@type' => 'Question',
'name' => get_the_title( $post ),
'text' => get_the_excerpt( $post ),
'dateCreated' => get_post_time( 'Y-m-d\TH:i:sP', false ),
'answerCount' => get_post_meta( $post->ID, '_bbp_reply_count', true ),
'author' => [
'@type' => 'Person',
'name' => get_the_author(),
],
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => get_post_field( 'post_content', $approved_answer ),
'dateCreated' => get_post_time( 'Y-m-d\TH:i:sP', false, $approved_answer ),
'url' => bbp_get_reply_url( $approved_answer ),
'author' => [
'@type' => 'Person',
'name' => bbp_get_reply_author( $approved_answer ),
],
],
],
];
return $data;
}
/**
* Add CSS inline, once.
*/
public function add_css() {
?>
<style type="text/css">
.rank-math-mark-solved {
text-align: right;
}
.rank-math-hidden {
display: none;
}
</style>
<?php
}
}

View File

@@ -0,0 +1 @@
(()=>{"use strict";const r=wp.i18n,t=wp.hooks;(0,t.addFilter)("rank_math_content_ai_help_text","rank-math-pro",(function(){return(0,r.__)("Contact your SEO service provider for more AI credits.","rank-math-pro")})),(0,t.addFilter)("rank_math_content_ai_credits_notice","rank-math-pro",(function(){return(0,r.__)("You have used all of your AI credits and need to purchase more from your SEO service provider.","rank-math-pro")}))})();

View File

@@ -0,0 +1,13 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n'
import { addFilter } from '@wordpress/hooks'
addFilter( 'rank_math_content_ai_help_text', 'rank-math-pro', () => {
return __( 'Contact your SEO service provider for more AI credits.', 'rank-math-pro' )
} )
addFilter( 'rank_math_content_ai_credits_notice', 'rank-math-pro', () => {
return __( 'You have used all of your AI credits and need to purchase more from your SEO service provider.', 'rank-math-pro' )
} )

View File

@@ -0,0 +1,52 @@
<?php
/**
* The Content AI module.
*
* @since 3.0.25
* @package RankMath
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro;
use RankMath\ContentAI\Content_AI as Content_AI_Free;
use RankMath\Helper;
use RankMathPro\Admin\Admin_Helper;
use RankMath\Traits\Hooker;
defined( 'ABSPATH' ) || exit;
/**
* Content_AI class.
*/
class Content_AI {
use Hooker;
/**
* Class constructor.
*/
public function __construct() {
if ( ! Admin_Helper::is_business_plan() || ! Content_AI_Free::can_add_tab() || ! Helper::get_current_editor() ) {
return;
}
$this->action( 'rank_math/admin/editor_scripts', 'editor_scripts', 19 );
}
/**
* Enqueue assets for post editors.
*
* @return void
*/
public function editor_scripts() {
wp_enqueue_script(
'rank-math-pro-content-ai',
RANK_MATH_PRO_URL . 'includes/modules/content-ai/assets/js/content-ai.js',
[ 'rank-math-content-ai' ],
rank_math_pro()->version,
true
);
}
}

View File

@@ -0,0 +1,648 @@
<?php
/**
* Image SEO module.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro\Admin
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro;
use stdClass;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\HTML;
defined( 'ABSPATH' ) || exit;
/**
* Image_Seo class.
*
* @codeCoverageIgnore
*/
class Image_Seo_Pro {
use Hooker;
/**
* Change the case of the alt attribute.
*
* @var string
*/
public $alt_change_case;
/**
* Change the case of the title attribute.
*
* @var string
*/
public $title_change_case;
/**
* Constructor.
*/
public function __construct() {
$this->action( 'rank_math/admin/settings/images', 'add_options' );
if ( Helper::get_settings( 'general.add_avatar_alt' ) ) {
$this->filter( 'get_avatar', 'avatar_add_missing_alt', 99, 6 );
}
if ( Helper::get_settings( 'general.add_img_caption' ) ) {
$this->filter( 'shortcode_atts_caption', 'add_caption', 99, 1 );
$this->filter( 'the_content', 'content_add_caption', 99 );
}
if ( Helper::get_settings( 'general.add_img_description' ) ) {
$this->filter( 'the_content', 'add_description', 99 );
}
$replacements = Helper::get_settings( 'general.image_replacements' );
if ( ! empty( $replacements ) ) {
$this->filter( 'the_content', 'attribute_replacements', 20 );
$this->filter( 'post_thumbnail_html', 'attribute_replacements', 20 );
$this->filter( 'woocommerce_single_product_image_thumbnail_html', 'attribute_replacements', 20 );
$this->filter( 'shortcode_atts_caption', 'caption_replacements', 20, 3 );
}
$this->action( 'wp_head', 'maybe_change_attributes_case', 110 );
if ( Helper::get_settings( 'general.img_caption_change_case' ) ) {
$this->filter( 'shortcode_atts_caption', 'change_caption_case', 110, 1 );
$this->filter( 'the_content', 'change_content_caption_case', 110 );
}
if ( Helper::get_settings( 'general.img_description_change_case' ) ) {
$this->filter( 'the_content', 'change_description_case', 110 );
}
$this->action( 'rank_math/vars/register_extra_replacements', 'register_replacements' );
$this->filter( 'cmb2_field_arguments', 'maybe_exclude_image_vars', 10 );
}
/**
* Registers variable replacements for the Image SEO Pro module.
*/
public function register_replacements() {
rank_math_register_var_replacement(
'imagealt',
[
'name' => esc_html__( 'Image Alt', 'rank-math-pro' ),
'description' => esc_html__( 'Alt text set for the current image.', 'rank-math-pro' ),
'variable' => 'imagealt',
'example' => '',
'nocache' => true,
],
[ $this, 'get_imagealt' ]
);
rank_math_register_var_replacement(
'imagetitle',
[
'name' => esc_html__( 'Image Title', 'rank-math-pro' ),
'description' => esc_html__( 'Title text set for the current image.', 'rank-math-pro' ),
'variable' => 'imagetitle',
'example' => '',
'nocache' => true,
],
[ $this, 'get_imagetitle' ]
);
}
/**
* Filter CMB field arguments to exclude `imagealt` & `imagetitle` when they are not needed.
*
* @param array $args Arguments array.
* @return array
*/
public function maybe_exclude_image_vars( $args ) {
if ( empty( $args['classes'] ) ) {
return $args;
}
$classes = is_array( $args['classes'] ) ? $args['classes'] : explode( ' ', $args['classes'] );
if ( ! in_array( 'rank-math-supports-variables', $classes, true ) ) {
return $args;
}
if ( ! is_string( $args['id'] ) || strpos( $args['id'], 'img_' ) !== false ) {
return $args;
}
if ( ! isset( $args['attributes']['data-exclude-variables'] ) ) {
$args['attributes']['data-exclude-variables'] = '';
}
$args['attributes']['data-exclude-variables'] .= ',imagealt,imagetitle';
$args['attributes']['data-exclude-variables'] = trim( $args['attributes']['data-exclude-variables'], ',' );
return $args;
}
/**
* Get the alt attribute of the attachment to use as a replacement.
* See rank_math_register_var_replacement().
*
* @codeCoverageIgnore
*
* @param string $var_args Variable name, for example %custom%. The '%' signs are optional.
* @param object $replacement_args Additional title, description and example values for the variable.
*
* @return bool Replacement was registered successfully or not.
*/
public function get_imagealt( $var_args, $replacement_args = null ) {
if ( empty( $replacement_args->alttext ) ) {
return null;
}
return $replacement_args->alttext;
}
/**
* Get the title attribute of the attachment to use as a replacement.
* See rank_math_register_var_replacement().
*
* @codeCoverageIgnore
*
* @param string $var_args Variable name, for example %custom%. The '%' signs are optional.
* @param object $replacement_args Additional title, description and example values for the variable.
*
* @return bool Replacement was registered successfully or not.
*/
public function get_imagetitle( $var_args, $replacement_args = null ) {
if ( empty( $replacement_args->titletext ) ) {
return null;
}
return $replacement_args->titletext;
}
/**
* Change case of alt & title attributes if needed.
*
* @return void
*/
public function maybe_change_attributes_case() {
// Change image title and alt casing.
$this->alt_change_case = Helper::get_settings( 'general.img_alt_change_case' );
$this->title_change_case = Helper::get_settings( 'general.img_title_change_case' );
if ( $this->alt_change_case || $this->title_change_case ) {
$this->filter( 'the_content', 'change_attribute_case', 30 );
$this->filter( 'post_thumbnail_html', 'change_attribute_case', 30 );
$this->filter( 'woocommerce_single_product_image_thumbnail_html', 'change_attribute_case', 30 );
}
}
/**
* Change case of alt & title attributes in a post content string.
*
* @param string $content Post content.
* @return string New post content.
*/
public function change_attribute_case( $content ) {
if ( empty( $content ) ) {
return $content;
}
$stripped_content = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $content );
preg_match_all( '/<img ([^>]+)\/?>/iU', $stripped_content, $matches, PREG_SET_ORDER );
if ( empty( $matches ) ) {
return $content;
}
foreach ( $matches as $image ) {
$is_dirty = false;
$attrs = HTML::extract_attributes( $image[0] );
if ( ! isset( $attrs['src'] ) ) {
continue;
}
$this->set_image_attribute( $attrs, 'alt', $this->alt_change_case, $is_dirty );
$this->set_image_attribute( $attrs, 'title', $this->title_change_case, $is_dirty );
if ( $is_dirty ) {
$new = '<img' . HTML::attributes_to_string( $attrs ) . '>';
$content = str_replace( $image[0], $new, $content );
}
}
return $content;
}
/**
* Change image attribute case after checking condition.
*
* @param array $attrs Array which hold rel attribute.
* @param string $attribute Attribute to set.
* @param boolean $condition Condition to check.
* @param boolean $is_dirty Is dirty variable.
*/
private function set_image_attribute( &$attrs, $attribute, $condition, &$is_dirty ) {
if ( $condition && ! empty( $attrs[ $attribute ] ) ) {
$is_dirty = true;
$attrs[ $attribute ] = $this->change_case( $attrs[ $attribute ], $condition );
}
}
/**
* Turn first character of every sentence to uppercase.
*
* @param string $string Original sring.
*
* @return string New string.
*/
private function sentence_case( $string ) {
$sentences = preg_split( '/([.?!]+)/', $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
$new_string = '';
foreach ( $sentences as $key => $sentence ) {
$new_string .= ( $key & 1 ) === 0 ?
$this->mb_ucfirst( trim( $sentence ) ) :
$sentence . ' ';
}
return trim( $new_string );
}
/**
* Multibyte ucfirst().
*
* @param string $string String.
*
* @return string New string.
*/
private function mb_ucfirst( $string ) {
return mb_strtoupper( mb_substr( $string, 0, 1 ) ) . mb_strtolower( mb_substr( $string, 1 ) );
}
/**
* Change case of string.
*
* @param string $string String to change.
* @param string $case Case type to change to.
*
* @return string New string.
*/
private function change_case( $string, $case ) {
$cases_hash = [
'titlecase' => MB_CASE_TITLE,
'sentencecase' => MB_CASE_LOWER,
'lowercase' => MB_CASE_LOWER,
'uppercase' => MB_CASE_UPPER,
];
if ( ! isset( $cases_hash[ $case ] ) ) {
return $string;
}
if ( 'sentencecase' === $case ) {
return $this->sentence_case( $string );
}
return mb_convert_case( $string, $cases_hash[ $case ] );
}
/**
* Add alt attribute for avatars if they don't have one.
*
* @param string $avatar Avatar HTML.
* @param mixed $id_or_email User ID or email.
* @param int $size Width in px.
* @param string $default Fallback.
* @param string $alt Alt attribute value.
* @param array $args Avatar args.
*
* @return string New avatar HTML.
*/
public function avatar_add_missing_alt( $avatar, $id_or_email, $size, $default, $alt, $args ) { // phpcs:ignore
if ( is_admin() ) {
return $avatar;
}
if ( empty( $avatar ) ) {
return $avatar;
}
if ( ! empty( $alt ) ) {
return $avatar;
}
if ( ! preg_match( '/<img ([^>]+)\/?>/iU', $avatar ) ) {
return $avatar;
}
$attrs = HTML::extract_attributes( $avatar );
if ( ! empty( $attrs['alt'] ) ) {
return $avatar;
}
$new_alt = '';
if ( is_a( $id_or_email, 'WP_Comment' ) ) {
$user = $id_or_email->comment_author;
} elseif ( is_int( $id_or_email ) ) {
// This is a user ID.
$user = get_user_by( 'id', $id_or_email );
} elseif ( is_string( $id_or_email ) ) {
$user = get_user_by( 'email', $id_or_email );
}
if ( is_a( $user, 'WP_User' ) ) {
$new_alt = $user->get( 'display_name' );
} elseif ( is_string( $user ) ) {
$new_alt = $user;
} else {
return $avatar;
}
// Translators: placeholder is the username or email.
$attrs['alt'] = sprintf( __( 'Avatar of %s', 'rank-math-pro' ), $new_alt );
$attrs['alt'] = apply_filters( 'rank_math_pro/images/avatar_alt', $attrs['alt'], $id_or_email );
$new = '<img' . HTML::attributes_to_string( $attrs ) . '>';
// Change image title and alt casing.
$this->alt_change_case = Helper::get_settings( 'general.img_alt_change_case' );
$this->title_change_case = Helper::get_settings( 'general.img_title_change_case' );
if ( $this->alt_change_case || $this->title_change_case ) {
$new = $this->change_attribute_case( $new );
}
return $new;
}
/**
* Add missing caption text if needed.
*
* @param string $out Shortcode output.
*
* @return string New shortcode output.
*/
public function add_caption( $out ) {
if ( ! empty( $out['caption'] ) ) {
return $out;
}
$out['caption'] = trim( Helper::replace_vars( Helper::get_settings( 'general.img_caption_format' ), $this->get_post() ) );
return $out;
}
/**
* Change case for captions.
*
* @param string $out Shortcode output.
*
* @return string New shortcode output.
*/
public function change_caption_case( $out ) {
if ( empty( $out['caption'] ) ) {
return $out;
}
$out['caption'] = $this->change_case( $out['caption'], Helper::get_settings( 'general.img_caption_change_case' ) );
return $out;
}
/**
* Change case for captions in Image Blocks.
*
* @param string $content Content output.
*
* @return string New output.
*/
public function change_content_caption_case( $content ) {
$content = preg_replace_callback( '/(<figure[^<]+class="([^"]+ )?(wp-block-image|wp-caption).+<figcaption[^>]*>)([^<]+)(<\/figcaption>)/sU', [ $this, 'caption_case_cb' ], $content );
return $content;
}
/**
* Change case for captions in Image Blocks.
*
* @param string $content Content output.
*
* @return string New output.
*/
public function content_add_caption( $content ) {
$content = preg_replace_callback( '/<figure class="([^"]+ )?wp-block-image .+<\/figure>/sU', [ $this, 'add_caption_cb' ], $content );
return $content;
}
/**
* Change case for captions in Image Blocks.
*
* @param string $matches Content output.
*
* @return string New output.
*/
public function caption_case_cb( $matches ) {
return $matches[1] . $this->change_case( $matches[4], Helper::get_settings( 'general.img_caption_change_case' ) ) . $matches[5];
}
/**
* Add caption in Image Blocks.
*
* @param string $matches Content output.
*
* @return string New output.
*/
public function add_caption_cb( $matches ) {
if ( stripos( $matches[0], '<figcaption' ) !== false ) {
return $matches[0];
}
$caption = trim( Helper::replace_vars( Helper::get_settings( 'general.img_caption_format' ), $this->get_post( $matches[0] ) ) );
return str_replace( '</figure>', '<figcaption>' . $caption . '</figcaption></figure>', $matches[0] );
}
/**
* Add missing attachment description if needed.
*
* @param string $content Original content.
*
* @return string New content.
*/
public function add_description( $content ) {
if ( get_post_type() !== 'attachment' ) {
return $content;
}
$content_stripped = wp_strip_all_tags( $content );
if ( ! empty( $content_stripped ) ) {
return $content;
}
return $content . trim( Helper::replace_vars( Helper::get_settings( 'general.img_description_format' ), $this->get_post() ) );
}
/**
* Change case for image description.
*
* @param string $content Original content.
*
* @return string New content.
*/
public function change_description_case( $content ) {
if ( get_post_type() !== 'attachment' ) {
return $content;
}
$parts = preg_split( '/(<[^>]+>)/', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
$new = '';
foreach ( $parts as $i => $part ) {
if ( '<' === substr( trim( $part ), 0, 1 ) ) {
$new .= $part;
continue;
}
$new .= $this->change_case( $part, Helper::get_settings( 'general.img_description_change_case' ) );
}
return $new;
}
/**
* Search & replace in alt & title attributes.
*
* @param string $content Original post content.
*
* @return string New post content.
*/
public function attribute_replacements( $content ) {
$replacements = Helper::get_settings( 'general.image_replacements' );
foreach ( $replacements as $replacement_id => $replacement ) {
if ( ! count( array_intersect( $replacement['replace_in'], [ 'alt', 'title' ] ) ) ) {
continue;
}
foreach ( $replacement['replace_in'] as $attr ) {
if ( 'caption' === $attr ) {
continue;
}
$content = $this->attribute_replacement( $content, $replacement['find'], $replacement['replace'], $attr );
}
}
return $content;
}
/**
* Do the replacement in an attribute.
*
* @param string $content Original content.
* @param string $find Search string.
* @param string $replace Replacement string.
* @param string $attribute Attribute to look for.
*
* @return string New content.
*/
public function attribute_replacement( $content, $find, $replace, $attribute ) {
$stripped_content = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $content );
preg_match_all( '/<img ([^>]+)\/?>/iU', $stripped_content, $matches, PREG_SET_ORDER );
if ( empty( $matches ) ) {
return $content;
}
foreach ( $matches as $image ) {
$attrs = HTML::extract_attributes( $image[0] );
if ( ! isset( $attrs['src'] ) ) {
continue;
}
if ( empty( $attrs[ $attribute ] ) ) {
continue;
}
$attrs[ $attribute ] = str_replace( $find, $replace, $attrs[ $attribute ] );
$new = '<img' . HTML::attributes_to_string( $attrs ) . '>';
$content = str_replace( $image[0], $new, $content );
}
return $content;
}
/**
* Search & replace in image captions.
*
* @param string $out Shortcode output.
* @param array $pairs Possible attributes.
* @param array $atts Shortcode attributes.
*
* @return string New shortcode output.
*/
public function caption_replacements( $out, $pairs, $atts ) {
$replacements = Helper::get_settings( 'general.image_replacements' );
foreach ( $replacements as $replacement_id => $replacement ) {
if ( ! in_array( 'caption', $replacement['replace_in'], true ) ) {
continue;
}
$caption = $atts['caption'];
if ( empty( $caption ) ) {
continue;
}
$new_caption = str_replace( $replacement['find'], $replacement['replace'], $caption );
$out = str_replace( $caption, $new_caption, $out );
}
return $out;
}
/**
* Get post object.
*
* @return object
*/
private function get_post( $image = [] ) {
$post = \get_post();
if ( empty( $post ) ) {
$post = new stdClass();
}
if ( empty( $image ) ) {
return $post;
}
$attrs = HTML::extract_attributes( $image );
if ( empty( $attrs['src'] ) ) {
return $post;
}
$post->filename = $attrs['src'];
// Lazy load support.
if ( ! empty( $attrs['data-src'] ) ) {
$post->filename = $attrs['data-src'];
} elseif ( ! empty( $attrs['data-layzr'] ) ) {
$post->filename = $attrs['data-layzr'];
} elseif ( ! empty( $attrs['nitro-lazy-srcset'] ) ) {
$post->filename = $attrs['nitro-lazy-srcset'];
}
return $post;
}
/**
* Add options to Image SEO module.
*
* @param object $cmb CMB object.
*/
public function add_options( $cmb ) {
$field_ids = wp_list_pluck( $cmb->prop( 'fields' ), 'id' );
$fields_position = array_search( 'img_title_format', array_keys( $field_ids ), true ) + 1;
include_once dirname( __FILE__ ) . '/options.php';
}
}

View File

@@ -0,0 +1,191 @@
<?php
/**
* The images settings.
*
* @package RankMath
* @subpackage RankMath\Settings
*/
defined( 'ABSPATH' ) || exit;
$cmb->add_field(
[
'id' => 'add_img_caption',
'type' => 'toggle',
'name' => esc_html__( 'Add missing image caption', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Add a caption for all images without a caption automatically. The caption is dynamically applied when the content is displayed, the stored content is not changed.', 'rank-math-pro' ) ),
'default' => 'off',
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'img_caption_format',
'type' => 'text',
'name' => esc_html__( 'Caption format', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Format used for the new captions.', 'rank-math-pro' ) ),
'classes' => 'large-text rank-math-supports-variables dropdown-up',
'default' => '%title% %count(title)%',
'dep' => [ [ 'add_img_caption', 'on' ] ],
'sanitization_cb' => false,
'attributes' => [ 'data-exclude-variables' => 'seo_title,seo_description' ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'add_img_description',
'type' => 'toggle',
'name' => esc_html__( 'Add missing image description', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Add a description for all images without a description automatically. The description is dynamically applied when the content is displayed, the stored content is not changed.', 'rank-math-pro' ) ),
'default' => 'off',
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'img_description_format',
'type' => 'text',
'name' => esc_html__( 'Description format', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Format used for the new descriptions.', 'rank-math-pro' ) ),
'classes' => 'large-text rank-math-supports-variables dropdown-up',
'default' => '%title% %count(title)%',
'dep' => [ [ 'add_img_description', 'on' ] ],
'sanitization_cb' => false,
'attributes' => [ 'data-exclude-variables' => 'seo_title,seo_description' ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'img_title_change_case',
'type' => 'select',
'name' => esc_html__( 'Change title casing', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Capitalization settings for the <code>title</code> attribute values. This will be applied for <strong>all</strong> <code>title</code> attributes.', 'rank-math-pro' ) ),
'default' => 'off',
'options' => [
'off' => esc_html__( 'No change', 'rank-math-pro' ),
'titlecase' => esc_html__( 'Title Casing', 'rank-math-pro' ),
'sentencecase' => esc_html__( 'Sentence casing', 'rank-math-pro' ),
'lowercase' => esc_html__( 'all lowercase', 'rank-math-pro' ),
'uppercase' => esc_html__( 'ALL UPPERCASE', 'rank-math-pro' ),
],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'img_alt_change_case',
'type' => 'select',
'name' => esc_html__( 'Change alt attribute casing', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Capitalization settings for the <code>alt</code> attribute values. This will be applied for <strong>all</strong> <code>alt</code> attributes.', 'rank-math-pro' ) ),
'default' => 'off',
'options' => [
'off' => esc_html__( 'No change', 'rank-math-pro' ),
'titlecase' => esc_html__( 'Title Casing', 'rank-math-pro' ),
'sentencecase' => esc_html__( 'Sentence casing', 'rank-math-pro' ),
'lowercase' => esc_html__( 'all lowercase', 'rank-math-pro' ),
'uppercase' => esc_html__( 'ALL UPPERCASE', 'rank-math-pro' ),
],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'img_description_change_case',
'type' => 'select',
'name' => esc_html__( 'Change description casing', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Capitalization settings for the image descriptions. This will be applied for <strong>all</strong> image descriptions.', 'rank-math-pro' ) ),
'default' => 'off',
'options' => [
'off' => esc_html__( 'No change', 'rank-math-pro' ),
'titlecase' => esc_html__( 'Title Casing', 'rank-math-pro' ),
'sentencecase' => esc_html__( 'Sentence casing', 'rank-math-pro' ),
'lowercase' => esc_html__( 'all lowercase', 'rank-math-pro' ),
'uppercase' => esc_html__( 'ALL UPPERCASE', 'rank-math-pro' ),
],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'img_caption_change_case',
'type' => 'select',
'name' => esc_html__( 'Change caption casing', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Capitalization settings for the image captions. This will be applied for <strong>all</strong> image captions.', 'rank-math-pro' ) ),
'default' => 'off',
'options' => [
'off' => esc_html__( 'No change', 'rank-math-pro' ),
'titlecase' => esc_html__( 'Title Casing', 'rank-math-pro' ),
'sentencecase' => esc_html__( 'Sentence casing', 'rank-math-pro' ),
'lowercase' => esc_html__( 'all lowercase', 'rank-math-pro' ),
'uppercase' => esc_html__( 'ALL UPPERCASE', 'rank-math-pro' ),
],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'add_avatar_alt',
'type' => 'toggle',
'name' => esc_html__( 'Add ALT attributes for avatars', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Add <code>alt</code> attributes for commenter profile pictures (avatars) automatically. The alt attribute value will be the username.', 'rank-math-pro' ) ),
'default' => 'off',
],
++$fields_position
);
$replacement_fields = $cmb->add_field( //phpcs:ignore
[
'id' => 'image_replacements',
'type' => 'group',
'name' => esc_html__( 'Replacements', 'rank-math-pro' ),
'desc' => esc_html__( 'Replace characters or words in the alt tags, title tags, or in the captions.', 'rank-math-pro' ),
'options' => [
'add_button' => esc_html__( 'Add another', 'rank-math-pro' ),
'remove_button' => esc_html__( 'Remove', 'rank-math-pro' ),
],
'classes' => 'cmb-group-text-only',
],
++$fields_position
);
$cmb->add_group_field(
$replacement_fields,
[
'id' => 'find',
'type' => 'text',
'attributes' => [ 'placeholder' => esc_attr__( 'Find', 'rank-math-pro' ) ],
]
);
$cmb->add_group_field(
$replacement_fields,
[
'id' => 'replace',
'type' => 'text',
'attributes' => [ 'placeholder' => esc_attr__( 'Replace', 'rank-math-pro' ) ],
]
);
$cmb->add_group_field(
$replacement_fields,
[
'id' => 'replace_in',
'type' => 'multicheck',
'options' => [
'alt' => __( 'Alt', 'rank-math-pro' ),
'title' => __( 'Title', 'rank-math-pro' ),
'caption' => __( 'Caption', 'rank-math-pro' ),
],
]
);

View File

@@ -0,0 +1 @@
.rank-math-business-wrapper .rank-math-directions-wrapper .rank-math-directions-result{display:none}.rank-math-business-wrapper .rank-math-directions-wrapper.show .rank-math-directions-result{display:block}

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(()=>{"use strict";var t={n:e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return t.d(n,{a:n}),n},d:(e,n)=>{for(var o in n)t.o(n,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:n[o]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)};const e=jQuery;var n=t.n(e);const o=lodash,r=wp.i18n;function i(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=rankMath.links[t]||"";if(!n)return"#";if(!e)return n;var o={utm_source:"Plugin",utm_medium:encodeURIComponent(e),utm_campaign:"WP"};return n+"?"+Object.keys(o).map((function(t){return"".concat(t,"=").concat(o[t])})).join("&")}function a(t){return a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},a(t)}function s(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,(r=o.key,i=void 0,i=function(t,e){if("object"!==a(t)||null===t)return t;var n=t[Symbol.toPrimitive];if(void 0!==n){var o=n.call(t,e||"default");if("object"!==a(o))return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(r,"string"),"symbol"===a(i)?i:String(i)),o)}var r,i}var u=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.pointers=this.getPointers(),this.showPointer=this.showPointer.bind(this),this.init()}var e,a,u;return e=t,(a=[{key:"init",value:function(){var t=this;(0,o.forEach)(this.pointers,(function(e,n){return t.showPointer(n),!1})),n()(".rank-math-toolbar-score").parent().hasClass("is-pressed")||n()(".rank-math-toolbar-score").parent().trigger("click")}},{key:"showPointer",value:function(t){var e=this,o=this.pointers[t],i=n().extend(o.options,{pointerClass:"wp-pointer rm-pointer",close:function(){o.next&&e.showPointer(o.next)},buttons:function(t,e){var o="wp-pointer-3"===e.pointer[0].id?(0,r.__)("Finish","rank-math-pro"):(0,r.__)("Next","rank-math-pro"),i=n()('<a class="close" href="#">'+(0,r.__)("Dismiss","rank-math-pro")+"</a>"),a=n()('<a class="button button-primary" href="#">'+o+"</a>"),s=n()('<div class="rm-pointer-buttons" />');return i.on("click.pointer",(function(t){t.preventDefault(),e.element.pointer("destroy")})),a.on("click.pointer",(function(t){t.preventDefault(),e.element.pointer("close")})),s.append(i),s.append(a),s}}),a=n()(o.target).pointer(i);a.pointer("open"),o.next_trigger&&n()(o.next_trigger.target).on(o.next_trigger.event,(function(){setTimeout((function(){a.pointer("close")}),400)}))}},{key:"getPointers",value:function(){return{title:{target:".editor-post-title__input",next:"schema",options:{content:"<h3>"+(0,r.__)("Local Business Name","rank-math-pro")+"</h3><p>"+(0,r.__)("Give your business's new location a name here. This field is required and will be visible to users.","rank-math-pro")+"</p>"}},schema:{target:".components-tab-panel__tabs-item.rank-math-schema-tab",next:"content",options:{content:"<h3>"+(0,r.__)("Local Business Schema","rank-math-pro")+"</h3><p>"+(0,r.__)('Add your local business\'s details here with "Local Business" Schema Markup in order to be eligible for local SERP features.',"rank-math-pro")+"</p>",position:{edge:"right",align:"left"}}},content:{target:".is-root-container",next:"submitdiv",options:{content:"<h3>"+(0,r.__)("Show Business Information","rank-math-pro")+"</h3><p>"+(0,r.sprintf)((0,r.__)("Make sure to add the Local Business Block or %s to display your business data.","rank-math-pro"),'<a href="'+i("location-data-shortcode","Show Business Information")+'" target="_blank">[rank_math_local] shortcode</a>')+"</p>",position:{edge:"bottom",align:"middle"}}},submitdiv:{target:".editor-post-publish-button__button",next:"",options:{content:"<h3>"+(0,r.__)("Publish your location!","rank-math-pro")+"</h3><p>"+(0,r.__)("When you're done editing, don't forget to hit \"publish\" to create this location.","rank-math-pro")+"</p>"}}}}}])&&s(e.prototype,a),u&&s(e,u),Object.defineProperty(e,"prototype",{writable:!1}),t}();n()(window).on("load",(function(){new u}))})();

View File

@@ -0,0 +1,87 @@
<?php
/**
* The Local_Seo Module
*
* @since 1.0.0
* @package RankMath
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\KB;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Admin\Admin_Helper;
use RankMath\Sitemap\Router;
defined( 'ABSPATH' ) || exit;
/**
* Admin class.
*/
class Admin {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
$this->filter( 'rank_math/settings/title', 'add_settings' );
$this->filter( 'rank_math/settings/sitemap', 'add_sitemap_settings', 11 );
$this->filter( 'rank_math/settings/snippet/types', 'add_local_business_schema_type', 10, 2 );
}
/**
* Add module settings into general optional panel.
*
* @param array $tabs Array of option panel tabs.
*
* @return array
*/
public function add_settings( $tabs ) {
$tabs['local']['file'] = dirname( __FILE__ ) . '/views/titles-options.php';
return $tabs;
}
/**
* Add module settings into general optional panel.
*
* @param array $tabs Array of option panel tabs.
*
* @return array
*/
public function add_sitemap_settings( $tabs ) {
$sitemap_url = Router::get_base_url( 'locations.kml' );
$tabs['kml-file'] = [
'icon' => 'rm-icon rm-icon-local-seo',
'title' => esc_html__( 'Local Sitemap', 'rank-math-pro' ),
'desc' => wp_kses_post( sprintf( __( 'KML is a file format used to display geographic data in an Earth browser such as Google Earth. More information: <a href="%s" target="_blank">Locations KML</a>', 'rank-math-pro' ), KB::get( 'kml-sitemap', 'Options Panel Sitemap Local Tab' ) ) ),
'file' => dirname( __FILE__ ) . '/views/sitemap-settings.php',
/* translators: KML File Url */
'after_row' => '<div class="notice notice-alt notice-info info inline rank-math-notice"><p>' . sprintf( esc_html__( 'Your Locations KML file can be found here: %s', 'rank-math-pro' ), '<a href="' . $sitemap_url . '" target="_blank">' . $sitemap_url . '</a>' ) . '</p></div>',
];
return $tabs;
}
/**
* Add Pro schema types in Schema settings choices array.
*
* @param array $types Schema types.
* @param string $post_type Post type.
*/
public function add_local_business_schema_type( $types, $post_type ) {
if ( 'rank_math_locations' === $post_type ) {
$types = [
'off' => esc_html__( 'None', 'rank-math-pro' ),
'LocalBusiness' => esc_html__( 'Local Business', 'rank-math-pro' ),
];
}
return $types;
}
}

View File

@@ -0,0 +1,199 @@
<?php
/**
* The Local_Seo Module
*
* @since 1.0.0
* @package RankMath
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\Helper;
use RankMath\Post;
use RankMath\Traits\Hooker;
use RankMath\Schema\DB;
defined( 'ABSPATH' ) || exit;
/**
* Frontend class.
*/
class Frontend {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
$this->action( 'rank_math/json_ld', 'add_location_schema', 100, 2 );
$this->action( 'rank_math/head', 'add_location_tags', 90 );
new Search();
}
/**
* Add Locations Metatags to head.
*/
public function add_location_tags() {
if ( ! is_singular( 'rank_math_locations' ) ) {
return;
}
$schema = DB::get_schemas( Post::get_page_id() );
if ( empty( $schema ) ) {
return;
}
$schema = current( $schema );
$meta_tags = [
'placename' => ! empty( $schema['address']['addressLocality'] ) ? $schema['address']['addressLocality'] : '',
'position' => ! empty( $schema['geo']['latitude'] ) ? $schema['geo']['latitude'] . ';' . $schema['geo']['longitude'] : '',
'region' => ! empty( $schema['address']['addressCountry'] ) ? $schema['address']['addressCountry'] : '',
];
foreach ( $meta_tags as $name => $value ) {
if ( ! $value ) {
continue;
}
printf( '<meta name="geo.%1$s" content="%2$s" />' . "\n", esc_attr( $name ), esc_attr( $value ) );
}
}
/**
* Add Locations Schema.
*
* @param array $data Array of json-ld data.
* @param JsonLD $jsonld Instance of jsonld.
*
* @return array
*/
public function add_location_schema( $data, $jsonld ) {
if ( ! is_singular( 'rank_math_locations' ) ) {
return $data;
}
global $post;
$schemas = DB::get_schemas( $post->ID );
$schema_key = key( $schemas );
if ( ! isset( $data[ $schema_key ] ) ) {
return $data;
}
$entity = $data[ $schema_key ];
$this->add_place_entity( $data, $entity, $jsonld, ! empty( $schemas[ $schema_key ]['metadata']['open247'] ) );
$this->validate_publisher_data( $data, $jsonld );
$data[ $schema_key ] = $this->validate_locations_data( $entity, $data );
return $data;
}
/**
* Add Schema Place entity on Rank Math locations posts.
*
* @param array $data Array of json-ld data.
* @param array $entity Location data.
* @param JsonLD $jsonld Instance of jsonld.
* @param bool $is_open247 Whether business is open 24*7.
*/
private function add_place_entity( &$data, &$entity, $jsonld, $is_open247 ) {
$properties = [];
foreach ( [ 'openingHoursSpecification', 'geo' ] as $property ) {
if ( isset( $entity[ $property ] ) ) {
$properties[ $property ] = $entity[ $property ];
}
}
if ( isset( $entity['address'] ) ) {
$properties['address'] = $entity['address'];
}
if ( $is_open247 ) {
$properties['openingHoursSpecification'] = [
'@type' => 'OpeningHoursSpecification',
'dayOfWeek' => [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
],
'opens' => '00:00',
'closes' => '23:59',
];
}
if ( empty( $properties ) ) {
return;
}
$data['place'] = array_merge(
[
'@type' => 'Place',
'@id' => $jsonld->parts['canonical'] . '#place',
],
$properties
);
}
/**
* Change Publisher Data when multiple locations option is enabled.
*
* @param array $data Array of json-ld data.
* @param JsonLD $jsonld Instance of jsonld.
*
* @return array
*/
private function validate_publisher_data( &$data, $jsonld ) {
if ( empty( $data['publisher'] ) ) {
return;
}
$data['publisher'] = [
'@type' => 'Organization',
'@id' => $data['publisher']['@id'],
'name' => $jsonld->get_website_name(),
'logo' => [
'@type' => 'ImageObject',
'url' => Helper::get_settings( 'titles.knowledgegraph_logo' ),
],
];
}
/**
* Validate Locations data before adding it in ld+json.
*
* @param array $entity Location data.
* @param array $data Array of json-ld data.
*
* @return array
*/
private function validate_locations_data( $entity, $data ) {
// Remove invalid properties.
foreach ( [ 'isPartOf', 'publisher', 'inLanguage' ] as $property ) {
if ( isset( $entity[ $property ] ) ) {
unset( $entity[ $property ] );
}
}
// Add Parent Organization.
if (
! empty( $data['publisher'] ) &&
Helper::get_settings( 'titles.same_organization_locations', false )
) {
$entity['parentOrganization'] = [ '@id' => $data['publisher']['@id'] ];
}
// Add reference to the place entity.
if ( isset( $data['place'] ) ) {
$entity['location'] = [ '@id' => $data['place']['@id'] ];
}
return $entity;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* The KML File
*
* @since 2.1.2
* @package RankMath
* @subpackage RankMathPro\Local_Seo
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Sitemap\Cache_Watcher;
use RankMath\Schema\DB;
defined( 'ABSPATH' ) || exit;
/**
* KML_File class.
*/
class KML_File {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
$this->filter( 'rank_math/sitemap/locations/data', 'add_location_data' );
$this->action( 'save_post_rank_math_locations', 'save_post' );
}
/**
* Check for relevant post type before invalidation.
*
* @param int $post_id Post ID to possibly invalidate for.
*/
public function save_post( $post_id ) {
if (
wp_is_post_revision( $post_id ) ||
false === Helper::is_post_indexable( $post_id )
) {
return false;
}
Cache_Watcher::invalidate( 'locations' );
}
/**
* Generate the KML file contents.
*
* @return string $kml KML file content.
*/
public function add_location_data() {
$rm_locations = get_posts(
[
'post_type' => 'rank_math_locations',
'numberposts' => -1,
]
);
if ( empty( $rm_locations ) ) {
return [];
}
$locations = [];
foreach ( $rm_locations as $rm_location ) {
$locations_data = current( DB::get_schemas( $rm_location->ID ) );
if ( empty( $locations_data ) ) {
continue;
}
rank_math()->variables->setup();
$name = ! empty( $locations_data['name'] ) ? $locations_data['name'] : '%seo_title%';
$description = ! empty( $locations_data['description'] ) ? $locations_data['description'] : '%seo_description%';
$address = '';
if ( ! empty( $locations_data['address'] ) && isset( $locations_data['address']['@type'] ) ) {
unset( $locations_data['address']['@type'] );
$address = $locations_data['address'];
}
$locations[] = [
'name' => Helper::replace_vars( $name, $rm_location ),
'description' => Helper::replace_vars( $description, $rm_location ),
'email' => ! empty( $locations_data['email'] ) ? Helper::replace_vars( $locations_data['email'], $rm_location ) : '',
'phone' => ! empty( $locations_data['telephone'] ) ? Helper::replace_vars( $locations_data['telephone'], $rm_location ) : '',
'url' => get_the_permalink( $rm_location ),
'address' => ! empty( $locations_data['address'] ) ? $locations_data['address'] : '',
'coords' => ! empty( $locations_data['geo'] ) ? $locations_data['geo'] : '',
];
}
return $locations;
}
}

View File

@@ -0,0 +1,315 @@
<?php
/**
* The Local_Seo Module
*
* @since 1.0.0
* @package RankMath
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Sitemap\Cache_Watcher;
defined( 'ABSPATH' ) || exit;
/**
* Local_Seo class.
*/
class Local_Seo {
use Hooker;
/**
* Post Singular Name.
*
* @var string
*/
private $post_singular_name = 'Location';
/**
* Post Type.
*
* @var string
*/
private $post_type = 'rank_math_locations';
/**
* The Constructor.
*/
public function __construct() {
$this->action( 'init', 'init' );
$this->action( 'rank_math/schema/update', 'update_post_schema_info', 10, 2 );
$this->action( 'save_post', 'invalidate_cache' );
$this->filter( 'classic_editor_enabled_editors_for_post_type', 'force_block_editor', 20, 2 );
$this->filter( 'rank_math/sitemap/locations', 'add_kml_file' );
$this->filter( "manage_{$this->post_type}_posts_columns", 'posts_columns' );
$this->filter( "manage_{$this->post_type}_posts_custom_column", 'posts_custom_column', 10, 2 );
$this->filter( "bulk_actions-edit-{$this->post_type}", 'post_bulk_actions', 20 );
$this->includes();
}
/**
* Update post info for analytics.
*
* @param int $object_id Object ID.
* @param array $schemas Schema data.
* @param string $object_type Object type.
*/
public function update_post_schema_info( $object_id, $schemas, $object_type = 'post' ) {
if ( 'post' !== $object_type || 'rank_math_locations' !== get_post_type( $object_id ) ) {
return;
}
$schema = current( $schemas );
if ( ! isset( $schema['geo'], $schema['geo']['latitude'], $schema['geo']['longitude'] ) ) {
return;
}
update_post_meta( $object_id, 'rank_math_local_business_latitide', $schema['geo']['latitude'] );
update_post_meta( $object_id, 'rank_math_local_business_longitude', $schema['geo']['longitude'] );
}
/**
* Intialize.
*/
public function init() {
if ( ! Helper::get_settings( 'titles.use_multiple_locations', false ) ) {
return;
}
$this->post_singular_name = Helper::get_settings( 'titles.locations_post_type_label', 'Location' );
$this->register_location_post_type();
$this->register_location_taxonomy();
}
/**
* Filters the editors that are enabled for the post type.
*
* @param array $editors Associative array of the editors and whether they are enabled for the post type.
* @param string $post_type The post type.
*/
public function force_block_editor( $editors, $post_type ) {
if ( 'rank_math_locations' !== $post_type || ! $this->do_filter( 'schema/cpt_force_gutenberg', true ) ) {
return $editors;
}
$editors['classic_editor'] = false;
return $editors;
}
/**
* Add Locations KML file in the sitemap
*/
public function add_kml_file() {
return Helper::get_settings( 'sitemap.local_sitemap', true );
}
/**
* Include required files.
*/
private function includes() {
if ( is_admin() ) {
new Admin();
new RM_Pointers();
return;
}
if ( Helper::get_settings( 'titles.use_multiple_locations', false ) ) {
new Frontend();
new Location_Shortcode();
new KML_File();
}
}
/**
* Register Locations post type.
*/
private function register_location_post_type() {
$plural_label = Helper::get_settings( 'titles.locations_post_type_plural_label', 'RM Locations' );
$post_type_slug = Helper::get_settings( 'titles.locations_post_type_base', 'locations' );
$labels = [
'name' => $this->post_singular_name,
'singular_name' => $this->post_singular_name,
'menu_name' => $plural_label,
/* translators: Post Type Plural Name */
'all_items' => sprintf( esc_html__( 'All %s', 'rank-math-pro' ), $plural_label ),
/* translators: Post Type Singular Name */
'add_new_item' => sprintf( esc_html__( 'Add New %s', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'new_item' => sprintf( esc_html__( 'New %s', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'edit_item' => sprintf( esc_html__( 'Edit %s', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'update_item' => sprintf( esc_html__( 'Update %s', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'view_item' => sprintf( esc_html__( 'View %s', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Plural Name */
'view_items' => sprintf( esc_html__( 'View %s', 'rank-math-pro' ), $plural_label ),
/* translators: Post Type Singular Name */
'search_items' => sprintf( esc_html__( 'Search %s', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'not_found' => sprintf( esc_html__( 'No %s found.', 'rank-math-pro' ), $plural_label ),
/* translators: Post Type Singular Name */
'not_found_in_trash' => sprintf( esc_html__( 'No %s found in Trash.', 'rank-math-pro' ), $plural_label ),
/* translators: Post Type Singular Name */
'item_published' => sprintf( esc_html__( '%s published.', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'item_published_privately' => sprintf( esc_html__( '%s published privately.', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'item_reverted_to_draft' => sprintf( esc_html__( '%s reverted to draft.', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'item_scheduled' => sprintf( esc_html__( '%s scheduled.', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'item_updated' => sprintf( esc_html__( '%s updated.', 'rank-math-pro' ), $this->post_singular_name ),
];
$capability = 'rank_math_general';
$args = [
'label' => $this->post_singular_name,
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'hierarchical' => false,
'has_archive' => $post_type_slug,
'menu_icon' => 'dashicons-location',
'query_var' => true,
'show_in_rest' => true,
'rest_base' => 'rank-math-locations',
'supports' => [ 'title', 'editor', 'excerpt', 'author', 'thumbnail', 'revisions', 'custom-fields', 'page-attributes', 'publicize' ],
'rewrite' => [
'slug' => $post_type_slug,
'with_front' => $this->filter( 'locations/front', true ),
],
'capabilities' => [
'edit_post' => $capability,
'read_post' => $capability,
'delete_post' => $capability,
'edit_posts' => $capability,
'edit_others_posts' => $capability,
'publish_posts' => $capability,
'read_private_posts' => $capability,
'create_posts' => $capability,
],
];
register_post_type( $this->post_type, $args );
}
/**
* Register Locations Category taxonomy.
*/
private function register_location_taxonomy() {
$category_slug = esc_html( Helper::get_settings( 'titles.locations_category_base', 'locations-category' ) );
$labels = [
/* translators: Post Type Singular Name */
'name' => sprintf( esc_html__( '%s categories', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'singular_name' => sprintf( esc_html__( '%s category', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'all_items' => sprintf( esc_html__( 'All %s categories', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'edit_item' => sprintf( esc_html__( 'Edit %s category', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'update_item' => sprintf( esc_html__( 'Update %s category', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'add_new_item' => sprintf( esc_html__( 'Add New %s category', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'new_item_name' => sprintf( esc_html__( 'New %s category', 'rank-math-pro' ), $this->post_singular_name ),
/* translators: Post Type Singular Name */
'menu_name' => sprintf( esc_html__( '%s categories', 'rank-math-pro' ), $this->post_singular_name ),
'search_items' => esc_html__( 'Search categories', 'rank-math-pro' ),
'parent_item' => esc_html__( 'Parent Category', 'rank-math-pro' ),
'parent_item_colon' => esc_html__( 'Parent Category:', 'rank-math-pro' ),
];
$args = [
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'show_in_rest' => true,
'rewrite' => [ 'slug' => $category_slug ],
];
register_taxonomy( 'rank_math_location_category', [ $this->post_type ], $args );
}
/**
* Check for relevant post type before invalidation.
*
* @param int $post_id Post ID to possibly invalidate for.
*/
public function invalidate_cache( $post_id ) {
if ( get_post_type( $post_id ) !== $this->post_type ) {
return false;
}
Cache_Watcher::clear( [ 'locations' ] );
}
/**
* Add custom columns for post type.
*
* @param array $columns Current columns.
*
* @return array
*/
public function posts_columns( $columns ) {
$columns['address'] = __( 'Address', 'rank-math-pro' );
$columns['telephone'] = __( 'Phone', 'rank-math-pro' );
return $columns;
}
/**
* Add the content in the custom columns.
*
* @param string $column Column name.
* @param int $post_id Post ID.
*
* @return void
*/
public function posts_custom_column( $column, $post_id ) {
$schemas = \RankMath\Schema\DB::get_schemas( $post_id );
if ( empty( $schemas ) ) {
return;
}
$schema = reset( $schemas );
if ( empty( $schema[ $column ] ) ) {
return;
}
switch ( $column ) {
case 'address':
unset( $schema['address']['@type'] );
echo esc_html( join( ' ', $schema['address'] ) );
break;
case 'telephone':
echo esc_html( $schema['telephone'] );
break;
}
}
/**
* Remove unneeded bulk actions.
*
* @param array $actions Actions.
* @return array New actions.
*/
public function post_bulk_actions( $actions ) {
unset( $actions['rank_math_bulk_schema_none'], $actions['rank_math_bulk_schema_default'] );
return $actions;
}
}

View File

@@ -0,0 +1,567 @@
<?php
/**
* The Schema Shortcode
*
* @since 1.0.24
* @package RankMath
* @subpackage RankMath\Schema
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\Helper;
use RankMath\Schema\DB;
use RankMath\Traits\Hooker;
use RankMath\Traits\Shortcode;
use MyThemeShop\Helpers\Str;
defined( 'ABSPATH' ) || exit;
/**
* Snippet_Shortcode class.
*/
class Location_Shortcode {
use Hooker, Shortcode;
/**
* Shortcode attributes.
*
* @var array
*/
public $atts = [];
/**
* Address Instance.
*
* @var Address
*/
public $address;
/**
* Opening Hours Instance.
*
* @var Opening_Hours
*/
public $opening_hours;
/**
* Map Instance.
*
* @var Map
*/
public $map;
/**
* Store Locator Instance.
*
* @var Store_Locator
*/
public $store_locator;
/**
* API Key.
*
* @var string
*/
public $api_key;
/**
* The Constructor.
*/
public function __construct() {
$this->address = new Address();
$this->opening_hours = new Opening_Hours();
$this->map = new Map();
$this->store_locator = new Store_Locator();
$this->api_key = Helper::get_settings( 'titles.maps_api_key' );
// Add Yoast compatibility shortcodes.
$this->add_shortcode( 'wpseo_all_locations', 'yoast_locations' );
$this->add_shortcode( 'wpseo_storelocator', 'yoast_store_locator' );
$this->add_shortcode( 'wpseo_opening_hours', 'yoast_opening_hours' );
$this->add_shortcode( 'wpseo_map', 'yoast_map' );
$this->add_shortcode( 'rank_math_local', 'local_shortcode' );
$this->action( 'wp_enqueue_scripts', 'enqueue' );
$this->action( 'wp_enqueue_scripts', 'enqueue' );
if ( ! function_exists( 'register_block_type' ) ) {
return;
}
register_block_type(
'rank-math/local-business',
[
'render_callback' => [ $this, 'local_shortcode' ],
'attributes' => $this->get_attributes(),
]
);
}
/**
* Enqueue Map scripts.
*/
public function enqueue() {
if ( ! $this->api_key ) {
return;
}
wp_register_script( 'rank-math-google-maps', '//maps.googleapis.com/maps/api/js?&key=' . rawurlencode( $this->api_key ), [], rank_math_pro()->version, true );
wp_register_script( 'rank-math-google-maps-cluster', 'https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/markerclusterer/markerclustererplus@4.0.1.min.js', [], rank_math_pro()->version, true );
wp_register_script( 'rank-math-local', RANK_MATH_PRO_URL . 'includes/modules/local-seo/assets/js/rank-math-local.js', [ 'jquery', 'lodash', 'rank-math-google-maps', 'rank-math-google-maps-cluster' ], rank_math_pro()->version, true );
}
/**
* Location shortcode.
*
* @param array $atts Optional. Shortcode arguments.
*
* @return string Shortcode output.
*/
public function local_shortcode( $atts ) {
$defaults = [];
foreach ( $this->get_attributes() as $key => $attribute ) {
$defaults[ $key ] = $attribute['default'];
}
$this->atts = shortcode_atts(
$defaults,
$atts,
'rank_math_local'
);
if ( ! $this->api_key && is_user_logged_in() && in_array( $this->atts['type'], [ 'store-locator', 'map' ], true ) ) {
return sprintf(
/* Translators: %s expands to General Settings Link. */
esc_html__( 'This page can\'t load Google Maps correctly. Please add %s.', 'rank-math-pro' ),
'<a href="' . Helper::get_admin_url( 'options-titles#setting-panel-local' ) . '" target="_blank">' . esc_html__( 'API Key', 'rank-math-pro' ) . '</a>'
);
}
wp_enqueue_style( 'rank-math-local-business', RANK_MATH_PRO_URL . 'includes/modules/local-seo/assets/css/local-business.css', null, rank_math_pro()->version );
if ( 'store-locator' === $this->atts['type'] ) {
return $this->store_locator->get_data( $this );
}
return $this->get_shortcode_data();
}
/**
* Yoast Map compatibility functionality.
*
* @param array $atts Array of arguments.
* @return string
*/
public function yoast_map( $atts ) {
$atts['type'] = 'map';
return $this->yoast_locations( $atts );
}
/**
* Yoast Opening Hours compatibility functionality.
*
* @param array $atts Array of arguments.
* @return string
*/
public function yoast_opening_hours( $atts ) {
$atts['type'] = 'opening-hours';
return $this->yoast_locations( $atts );
}
/**
* Yoast Store Locator compatibility functionality.
*
* @param array $atts Array of arguments.
* @return string
*/
public function yoast_store_locator( $atts ) {
$atts['type'] = 'store-locator';
return $this->yoast_locations( $atts );
}
/**
* Yoast locations compatibility functionality.
*
* @param array $args Array of arguments.
* @return string
*/
public function yoast_locations( $args ) {
$defaults = [
'id' => '',
'number' => -1,
'type' => 'address',
'term_id' => '',
'orderby' => 'menu_order title',
'order' => 'ASC',
'show_state' => true,
'show_country' => true,
'show_phone' => true,
'show_phone_2' => true,
'show_fax' => true,
'show_email' => true,
'show_url' => false,
'show_logo' => false,
'show_opening_hours' => false,
'hide_closed' => false,
'oneline' => false,
'echo' => false,
'comment' => '',
'radius' => 10,
'max_number' => '',
'show_radius' => false,
'show_nearest_suggestion' => true,
'show_map' => true,
'show_filter' => false,
'map_width' => '100%',
'scrollable' => true,
'draggable' => true,
'marker_clustering' => false,
'map_style' => 'ROADMAP',
'show_route' => true,
'show_route_label' => '',
'show_category_filter' => false,
'height' => 300,
'zoom' => -1,
'show_open_label' => false,
'show_days' => '',
'center' => '',
'default_show_infowindow' => false,
];
$new_atts = [];
$atts = shortcode_atts( $defaults, $args, 'wpseo_local_show_all_locations' );
$data = [
'type' => 'type',
'id' => 'locations',
'number' => 'limit',
'max_number' => 'limit',
'term_id' => 'terms',
'show_state' => 'show_state',
'show_country' => 'show_country',
'show_phone' => 'show_telephone',
'show_phone_2' => 'show_secondary_number',
'show_fax' => 'show_fax',
'show_email' => 'show_email',
'show_url' => 'show_url',
'show_logo' => 'show_logo',
'show_opening_hours' => 'show_opening_hours',
'show_days' => 'show_days',
'hide_closed' => 'hide_closed_days',
'oneline' => 'show_on_one_line',
'comment' => 'opening_hours_note',
'radius' => 'search_radius',
'show_radius' => 'show_radius',
'show_nearest_suggestion' => 'show_nearest_location',
'show_map' => 'show_map',
'show_filter' => 'show_category_filter',
'map_width' => 'map_width',
'map_height' => 'map_height',
'center' => 'map_center',
'zoom' => 'zoom_level',
'scrollable' => 'allow_scrolling',
'draggable' => 'allow_dragging',
'marker_clustering' => 'show_marker_clustering',
'map_style' => 'map_style',
'show_route' => 'show_route_planner',
'show_route_label' => 'route_label',
'show_category_filter' => 'show_category_filter',
'default_show_infowindow' => 'show_infowindow',
];
foreach ( $atts as $key => $value ) {
if ( ! isset( $data[ $key ] ) ) {
continue;
}
$new_atts[ $data[ $key ] ] = $value;
}
return $this->local_shortcode( $new_atts );
}
/**
* Get Location Shortcode data.
*
* @return string Shortcode data.
*/
private function get_shortcode_data() {
$args = [
'post_type' => 'rank_math_locations',
'numberposts' => empty( $this->atts['limit'] ) ? -1 : (int) $this->atts['limit'],
'include' => (int) $this->atts['locations'],
];
if ( ! empty( $this->atts['terms'] ) ) {
$args['tax_query'] = [
[
'taxonomy' => 'rank_math_location_category',
'field' => 'term_id',
'terms' => $this->atts['terms'],
],
];
}
/**
* Filter to change Locations query args.
*
* @param array $args Arguments to retrieve locations.
* @return array $args.
*/
$args = $this->do_filter( 'location_args', $args );
$locations = get_posts( $args );
if ( empty( $locations ) ) {
return esc_html__( 'Sorry, no locations were found.', 'rank-math-pro' );
}
if ( 'map' === $this->atts['type'] ) {
return $this->map->get_data( $this, $locations );
}
$data = '';
foreach ( $locations as $location ) {
$schema = DB::get_schemas( $location->ID );
if ( empty( $schema ) ) {
continue;
}
$schema = current( $this->replace_variables( $schema, $location ) );
$data .= '<div class="rank-math-business-wrapper">';
$data .= $this->get_title( $schema );
$data .= $this->get_image( $schema );
if ( 'address' === $this->atts['type'] ) {
$data .= $this->address->get_data( $this, $schema );
}
if ( 'opening-hours' === $this->atts['type'] || $this->atts['show_opening_hours'] ) {
$data .= $this->opening_hours->get_data( $this, $schema );
}
$data .= '</div>';
}
return $data;
}
/**
* Get Location Title.
*
* @param Object $schema Location schema data.
*
* @return string Shortcode data.
*/
public function get_title( $schema ) {
if ( empty( $this->atts['show_company_name'] ) || empty( $schema['name'] ) ) {
return;
}
return '<h3 class="rank-math-business-name">' . esc_html( $schema['name'] ) . '</h3>';
}
/**
* Get Location Image.
*
* @param Object $schema Schema Data.
*
* @return string Shortcode data.
*/
public function get_image( $schema ) {
if ( empty( $this->atts['show_logo'] ) || empty( $schema['image'] ) ) {
return;
}
return '<div class="rank-math-business-image"><img src="' . esc_url( $schema['image']['url'] ) . '" /><div>';
}
/**
* Replace variable.
*
* @param array $schemas Schema to replace.
* @param object $location Location Post Object.
* @return array
*/
public function replace_variables( $schemas, $location = [] ) {
$new_schemas = [];
foreach ( $schemas as $key => $schema ) {
if ( is_array( $schema ) ) {
$new_schemas[ $key ] = $this->replace_variables( $schema, $location );
continue;
}
$new_schemas[ $key ] = Str::contains( '%', $schema ) ? Helper::replace_seo_fields( $schema, $location ) : $schema;
}
return $new_schemas;
}
/**
* Shortcode & Block default attributes.
*/
private function get_attributes() {
return [
'type' => [
'default' => 'address',
'type' => 'string',
],
'locations' => [
'default' => '',
'type' => 'string',
],
'terms' => [
'default' => [],
'type' => 'array',
],
'limit' => [
'default' => Helper::get_settings( 'titles.limit_results', 10 ),
'type' => 'integer',
],
'show_company_name' => [
'type' => 'boolean',
'default' => true,
],
'show_company_address' => [
'type' => 'boolean',
'default' => true,
],
'show_on_one_line' => [
'type' => 'boolean',
'default' => false,
],
'show_state' => [
'type' => 'boolean',
'default' => true,
],
'show_country' => [
'type' => 'boolean',
'default' => true,
],
'show_telephone' => [
'type' => 'boolean',
'default' => true,
],
'show_secondary_number' => [
'type' => 'boolean',
'default' => true,
],
'show_fax' => [
'type' => 'boolean',
'default' => false,
],
'show_email' => [
'type' => 'boolean',
'default' => true,
],
'show_url' => [
'type' => 'boolean',
'default' => true,
],
'show_logo' => [
'type' => 'boolean',
'default' => true,
],
'show_vat_id' => [
'type' => 'boolean',
'default' => false,
],
'show_tax_id' => [
'type' => 'boolean',
'default' => false,
],
'show_coc_id' => [
'type' => 'boolean',
'default' => false,
],
'show_priceRange' => [
'type' => 'boolean',
'default' => false,
],
'show_opening_hours' => [
'type' => 'boolean',
'default' => false,
],
'show_days' => [
'type' => 'string',
'default' => 'Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday',
],
'hide_closed_days' => [
'type' => 'boolean',
'default' => false,
],
'show_opening_now_label' => [
'type' => 'boolean',
'default' => false,
],
'opening_hours_note' => [
'type' => 'string',
'default' => 'Open Now',
],
'show_map' => [
'type' => 'boolean',
'default' => false,
],
'map_style' => [
'type' => 'string',
'default' => Helper::get_settings( 'titles.map_style', 'roadmap' ),
],
'map_width' => [
'type' => 'string',
'default' => '100%',
],
'map_height' => [
'type' => 'string',
'default' => '300px',
],
'zoom_level' => [
'type' => 'integer',
'default' => -1,
],
'allow_zoom' => [
'type' => 'boolean',
'default' => true,
],
'allow_scrolling' => [
'type' => 'boolean',
'default' => true,
],
'allow_dragging' => [
'type' => 'boolean',
'default' => true,
],
'show_route_planner' => [
'type' => 'boolean',
'default' => true,
],
'route_label' => [
'type' => 'string',
'default' => Helper::get_settings( 'titles.route_label' ),
],
'show_category_filter' => [
'type' => 'boolean',
'default' => false,
],
'show_marker_clustering' => [
'type' => 'boolean',
'default' => true,
],
'show_infowindow' => [
'type' => 'boolean',
'default' => true,
],
'show_radius' => [
'type' => 'boolean',
'default' => true,
],
'show_nearest_location' => [
'type' => 'boolean',
'default' => true,
],
'search_radius' => [
'type' => 'string',
'default' => '10',
],
];
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* The Rank Math Tutorial class.
*
* @since 2.1.2
* @package RankMath
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*
* @copyright Copyright (C) 2008-2020, WooCommerce
* The following code is a derivative work of the code from the WooCommerce(https://github.com/woocommerce/woocommerce), which is licensed under GPL v3.
*/
namespace RankMathPro\Local_Seo;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* RM_Pointers Class.
*/
class RM_Pointers {
use Hooker;
/**
* Constructor.
*/
public function __construct() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$this->action( 'admin_enqueue_scripts', 'enqueue_pointers' );
}
/**
* Enqueue pointers and add script to page.
*/
public function enqueue_pointers() {
$screen = get_current_screen();
if ( ! $screen || 'rank_math_locations' !== $screen->id ) {
return;
}
if ( get_option( 'rank_math_remove_locations_tutorial' ) ) {
return;
}
update_option( 'rank_math_remove_locations_tutorial', true );
wp_enqueue_style( 'wp-pointer' );
wp_enqueue_script(
'rank-math-pro-pointers',
RANK_MATH_PRO_URL . 'includes/modules/local-seo/assets/js/rank-math-pointers.js',
[
'jquery',
'wp-pointer',
'wp-i18n',
'lodash',
],
rank_math_pro()->version,
true
);
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* The Local_Seo Module
*
* @since 1.0.0
* @package RankMathPro
* @subpackage RankMath
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\Helper;
use RankMath\Traits\Hooker;
defined( 'ABSPATH' ) || exit;
/**
* Search class.
*/
class Search {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
$this->action( 'wp', 'integrations' );
}
/**
* Initialize integrations.
*/
public function integrations() {
if ( ! is_search() || ! Helper::get_settings( 'titles.locations_enhanced_search' ) ) {
return;
}
$this->filter( 'the_excerpt', 'add_locations_data' );
}
/**
* Add Locations data to search results.
*
* @param string $excerpt Post excerpt.
* @return string $excerpt Processed excerpt.
*/
public function add_locations_data( $excerpt ) {
global $post;
if ( get_post_type( $post->ID ) !== 'rank_math_locations' ) {
return $excerpt;
}
$excerpt .= do_shortcode( '[rank_math_local type="address" locations="' . $post->ID . '" show_company_name=""]' );
return $excerpt;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* The Address shortcode Class.
*
* @since 1.0.1
* @package RankMath
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
/**
* Address class.
*/
class Address {
/**
* Get Address Data.
*
* @param Location_Shortcode $shortcode Location_Shortcode Instance.
* @param array $schema Array of Schema data.
*
* @return string
*/
public function get_data( $shortcode, $schema ) {
$atts = $shortcode->atts;
$data = $this->get_address( $schema, $atts );
$schema = $schema['metadata'] + $schema;
$labels = [
'telephone' => [
'key' => 'telephone',
'label' => esc_html__( 'Phone', 'rank-math-pro' ),
],
'secondary_number' => [
'key' => 'secondary_number',
'label' => esc_html__( 'Secondary phone', 'rank-math-pro' ),
],
'fax' => [
'key' => 'faxNumber',
'label' => esc_html__( 'Fax', 'rank-math-pro' ),
],
'email' => [
'key' => 'email',
'label' => esc_html__( 'Email', 'rank-math-pro' ),
],
'url' => [
'key' => 'url',
'label' => esc_html__( 'URL', 'rank-math-pro' ),
],
'vat_id' => [
'key' => 'vatID',
'label' => esc_html__( 'VAT ID', 'rank-math-pro' ),
],
'tax_id' => [
'key' => 'taxID',
'label' => esc_html__( 'Tax ID', 'rank-math-pro' ),
],
'coc_id' => [
'key' => 'coc_id',
'label' => esc_html__( 'Chamber of Commerce ID', 'rank-math-pro' ),
],
'priceRange' => [
'key' => 'priceRange',
'label' => esc_html__( 'Price indication', 'rank-math-pro' ),
],
];
foreach ( $labels as $key => $label ) {
if ( empty( $atts[ "show_$key" ] ) || empty( $schema[ $label['key'] ] ) ) {
continue;
}
$value = esc_html( $schema[ $label['key'] ] );
if ( 'email' === $key ) {
$value = '<a href="mailto:' . $value . '">' . $value . '</a>';
}
if ( in_array( $key, [ 'telephone', 'secondary_number' ], true ) ) {
$value = '<a href="tel:' . $value . '">' . $value . '</a>';
}
$data .= '<div><strong>' . $label['label'] . '</strong>: ' . $value . '</div>';
}
return $data;
}
/**
* Get Address Data.
*
* @param array $schema Array of Schema data.
* @param array $atts Shortcode attributes.
*
* @return string
*/
public function get_address( $schema, $atts = [] ) {
$address = array_filter( $schema['address'] );
if ( false === $address || empty( $atts['show_company_address'] ) ) {
return '';
}
$hash = array_filter(
[
'streetAddress' => true,
'addressLocality' => true,
'addressRegion' => ! empty( $atts['show_state'] ),
'addressCountry' => ! empty( $atts['show_country'] ),
'postalCode' => true,
]
);
$glue = empty( $atts['show_on_one_line'] ) ? ',<br />' : ', ';
$data = implode( $glue, array_intersect_key( $address, $hash ) );
return '<h5>' . esc_html__( 'Address:', 'rank-math-pro' ) . '</h5><address>' . $data . '</address>';
}
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* The Map shortcode Class.
*
* @since 1.0.1
* @package RankMath
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\Schema\DB;
defined( 'ABSPATH' ) || exit;
/**
* Map class.
*/
class Map {
/**
* Shortcode Instance.
*
* @var Location_Shortcode
*/
public $shortcode;
/**
* Get Address Data.
*
* @param Location_Shortcode $shortcode Location_Shortcode Instance.
* @param array $locations Locations data.
* @return string
*/
public function get_data( $shortcode, $locations ) {
$this->shortcode = $shortcode;
$atts = $shortcode->atts;
$options = [
'map_style' => $atts['map_style'],
'allow_zoom' => $atts['allow_zoom'],
'zoom_level' => $atts['zoom_level'],
'allow_dragging' => $atts['allow_dragging'],
'show_clustering' => $atts['show_marker_clustering'],
'show_infowindow' => $atts['show_infowindow'],
];
$terms_data = [];
foreach ( $locations as $location ) {
$schema = DB::get_schemas( $location->ID );
if ( empty( $schema ) ) {
continue;
}
$schema = current( $shortcode->replace_variables( $schema ) );
if ( empty( $schema['geo']['latitude'] ) || empty( $schema['geo']['longitude'] ) ) {
continue;
}
$options['locations'][ $location->ID ] = [
'content' => $this->get_infobox_content( $location->ID, $schema ),
'lat' => $schema['geo']['latitude'],
'lng' => $schema['geo']['longitude'],
];
if ( ! empty( $atts['show_category_filter'] ) && 'map' === $atts['type'] ) {
$terms = get_the_terms( $location->ID, 'rank_math_location_category' );
if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
$terms_data = array_merge( $terms_data, $terms );
$options['locations'][ $location->ID ]['terms'] = wp_list_pluck( $terms, 'term_id' );
}
}
}
if ( empty( $options['locations'] ) ) {
return;
}
wp_enqueue_script( 'rank-math-local' );
$width = ! empty( $atts['map_width'] ) ? $atts['map_width'] : '100%';
$height = ! empty( $atts['map_height'] ) ? $atts['map_height'] : '500px';
$style = sprintf( 'style="width: %s; height: %s"', $width, $height );
ob_start();
?>
<div class="rank-math-local-map-wrapper">
<div class="rank-math-local-map" data-map-options="<?php echo esc_attr( wp_json_encode( $options ) ); ?>" <?php printf( 'style="width: %s; height: %s"', esc_attr( $width ), esc_attr( $height ) ); ?>></div>
<?php
if ( ! empty( $terms_data ) ) {
echo '<select id="rank-math-select-category">';
echo '<option value="">' . esc_html__( 'Select Category', 'rank-math-pro' ) . '</option>';
foreach ( $terms_data as $term ) {
echo '<option value="' . esc_attr( $term->term_id ) . '">' . esc_html( $term->name ) . '</option>';
}
echo '</select>';
}
?>
</div>
<?php
return ob_get_clean();
}
/**
* Get Infobox Content.
*
* @param int $location_id The Location ID.
* @param array $schema Schema Data.
* @return string
*/
public function get_infobox_content( $location_id, $schema ) {
return '<div class="rank-math-infobox-wrapper">
<h5><a href="' . esc_url( get_the_permalink( $location_id ) ) . '">' . esc_html( get_the_title( $location_id ) ) . '</a></h5>
<p>' . $this->shortcode->address->get_data( $this->shortcode, $schema ) . '</p>
</div>';
}
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* The Opening Hours shortcode Class.
*
* @since 1.0.1
* @package RankMath
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
/**
* Opening_Hours class.
*/
class Opening_Hours {
/**
* Get Opening_Hours Data.
*
* @param Location_Shortcode $shortcode Location_Shortcode Instance.
* @param array $schema Schema data.
* @return string
*/
public function get_data( $shortcode, $schema ) {
if ( ! isset( $schema['openingHoursSpecification'] ) ) {
return '<p>' . esc_html__( 'Open 24/7', 'rank-math-pro' ) . '</p>';
}
if ( empty( $schema['openingHoursSpecification'] ) ) {
return false;
}
$days = $this->normalize_days( $schema, $shortcode );
ob_start();
?>
<h5><?php esc_html_e( 'Opening Hours:', 'rank-math-pro' ); ?></h5>
<div class="rank-math-business-opening-hours">
<?php
foreach ( $days as $day => $hours ) {
$time = ! empty( $hours['time'] ) ? implode( ' and ', $hours['time'] ) : esc_html__( 'Closed', 'rank-math-pro' );
$time = str_replace( '-', ' &ndash; ', $time );
printf(
'<div class="rank-math-opening-hours"><span class="rank-math-opening-days">%1$s</span> : <span class="rank-math-opening-time">%2$s</span> <span class="rank-math-business-open">%3$s</span></div>',
esc_html( $this->get_localized_day( $day ) ),
esc_html( $time ),
esc_html( $hours['isOpen'] )
);
}
?>
</div>
<?php
return ob_get_clean();
}
/**
* Get Local Time.
*
* @param Location_Shortcode $shortcode Location_Shortcode Instance.
* @param array $schema Schema data.
* @return string
*/
private function get_local_time( $shortcode, $schema ) {
if ( empty( $shortcode->atts['show_opening_now_label'] ) ) {
return false;
}
$timezone = ! empty( $schema['metadata']['timeZone'] ) ? $schema['metadata']['timeZone'] : wp_timezone_string();
$local_datetime = new \DateTime( 'now', new \DateTimeZone( $timezone ) );
return [
'day' => $local_datetime->format( 'l' ),
'time' => strtotime( $local_datetime->format( 'H:i' ) ),
];
}
/**
* Normalize Weekdays.
*
* @param array $schema Schema data.
* @param Location_Shortcode $shortcode Location_Shortcode Instance.
* @return array
*/
private function normalize_days( $schema, $shortcode ) {
$hours = $schema['openingHoursSpecification'];
$days = explode( ',', $shortcode->atts['show_days'] );
$format = ! isset( $schema['metadata']['use_24h_format'] ) ? Helper::get_settings( 'titles.opening_hours_format' ) : empty( $schema['metadata']['use_24h_format'] );
$data = [];
$local_time = $this->get_local_time( $shortcode, $schema );
foreach ( $days as $day ) {
$day = ucfirst( trim( $day ) );
$data[ $day ] = [
'isOpen' => '',
];
foreach ( $hours as $hour ) {
if ( ! in_array( $day, (array) $hour['dayOfWeek'], true ) ) {
continue;
}
$open = strtotime( $hour['opens'] );
$close = strtotime( $hour['closes'] );
$is_open = ! empty( $local_time ) &&
$day === $local_time['day'] &&
$local_time['time'] >= $open &&
$local_time['time'] <= $close;
$data[ $day ]['time'][] = $format ? date_i18n( 'g:i a', $open ) . ' - ' . date_i18n( 'g:i a', $close ) : $hour['opens'] . ' - ' . $hour['closes'];
$data[ $day ]['isOpen'] = $is_open ? $this->get_opening_hours_note( $shortcode ) : '';
}
if ( $shortcode->atts['hide_closed_days'] && empty( $data[ $day ]['time'] ) ) {
unset( $data[ $day ] );
}
}
return $data;
}
/**
* Get Opening Hours note.
*
* @param Location_Shortcode $shortcode Location_Shortcode Instance.
* @return string
*/
private function get_opening_hours_note( $shortcode ) {
return empty( $shortcode->atts['opening_hours_note'] ) ? esc_html__( 'Open now', 'rank-math-pro' ) : esc_html( $shortcode->atts['opening_hours_note'] );
}
/**
* Retrieve the full translated weekday word.
*
* @param string $day Day to translate.
*
* @return string
*/
private function get_localized_day( $day ) {
global $wp_locale;
$hash = [
'Sunday' => 0,
'Monday' => 1,
'Tuesday' => 2,
'Wednesday' => 3,
'Thursday' => 4,
'Friday' => 5,
'Saturday' => 6,
];
return ! isset( $hash[ $day ] ) ? $day : $wp_locale->get_weekday( $hash[ $day ] );
}
}

View File

@@ -0,0 +1,232 @@
<?php
/**
* The Store Locator shortcode Class.
*
* @since 1.0.1
* @package RankMath
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Local_Seo;
use RankMath\Helper;
use RankMath\Post;
use RankMath\Schema\DB;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* Store_Locator class.
*/
class Store_Locator {
/**
* Get Store_Locator Data.
*
* @param Location_Shortcode $shortcode Location_Shortcode Instance.
* @return string
*/
public function get_data( $shortcode ) {
$unit = 'miles' === Helper::get_settings( 'titles.map_unit', 'kilometers' ) ? 'mi' : 'km';
$radius = Param::post( 'rank-math-search-radius', 20 );
$address = Param::post( 'rank-math-search-address' );
$category = Param::post( 'rank-math-location-category' );
$terms = empty( $shortcode->atts['show_category_filter'] )
? []
: get_terms(
[
'taxonomy' => 'rank_math_location_category',
'fields' => 'id=>name',
]
);
wp_enqueue_script( 'rank-math-local' );
ob_start();
?>
<div class="rank-math-business-wrapper">
<form id="rank-math-local-store-locator" method="post" action="#rank-math-local-store-locator">
<?php if ( ! empty( $shortcode->atts['show_radius'] ) ) { ?>
<div class="rank-math-form-field">
<select name="rank-math-search-radius">
<?php
foreach ( [ 1, 5, 10, 20, 40, 50, 75, 100, 200, 300, 400, 500, 1000 ] as $value ) {
echo "<option value='{$value}' " . selected( $radius, $value, true ) . ">{$value}{$unit}</option>";
}
?>
</select>
</div>
<?php } ?>
<?php if ( ! empty( $terms ) ) { ?>
<div class="rank-math-form-field">
<select name="rank-math-location-category">
<option value=""><?php echo esc_html__( 'Select Category', 'rank-math-pro' ); ?></option>
<?php foreach ( $terms as $term_id => $term_name ) { ?>
<option value="<?php echo esc_attr( $term_id ); ?>" <?php selected( $category, $term_id ); ?>><?php echo esc_html( $term_name ); ?></option>
<?php } ?>
</select>
</div>
<?php } ?>
<div class="rank-math-form-field">
<input type="text" name="rank-math-search-address" id="rank-math-search-address" placeholder="<?php echo esc_html__( 'Address, Suburb, Region, Zip or Landmark', 'rank-math-pro' ); ?>" value="<?php echo esc_attr( $address ); ?>" />
<input type="hidden" name="lat" id="rank-math-lat" />
<input type="hidden" name="lng" id="rank-math-lng" />
</div>
<?php $this->detect_location(); ?>
<div class="rank-math-form-field">
<button type="submit" name="rank-math-submit" value="search"><?php echo esc_html__( 'Search', 'rank-math-pro' ); ?></button>
</div>
</form>
<?php
echo $this->get_results( $shortcode, $unit );
echo '</div>';
return ob_get_clean();
}
/**
* Add detect current location button.
*
* @return string
*/
private function detect_location() {
if ( ! Helper::get_settings( 'titles.enable_location_detection' ) ) {
return;
}
echo '<a href="#" id="rank-math-current-location">' . esc_html__( 'Detect Location', 'rank-math-pro' ) . '</a>';
}
/**
* Get Map Results.
*
* @param Location_Shortcode $shortcode Location_Shortcode Instance.
* @param string $unit Map measurement unit.
* @return string
*/
private function get_results( $shortcode, $unit ) {
if ( ! Param::post( 'rank-math-search-address' ) ) {
return false;
}
global $wpdb;
// Radius of the earth 3959 miles or 6371 kilometers.
$earth_radius = 'mi' === $unit ? 3959 : 6371;
$radius = ! empty( $shortcode->atts['show_radius'] ) ? Param::post( 'rank-math-search-radius', 20 ) : $shortcode->atts['search_radius'];
$latitude = Param::post( 'lat' );
$longitude = Param::post( 'lng' );
$category = Param::post( 'rank-math-location-category', 0, FILTER_VALIDATE_INT );
$inner_join = '';
if ( $category ) {
$inner_join .= $wpdb->prepare(
"INNER JOIN $wpdb->term_relationships AS tr ON p.ID = tr.object_id
INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
AND tt.taxonomy = 'rank_math_location_category'
AND tt.term_id = %d",
$category
);
}
$nearby_locations = $wpdb->get_results(
$wpdb->prepare(
"SELECT DISTINCT
p.*,
map_lat.meta_value as locLat,
map_lng.meta_value as locLong,
( %d * acos(
cos( radians( %s ) )
* cos( radians( map_lat.meta_value ) )
* cos( radians( map_lng.meta_value ) - radians( %s ) )
+ sin( radians( %s ) )
* sin( radians( map_lat.meta_value ) )
) )
AS distance
FROM $wpdb->posts p
INNER JOIN $wpdb->postmeta map_lat ON p.ID = map_lat.post_id
INNER JOIN $wpdb->postmeta map_lng ON p.ID = map_lng.post_id
$inner_join
WHERE 1 = 1
AND p.post_type = 'rank_math_locations'
AND p.post_status = 'publish'
AND map_lat.meta_key = 'rank_math_local_business_latitide'
AND map_lng.meta_key = 'rank_math_local_business_longitude'
HAVING distance < %s
ORDER BY distance ASC",
$earth_radius,
$latitude,
$longitude,
$latitude,
$radius
)
);
//phpcs:enable
if ( empty( $nearby_locations ) ) {
return esc_html__( 'Sorry, no locations were found.', 'rank-math-pro' );
}
$data = ! empty( $shortcode->atts['show_map'] ) ? $shortcode->map->get_data( $shortcode, $nearby_locations ) : '';
foreach ( $nearby_locations as $location ) {
$schema = DB::get_schemas( $location->ID );
if ( empty( $schema ) ) {
continue;
}
$schema = current( $shortcode->replace_variables( $schema, $location ) );
$data .= $shortcode->get_title( $schema );
$data .= $shortcode->address->get_data( $shortcode, $schema );
$data .= ! empty( $shortcode->atts['show_opening_hours'] ) ? $shortcode->opening_hours->get_data( $shortcode, $schema ) : '';
$data .= $this->get_directions( $location, $shortcode );
}
return $data;
}
/**
* Get Map Results.
*
* @param Object $location Current Location Post.
* @param Location_Shortcode $shortcode Location_Shortcode Instance.
* @return string
*/
public function get_directions( $location, $shortcode ) {
if ( empty( $shortcode->atts['show_route_planner'] ) || empty( $shortcode->atts['show_map'] ) ) {
return '';
}
$lat = Post::get_meta( 'local_business_latitide', $location->ID );
$lng = Post::get_meta( 'local_business_longitude', $location->ID );
ob_start();
?>
<div class="rank-math-directions-wrapper">
<a href="#" class="rank-math-show-route" data-toggle-text="<?php echo esc_html__( 'Hide route', 'rank-math-pro' ); ?>">
<?php echo esc_html( $shortcode->atts['route_label'] ); ?>
</a>
<div class="rank-math-directions-result">
<h3><?php echo esc_html__( 'Route', 'rank-math-pro' ); ?></h3>
<div class="rank-math-directions-form">
<form method="post">
<div class="rank-math-form-field">
<label for="origin"><?php echo esc_html__( 'Your location:', 'rank-math-pro' ); ?></label>
<input type="text" name="origin" id="rank-math-origin" value="<?php echo esc_attr( Param::post( 'rank-math-search-address' ) ); ?>" />
<input type="submit" name="get-direction" value="<?php echo esc_html__( 'Show route', 'rank-math-pro' ); ?>" />
<input type="hidden" name="rank-math-lat" id="rank-math-lat" value="<?php echo esc_attr( $lat ); ?>" />
<input type="hidden" name="rank-math-lng" id="rank-math-lng" value="<?php echo esc_attr( $lng ); ?>" />
</div>
</form>
</div>
<div class="rank-math-directions"></div>
</div>
</div>
<?php
return ob_get_clean();
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* Locations KML File settings.
*
* @since 2.2.0
* @package RankMath
* @subpackage RankMathPro
* @author MyThemeShop <admin@mythemeshop.com>
*/
defined( 'ABSPATH' ) || exit;
$cmb->add_field(
[
'id' => 'local_sitemap',
'type' => 'toggle',
'name' => esc_html__( 'Include KML File in the Sitemap', 'rank-math-pro' ),
'desc' => esc_html__( 'locations.kml Sitemap is generated automatically when the Local SEO module is enabled, and the geo-coordinates are added.', 'rank-math-pro' ),
'default' => 'on',
]
);

View File

@@ -0,0 +1,764 @@
<?php
/**
* The local seo settings.
*
* @package RankMath
* @subpackage RankMathPro
*/
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
$company = [ [ 'knowledgegraph_type', 'company' ] ];
$person = [ [ 'knowledgegraph_type', 'person' ] ];
$use_multiple_locations = [ 'relation' => 'and' ] + $company;
$use_multiple_locations[] = [ 'use_multiple_locations', 'on' ];
$hide_on_multiple_locations = [ 'relation' => 'and' ] + $company;
$hide_on_multiple_locations[] = [ 'use_multiple_locations', 'on', '!=' ];
$countries = [
'' => __( 'Choose a country', 'rank-math-pro' ),
'AX' => __( 'Åland Islands', 'rank-math-pro' ),
'AF' => __( 'Afghanistan', 'rank-math-pro' ),
'AL' => __( 'Albania', 'rank-math-pro' ),
'DZ' => __( 'Algeria', 'rank-math-pro' ),
'AD' => __( 'Andorra', 'rank-math-pro' ),
'AO' => __( 'Angola', 'rank-math-pro' ),
'AI' => __( 'Anguilla', 'rank-math-pro' ),
'AQ' => __( 'Antarctica', 'rank-math-pro' ),
'AG' => __( 'Antigua and Barbuda', 'rank-math-pro' ),
'AR' => __( 'Argentina', 'rank-math-pro' ),
'AM' => __( 'Armenia', 'rank-math-pro' ),
'AW' => __( 'Aruba', 'rank-math-pro' ),
'AU' => __( 'Australia', 'rank-math-pro' ),
'AT' => __( 'Austria', 'rank-math-pro' ),
'AZ' => __( 'Azerbaijan', 'rank-math-pro' ),
'BS' => __( 'Bahamas', 'rank-math-pro' ),
'BH' => __( 'Bahrain', 'rank-math-pro' ),
'BD' => __( 'Bangladesh', 'rank-math-pro' ),
'BB' => __( 'Barbados', 'rank-math-pro' ),
'BY' => __( 'Belarus', 'rank-math-pro' ),
'PW' => __( 'Belau', 'rank-math-pro' ),
'BE' => __( 'Belgium', 'rank-math-pro' ),
'BZ' => __( 'Belize', 'rank-math-pro' ),
'BJ' => __( 'Benin', 'rank-math-pro' ),
'BM' => __( 'Bermuda', 'rank-math-pro' ),
'BT' => __( 'Bhutan', 'rank-math-pro' ),
'BO' => __( 'Bolivia', 'rank-math-pro' ),
'BQ' => __( 'Bonaire, Sint Eustatius and Saba', 'rank-math-pro' ),
'BA' => __( 'Bosnia and Herzegovina', 'rank-math-pro' ),
'BW' => __( 'Botswana', 'rank-math-pro' ),
'BV' => __( 'Bouvet Island', 'rank-math-pro' ),
'BR' => __( 'Brazil', 'rank-math-pro' ),
'IO' => __( 'British Indian Ocean Territory', 'rank-math-pro' ),
'VG' => __( 'British Virgin Islands', 'rank-math-pro' ),
'BN' => __( 'Brunei', 'rank-math-pro' ),
'BG' => __( 'Bulgaria', 'rank-math-pro' ),
'BF' => __( 'Burkina Faso', 'rank-math-pro' ),
'BI' => __( 'Burundi', 'rank-math-pro' ),
'KH' => __( 'Cambodia', 'rank-math-pro' ),
'CM' => __( 'Cameroon', 'rank-math-pro' ),
'CA' => __( 'Canada', 'rank-math-pro' ),
'CV' => __( 'Cape Verde', 'rank-math-pro' ),
'KY' => __( 'Cayman Islands', 'rank-math-pro' ),
'CF' => __( 'Central African Republic', 'rank-math-pro' ),
'TD' => __( 'Chad', 'rank-math-pro' ),
'CL' => __( 'Chile', 'rank-math-pro' ),
'CN' => __( 'China', 'rank-math-pro' ),
'CX' => __( 'Christmas Island', 'rank-math-pro' ),
'CC' => __( 'Cocos (Keeling) Islands', 'rank-math-pro' ),
'CO' => __( 'Colombia', 'rank-math-pro' ),
'KM' => __( 'Comoros', 'rank-math-pro' ),
'CG' => __( 'Congo (Brazzaville)', 'rank-math-pro' ),
'CD' => __( 'Congo (Kinshasa)', 'rank-math-pro' ),
'CK' => __( 'Cook Islands', 'rank-math-pro' ),
'CR' => __( 'Costa Rica', 'rank-math-pro' ),
'HR' => __( 'Croatia', 'rank-math-pro' ),
'CU' => __( 'Cuba', 'rank-math-pro' ),
'CW' => __( 'Curaçao', 'rank-math-pro' ),
'CY' => __( 'Cyprus', 'rank-math-pro' ),
'CZ' => __( 'Czech Republic', 'rank-math-pro' ),
'DK' => __( 'Denmark', 'rank-math-pro' ),
'DJ' => __( 'Djibouti', 'rank-math-pro' ),
'DM' => __( 'Dominica', 'rank-math-pro' ),
'DO' => __( 'Dominican Republic', 'rank-math-pro' ),
'EC' => __( 'Ecuador', 'rank-math-pro' ),
'EG' => __( 'Egypt', 'rank-math-pro' ),
'SV' => __( 'El Salvador', 'rank-math-pro' ),
'GQ' => __( 'Equatorial Guinea', 'rank-math-pro' ),
'ER' => __( 'Eritrea', 'rank-math-pro' ),
'EE' => __( 'Estonia', 'rank-math-pro' ),
'ET' => __( 'Ethiopia', 'rank-math-pro' ),
'FK' => __( 'Falkland Islands', 'rank-math-pro' ),
'FO' => __( 'Faroe Islands', 'rank-math-pro' ),
'FJ' => __( 'Fiji', 'rank-math-pro' ),
'FI' => __( 'Finland', 'rank-math-pro' ),
'FR' => __( 'France', 'rank-math-pro' ),
'GF' => __( 'French Guiana', 'rank-math-pro' ),
'PF' => __( 'French Polynesia', 'rank-math-pro' ),
'TF' => __( 'French Southern Territories', 'rank-math-pro' ),
'GA' => __( 'Gabon', 'rank-math-pro' ),
'GM' => __( 'Gambia', 'rank-math-pro' ),
'GE' => __( 'Georgia', 'rank-math-pro' ),
'DE' => __( 'Germany', 'rank-math-pro' ),
'GH' => __( 'Ghana', 'rank-math-pro' ),
'GI' => __( 'Gibraltar', 'rank-math-pro' ),
'GR' => __( 'Greece', 'rank-math-pro' ),
'GL' => __( 'Greenland', 'rank-math-pro' ),
'GD' => __( 'Grenada', 'rank-math-pro' ),
'GP' => __( 'Guadeloupe', 'rank-math-pro' ),
'GT' => __( 'Guatemala', 'rank-math-pro' ),
'GG' => __( 'Guernsey', 'rank-math-pro' ),
'GN' => __( 'Guinea', 'rank-math-pro' ),
'GW' => __( 'Guinea-Bissau', 'rank-math-pro' ),
'GY' => __( 'Guyana', 'rank-math-pro' ),
'HT' => __( 'Haiti', 'rank-math-pro' ),
'HM' => __( 'Heard Island and McDonald Islands', 'rank-math-pro' ),
'HN' => __( 'Honduras', 'rank-math-pro' ),
'HK' => __( 'Hong Kong', 'rank-math-pro' ),
'HU' => __( 'Hungary', 'rank-math-pro' ),
'IS' => __( 'Iceland', 'rank-math-pro' ),
'IN' => __( 'India', 'rank-math-pro' ),
'ID' => __( 'Indonesia', 'rank-math-pro' ),
'IR' => __( 'Iran', 'rank-math-pro' ),
'IQ' => __( 'Iraq', 'rank-math-pro' ),
'IM' => __( 'Isle of Man', 'rank-math-pro' ),
'IL' => __( 'Israel', 'rank-math-pro' ),
'IT' => __( 'Italy', 'rank-math-pro' ),
'CI' => __( 'Ivory Coast', 'rank-math-pro' ),
'JM' => __( 'Jamaica', 'rank-math-pro' ),
'JP' => __( 'Japan', 'rank-math-pro' ),
'JE' => __( 'Jersey', 'rank-math-pro' ),
'JO' => __( 'Jordan', 'rank-math-pro' ),
'KZ' => __( 'Kazakhstan', 'rank-math-pro' ),
'KE' => __( 'Kenya', 'rank-math-pro' ),
'KI' => __( 'Kiribati', 'rank-math-pro' ),
'KW' => __( 'Kuwait', 'rank-math-pro' ),
'KG' => __( 'Kyrgyzstan', 'rank-math-pro' ),
'LA' => __( 'Laos', 'rank-math-pro' ),
'LV' => __( 'Latvia', 'rank-math-pro' ),
'LB' => __( 'Lebanon', 'rank-math-pro' ),
'LS' => __( 'Lesotho', 'rank-math-pro' ),
'LR' => __( 'Liberia', 'rank-math-pro' ),
'LY' => __( 'Libya', 'rank-math-pro' ),
'LI' => __( 'Liechtenstein', 'rank-math-pro' ),
'LT' => __( 'Lithuania', 'rank-math-pro' ),
'LU' => __( 'Luxembourg', 'rank-math-pro' ),
'MO' => __( 'Macao S.A.R., China', 'rank-math-pro' ),
'MK' => __( 'Macedonia', 'rank-math-pro' ),
'MG' => __( 'Madagascar', 'rank-math-pro' ),
'MW' => __( 'Malawi', 'rank-math-pro' ),
'MY' => __( 'Malaysia', 'rank-math-pro' ),
'MV' => __( 'Maldives', 'rank-math-pro' ),
'ML' => __( 'Mali', 'rank-math-pro' ),
'MT' => __( 'Malta', 'rank-math-pro' ),
'MH' => __( 'Marshall Islands', 'rank-math-pro' ),
'MQ' => __( 'Martinique', 'rank-math-pro' ),
'MR' => __( 'Mauritania', 'rank-math-pro' ),
'MU' => __( 'Mauritius', 'rank-math-pro' ),
'YT' => __( 'Mayotte', 'rank-math-pro' ),
'MX' => __( 'Mexico', 'rank-math-pro' ),
'FM' => __( 'Micronesia', 'rank-math-pro' ),
'MD' => __( 'Moldova', 'rank-math-pro' ),
'MC' => __( 'Monaco', 'rank-math-pro' ),
'MN' => __( 'Mongolia', 'rank-math-pro' ),
'ME' => __( 'Montenegro', 'rank-math-pro' ),
'MS' => __( 'Montserrat', 'rank-math-pro' ),
'MA' => __( 'Morocco', 'rank-math-pro' ),
'MZ' => __( 'Mozambique', 'rank-math-pro' ),
'MM' => __( 'Myanmar', 'rank-math-pro' ),
'NA' => __( 'Namibia', 'rank-math-pro' ),
'NR' => __( 'Nauru', 'rank-math-pro' ),
'NP' => __( 'Nepal', 'rank-math-pro' ),
'NL' => __( 'Netherlands', 'rank-math-pro' ),
'AN' => __( 'Netherlands Antilles', 'rank-math-pro' ),
'NC' => __( 'New Caledonia', 'rank-math-pro' ),
'NZ' => __( 'New Zealand', 'rank-math-pro' ),
'NI' => __( 'Nicaragua', 'rank-math-pro' ),
'NE' => __( 'Niger', 'rank-math-pro' ),
'NG' => __( 'Nigeria', 'rank-math-pro' ),
'NU' => __( 'Niue', 'rank-math-pro' ),
'NF' => __( 'Norfolk Island', 'rank-math-pro' ),
'KP' => __( 'North Korea', 'rank-math-pro' ),
'NO' => __( 'Norway', 'rank-math-pro' ),
'OM' => __( 'Oman', 'rank-math-pro' ),
'PK' => __( 'Pakistan', 'rank-math-pro' ),
'PS' => __( 'Palestinian Territory', 'rank-math-pro' ),
'PA' => __( 'Panama', 'rank-math-pro' ),
'PG' => __( 'Papua New Guinea', 'rank-math-pro' ),
'PY' => __( 'Paraguay', 'rank-math-pro' ),
'PE' => __( 'Peru', 'rank-math-pro' ),
'PH' => __( 'Philippines', 'rank-math-pro' ),
'PN' => __( 'Pitcairn', 'rank-math-pro' ),
'PL' => __( 'Poland', 'rank-math-pro' ),
'PT' => __( 'Portugal', 'rank-math-pro' ),
'QA' => __( 'Qatar', 'rank-math-pro' ),
'IE' => __( 'Republic of Ireland', 'rank-math-pro' ),
'RE' => __( 'Reunion', 'rank-math-pro' ),
'RO' => __( 'Romania', 'rank-math-pro' ),
'RU' => __( 'Russia', 'rank-math-pro' ),
'RW' => __( 'Rwanda', 'rank-math-pro' ),
'ST' => __( 'São Tomé and Príncipe', 'rank-math-pro' ),
'BL' => __( 'Saint Barthélemy', 'rank-math-pro' ),
'SH' => __( 'Saint Helena', 'rank-math-pro' ),
'KN' => __( 'Saint Kitts and Nevis', 'rank-math-pro' ),
'LC' => __( 'Saint Lucia', 'rank-math-pro' ),
'SX' => __( 'Saint Martin (Dutch part)', 'rank-math-pro' ),
'MF' => __( 'Saint Martin (French part)', 'rank-math-pro' ),
'PM' => __( 'Saint Pierre and Miquelon', 'rank-math-pro' ),
'VC' => __( 'Saint Vincent and the Grenadines', 'rank-math-pro' ),
'SM' => __( 'San Marino', 'rank-math-pro' ),
'SA' => __( 'Saudi Arabia', 'rank-math-pro' ),
'SN' => __( 'Senegal', 'rank-math-pro' ),
'RS' => __( 'Serbia', 'rank-math-pro' ),
'SC' => __( 'Seychelles', 'rank-math-pro' ),
'SL' => __( 'Sierra Leone', 'rank-math-pro' ),
'SG' => __( 'Singapore', 'rank-math-pro' ),
'SK' => __( 'Slovakia', 'rank-math-pro' ),
'SI' => __( 'Slovenia', 'rank-math-pro' ),
'SB' => __( 'Solomon Islands', 'rank-math-pro' ),
'SO' => __( 'Somalia', 'rank-math-pro' ),
'ZA' => __( 'South Africa', 'rank-math-pro' ),
'GS' => __( 'South Georgia/Sandwich Islands', 'rank-math-pro' ),
'KR' => __( 'South Korea', 'rank-math-pro' ),
'SS' => __( 'South Sudan', 'rank-math-pro' ),
'ES' => __( 'Spain', 'rank-math-pro' ),
'LK' => __( 'Sri Lanka', 'rank-math-pro' ),
'SD' => __( 'Sudan', 'rank-math-pro' ),
'SR' => __( 'Suriname', 'rank-math-pro' ),
'SJ' => __( 'Svalbard and Jan Mayen', 'rank-math-pro' ),
'SZ' => __( 'Swaziland', 'rank-math-pro' ),
'SE' => __( 'Sweden', 'rank-math-pro' ),
'CH' => __( 'Switzerland', 'rank-math-pro' ),
'SY' => __( 'Syria', 'rank-math-pro' ),
'TW' => __( 'Taiwan', 'rank-math-pro' ),
'TJ' => __( 'Tajikistan', 'rank-math-pro' ),
'TZ' => __( 'Tanzania', 'rank-math-pro' ),
'TH' => __( 'Thailand', 'rank-math-pro' ),
'TL' => __( 'Timor-Leste', 'rank-math-pro' ),
'TG' => __( 'Togo', 'rank-math-pro' ),
'TK' => __( 'Tokelau', 'rank-math-pro' ),
'TO' => __( 'Tonga', 'rank-math-pro' ),
'TT' => __( 'Trinidad and Tobago', 'rank-math-pro' ),
'TN' => __( 'Tunisia', 'rank-math-pro' ),
'TR' => __( 'Turkey', 'rank-math-pro' ),
'TM' => __( 'Turkmenistan', 'rank-math-pro' ),
'TC' => __( 'Turks and Caicos Islands', 'rank-math-pro' ),
'TV' => __( 'Tuvalu', 'rank-math-pro' ),
'UG' => __( 'Uganda', 'rank-math-pro' ),
'UA' => __( 'Ukraine', 'rank-math-pro' ),
'AE' => __( 'United Arab Emirates', 'rank-math-pro' ),
'GB' => __( 'United Kingdom (UK)', 'rank-math-pro' ),
'US' => __( 'United States (US)', 'rank-math-pro' ),
'UY' => __( 'Uruguay', 'rank-math-pro' ),
'UZ' => __( 'Uzbekistan', 'rank-math-pro' ),
'VU' => __( 'Vanuatu', 'rank-math-pro' ),
'VA' => __( 'Vatican', 'rank-math-pro' ),
'VE' => __( 'Venezuela', 'rank-math-pro' ),
'VN' => __( 'Vietnam', 'rank-math-pro' ),
'WF' => __( 'Wallis and Futuna', 'rank-math-pro' ),
'EH' => __( 'Western Sahara', 'rank-math-pro' ),
'WS' => __( 'Western Samoa', 'rank-math-pro' ),
'YE' => __( 'Yemen', 'rank-math-pro' ),
'ZM' => __( 'Zambia', 'rank-math-pro' ),
'ZW' => __( 'Zimbabwe', 'rank-math-pro' ),
];
$cmb->add_field(
[
'id' => 'knowledgegraph_type',
'type' => 'radio_inline',
'name' => esc_html__( 'Person or Company', 'rank-math-pro' ),
'options' => [
'person' => esc_html__( 'Person', 'rank-math-pro' ),
'company' => esc_html__( 'Organization', 'rank-math-pro' ),
],
'desc' => esc_html__( 'Choose whether the site represents a person or an organization.', 'rank-math-pro' ),
'default' => 'person',
]
);
$cmb->add_field(
[
'id' => 'website_name',
'type' => 'text',
'name' => esc_html__( 'Website Name', 'rank-math-pro' ),
'desc' => esc_html__( 'Enter the name of your site to appear in search results.', 'rank-math-pro' ),
'default' => get_bloginfo( 'name' ),
]
);
$cmb->add_field(
[
'id' => 'website_alternate_name',
'type' => 'text',
'name' => esc_html__( 'Website Alternate Name', 'rank-math-pro' ),
'desc' => esc_html__( 'An alternate version of your site name (for example, an acronym or shorter name).', 'rank-math-pro' ),
]
);
$cmb->add_field(
[
'id' => 'knowledgegraph_name',
'type' => 'text',
'name' => esc_html__( 'Person/Organization Name', 'rank-math-pro' ),
'desc' => esc_html__( 'Your name or company name intended to feature in Google\'s Knowledge Panel.', 'rank-math-pro' ),
'default' => get_bloginfo( 'name' ),
]
);
$cmb->add_field(
[
'id' => 'knowledgegraph_logo',
'type' => 'file',
'name' => esc_html__( 'Logo', 'rank-math-pro' ),
'desc' => __( '<strong>Min Size: 112Χ112px</strong>.<br /> A squared image is preferred by the search engines.', 'rank-math-pro' ),
'options' => [ 'url' => false ],
]
);
$cmb->add_field(
[
'id' => 'url',
'type' => 'text_url',
'name' => esc_html__( 'URL', 'rank-math-pro' ),
'desc' => esc_html__( 'URL of the item.', 'rank-math-pro' ),
'default' => home_url(),
]
);
$cmb->add_field(
[
'id' => 'use_multiple_locations',
'type' => 'toggle',
'name' => esc_html__( 'Use Multiple Locations', 'rank-math-pro' ),
'desc' => esc_html__( 'Once you save the changes, we will create a new custom post type called "Locations" where you can add multiple locations of your business/organization.', 'rank-math-pro' ),
'options' => [
'off' => esc_html__( 'Default', 'rank-math-pro' ),
'on' => esc_html__( 'Custom', 'rank-math-pro' ),
],
'default' => 'off',
'dep' => $company,
]
);
$cmb->add_field(
[
'id' => 'email',
'type' => 'text',
'name' => esc_html__( 'Email', 'rank-math-pro' ),
'desc' => esc_html__( 'Search engines display your email address.', 'rank-math-pro' ),
]
);
$cmb->add_field(
[
'id' => 'phone',
'type' => 'text',
'name' => esc_html__( 'Phone', 'rank-math-pro' ),
'desc' => esc_html__( 'Search engines may prominently display your contact phone number for mobile users.', 'rank-math-pro' ),
'dep' => $person,
]
);
$cmb->add_field(
[
'id' => 'local_address',
'type' => 'address',
'name' => esc_html__( 'Address', 'rank-math-pro' ),
'dep' => [ [ 'use_multiple_locations', 'on', '!=' ] ],
]
);
$cmb->add_field(
[
'id' => 'local_address_format',
'type' => 'textarea_small',
'name' => esc_html__( 'Address Format', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'Format used when the address is displayed using the <code>[rank_math_contact_info]</code> shortcode.<br><strong>Available Tags: {address}, {locality}, {region}, {postalcode}, {country}, {gps}</strong>', 'rank-math-pro' ) ),
'default' => '{address} {locality}, {region} {postalcode}',
'classes' => 'rank-math-address-format',
'attributes' => [
'rows' => 2,
'placeholder' => '{address} {locality}, {region} {country}. {postalcode}.',
],
'dep' => $company,
]
);
$cmb->add_field(
[
'id' => 'local_business_type',
'type' => 'select',
'name' => esc_html__( 'Business Type', 'rank-math-pro' ),
'options' => Helper::choices_business_types( true ),
'attributes' => ( 'data-s2' ),
'dep' => $hide_on_multiple_locations,
]
);
$opening_hours = $cmb->add_field(
[
'id' => 'opening_hours',
'type' => 'group',
'name' => esc_html__( 'Opening Hours', 'rank-math-pro' ),
'desc' => esc_html__( 'Select opening hours. You can add multiple sets if you have different opening or closing hours on some days or if you have a mid-day break. Times are specified using 24:00 time.', 'rank-math-pro' ),
'options' => [
'add_button' => esc_html__( 'Add time', 'rank-math-pro' ),
'remove_button' => esc_html__( 'Remove', 'rank-math-pro' ),
],
'dep' => $hide_on_multiple_locations,
'classes' => 'cmb-group-text-only',
]
);
$cmb->add_group_field(
$opening_hours,
[
'id' => 'day',
'type' => 'select',
'options' => [
'Monday' => esc_html__( 'Monday', 'rank-math-pro' ),
'Tuesday' => esc_html__( 'Tuesday', 'rank-math-pro' ),
'Wednesday' => esc_html__( 'Wednesday', 'rank-math-pro' ),
'Thursday' => esc_html__( 'Thursday', 'rank-math-pro' ),
'Friday' => esc_html__( 'Friday', 'rank-math-pro' ),
'Saturday' => esc_html__( 'Saturday', 'rank-math-pro' ),
'Sunday' => esc_html__( 'Sunday', 'rank-math-pro' ),
],
]
);
$cmb->add_group_field(
$opening_hours,
[
'id' => 'time',
'type' => 'text',
'attributes' => [ 'placeholder' => esc_html__( 'e.g. 09:00-17:00', 'rank-math-pro' ) ],
]
);
$cmb->add_field(
[
'id' => 'opening_hours_format',
'type' => 'switch',
'name' => esc_html__( 'Opening Hours Format', 'rank-math-pro' ),
'options' => [
'off' => '24:00',
'on' => '12:00',
],
'desc' => esc_html__( 'Time format used in the contact shortcode.', 'rank-math-pro' ),
'default' => 'off',
'dep' => $company,
]
);
$phones = $cmb->add_field(
[
'id' => 'phone_numbers',
'type' => 'group',
'name' => esc_html__( 'Phone Number', 'rank-math-pro' ),
'desc' => esc_html__( 'Search engines may prominently display your contact phone number for mobile users.', 'rank-math-pro' ),
'options' => [
'add_button' => esc_html__( 'Add number', 'rank-math-pro' ),
'remove_button' => esc_html__( 'Remove', 'rank-math-pro' ),
],
'dep' => $hide_on_multiple_locations,
'classes' => 'cmb-group-text-only',
]
);
$cmb->add_group_field(
$phones,
[
'id' => 'type',
'type' => 'select',
'options' => Helper::choices_phone_types(),
'default' => 'customer_support',
]
);
$cmb->add_group_field(
$phones,
[
'id' => 'number',
'type' => 'text',
'attributes' => [ 'placeholder' => esc_html__( 'Format: +1-401-555-1212', 'rank-math-pro' ) ],
]
);
$cmb->add_field(
[
'id' => 'price_range',
'type' => 'text',
'name' => esc_html__( 'Price Range', 'rank-math-pro' ),
'desc' => esc_html__( 'The price range of the business, for example $$$.', 'rank-math-pro' ),
'dep' => $hide_on_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'hide_opening_hours',
'type' => 'switch',
'name' => esc_html__( 'Hide Opening Hours', 'rank-math-pro' ),
'desc' => esc_html__( 'Don\'t add opening hours data in Schema', 'rank-math-pro' ),
'options' => [
'off' => esc_html__( 'No', 'rank-math-pro' ),
'on' => esc_html__( 'Yes', 'rank-math-pro' ),
],
'default' => 'off',
'dep' => $use_multiple_locations,
]
);
$hide_opening_hours = [ 'relation' => 'and' ] + $use_multiple_locations;
$hide_opening_hours[] = [ 'hide_opening_hours', 'on', '!=' ];
$cmb->add_field(
[
'id' => 'closed_label',
'type' => 'text',
'name' => esc_html__( 'Closed label', 'rank-math-pro' ),
'desc' => esc_html__( 'Text to show in Opening hours when business is closed.', 'rank-math-pro' ),
'default' => 'Closed',
'dep' => $hide_opening_hours,
]
);
$cmb->add_field(
[
'id' => 'open_24_7',
'type' => 'text',
'name' => esc_html__( 'Open 24/7 label', 'rank-math-pro' ),
'desc' => esc_html__( 'Select the text to display alongside your opening hours when your store is open 24/7.', 'rank-math-pro' ),
'default' => 'Open 24/7',
'dep' => $hide_opening_hours,
]
);
$cmb->add_field(
[
'id' => 'open_24h',
'type' => 'text',
'name' => esc_html__( 'Open 24h label', 'rank-math-pro' ),
'default' => 'Open 24h',
'dep' => $hide_opening_hours,
]
);
$cmb->add_field(
[
'id' => 'map_unit',
'type' => 'select',
'name' => esc_html__( 'Measurement system', 'rank-math-pro' ),
'desc' => esc_html__( 'Select your preferred measurement system (miles or kilometers).', 'rank-math-pro' ),
'options' => [
'kilometers' => esc_html__( 'Kilometers', 'rank-math-pro' ),
'miles' => esc_html__( 'Miles', 'rank-math-pro' ),
],
'default' => 'kilometers',
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'map_style',
'type' => 'select',
'name' => esc_html__( 'Map Style', 'rank-math-pro' ),
'desc' => esc_html__( 'Select the map style you wish to use on the frontend of your website.', 'rank-math-pro' ),
'options' => [
'hybrid' => esc_html__( 'Hybrid', 'rank-math-pro' ),
'satellite' => esc_html__( 'Satellite', 'rank-math-pro' ),
'roadmap' => esc_html__( 'Roadmap', 'rank-math-pro' ),
'terrain' => esc_html__( 'Terrain', 'rank-math-pro' ),
],
'default' => 'hybrid',
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'limit_results',
'type' => 'text',
'name' => esc_html__( 'Maximum number of locations to show', 'rank-math-pro' ),
'desc' => esc_html__( 'Limit the number of locations shown on your website to those nearest your user.', 'rank-math-pro' ),
'default' => 10,
'dep' => $use_multiple_locations,
'attributes' => [
'type' => 'number',
'pattern' => '\d*',
'class' => 'small-text',
],
'sanitization_cb' => 'absint',
'escape_cb' => 'absint',
]
);
$cmb->add_field(
[
'id' => 'primary_country',
'type' => 'select',
'options' => $countries,
'name' => esc_html__( 'Primary Country', 'rank-math-pro' ),
'desc' => esc_html__( 'Select your organizations primary country of operation. This helps improve the accuracy of the store locator.', 'rank-math-pro' ),
'attributes' => ( 'data-s2' ),
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'route_label',
'type' => 'text',
'name' => esc_html__( 'Show Route label', 'rank-math-pro' ),
'desc' => esc_html__( 'Customize the label of the option users can use can click to get directions to your business location on the frontend.', 'rank-math-pro' ),
'default' => 'My Route',
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'enable_location_detection',
'type' => 'toggle',
'name' => esc_html__( 'Location Detection', 'rank-math-pro' ),
'desc' => esc_html__( 'Automatically detect the user\'s location as the starting point.', 'rank-math-pro' ),
'options' => [
'off' => esc_html__( 'Default', 'rank-math-pro' ),
'on' => esc_html__( 'Custom', 'rank-math-pro' ),
],
'default' => 'off',
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'same_organization_locations',
'type' => 'toggle',
'name' => esc_html__( 'All Locations are part of the same Organization', 'rank-math-pro' ),
'desc' => esc_html__( 'Enable if all of the locations where you serve customers are a part of the same legal entity.', 'rank-math-pro' ),
'options' => [
'off' => esc_html__( 'Default', 'rank-math-pro' ),
'on' => esc_html__( 'Custom', 'rank-math-pro' ),
],
'default' => 'off',
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'locations_enhanced_search',
'type' => 'toggle',
'name' => esc_html__( 'Enhanced Search', 'rank-math-pro' ),
'desc' => esc_html__( 'Include business locations in site-wide search results.', 'rank-math-pro' ),
'options' => [
'off' => esc_html__( 'Default', 'rank-math-pro' ),
'on' => esc_html__( 'Custom', 'rank-math-pro' ),
],
'default' => 'off',
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'maps_api_key',
'type' => 'text',
'name' => esc_html__( 'Google Maps API Key', 'rank-math-pro' ),
/* translators: %s expands to "Google Maps Embed API" https://developers.google.com/maps/documentation/embed/ */
'desc' => sprintf( esc_html__( 'An API Key is required to display embedded Google Maps on your site. Get it here: %s', 'rank-math-pro' ), '<a href="https://developers.google.com/maps/documentation/javascript/get-api-key" target="_blank">' . __( 'Google Maps Embed API', 'rank-math-pro' ) . '</a>' ),
'dep' => $company,
'attributes' => [ 'type' => 'password' ],
]
);
$cmb->add_field(
[
'id' => 'geo',
'type' => 'text',
'name' => esc_html__( 'Geo Coordinates', 'rank-math-pro' ),
'desc' => esc_html__( 'Latitude and longitude values separated by comma.', 'rank-math-pro' ),
'dep' => $company,
]
);
$cmb->add_field(
[
'id' => 'locations_post_type_base',
'type' => 'text',
'name' => esc_html__( 'Locations Post Type Base', 'rank-math-pro' ),
'default' => 'locations',
'desc' => '<code>' . home_url( 'locations/africa/' ) . '</code>',
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'locations_category_base',
'type' => 'text',
'name' => esc_html__( 'Locations Category Base', 'rank-math-pro' ),
'default' => 'locations-category',
'desc' => '<code>' . home_url( 'locations-category/regional-offices/' ) . '</code>',
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'locations_post_type_label',
'type' => 'text',
'name' => esc_html__( 'Location Post Type Name', 'rank-math-pro' ),
'default' => 'RM Locations',
'desc' => esc_html__( 'The label that appears in the sidebar for the custom post type where you can add & manage locations.', 'rank-math-pro' ),
'dep' => $use_multiple_locations,
]
);
$cmb->add_field(
[
'id' => 'locations_post_type_plural_label',
'type' => 'text',
'name' => esc_html__( 'Locations Post Type Name (Plural)', 'rank-math-pro' ),
'default' => 'RM Locations',
'desc' => esc_html__( 'As above, but the label that would be applicable for more than one location (default: RM Locations).', 'rank-math-pro' ),
'dep' => $use_multiple_locations,
]
);
$about_page = Helper::get_settings( 'titles.local_seo_about_page' );
$about_options = [ '' => __( 'Select Page', 'rank-math-pro' ) ];
if ( $about_page ) {
$about_options[ $about_page ] = get_the_title( $about_page );
}
$cmb->add_field(
[
'id' => 'local_seo_about_page',
'type' => 'select',
'options' => $about_options,
'name' => esc_html__( 'About Page', 'rank-math-pro' ),
'desc' => esc_html__( 'Select a page on your site where you want to show the LocalBusiness meta data.', 'rank-math-pro' ),
'attributes' => ( 'data-s2-pages' ),
]
);
$contact_page = Helper::get_settings( 'titles.local_seo_contact_page' );
$contact_options = [ '' => __( 'Select Page', 'rank-math-pro' ) ];
if ( $contact_page ) {
$contact_options[ $contact_page ] = get_the_title( $contact_page );
}
$cmb->add_field(
[
'id' => 'local_seo_contact_page',
'type' => 'select',
'options' => $contact_options,
'name' => esc_html__( 'Contact Page', 'rank-math-pro' ),
'desc' => esc_html__( 'Select a page on your site where you want to show the LocalBusiness meta data.', 'rank-math-pro' ),
'attributes' => ( 'data-s2-pages' ),
]
);

View File

@@ -0,0 +1 @@
(()=>{"use strict";const e=wp.hooks,o=wp.i18n,n=wp.element,t=wp.compose,a=wp.data,r=wp.components;const l=(0,t.compose)((0,a.withSelect)((function(e){return{robots:e("rank-math-pro").getNewsRobots()}})),(0,a.withDispatch)((function(e){return{updateNewsRobots:function(o){e("rank-math-pro").updateNewsRobots(o)}}})))((function(e){return wp.element.createElement(n.Fragment,null,wp.element.createElement(r.PanelBody,{initialOpen:!0},wp.element.createElement(r.RadioControl,{label:(0,o.__)("Googlebot-News index","rank-math-pro"),help:(0,o.__)('Using an "Index" or "NoIndex" option allows you to control what Google News Bot (not to be confused with Google Search Bot) can include or not include in the Google News Index.',"rank-math-pro"),selected:e.robots,options:[{value:"index",label:(0,o.__)("Index","rank-math-pro")},{value:"noindex",label:(0,o.__)("No Index","rank-math-pro")}],onChange:e.updateNewsRobots})))}));(0,e.addAction)("rank_math_loaded","rank-math-pro",(function(){(0,e.addFilter)("rankMath.advanced.newsSitemap","rank-math-pro",(function(){return l}))}))})();

View File

@@ -0,0 +1,118 @@
<?php
/**
* The News Sitemap Metabox.
*
* @since 1.0.0
* @package RankMath
* @subpackage RankMathPro
* @author MyThemeShop <admin@mythemeshop.com>
*/
namespace RankMathPro\Sitemap;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Admin\Admin_Helper;
use RankMath\Sitemap\Cache_Watcher;
use MyThemeShop\Helpers\Param;
use MyThemeShop\Helpers\WordPress;
defined( 'ABSPATH' ) || exit;
/**
* News_Metabox class.
*/
class News_Metabox {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
if ( ! Helper::has_cap( 'sitemap' ) ) {
return;
}
$this->action( 'save_post', 'save_post' );
$this->action( 'rank_math/admin/editor_scripts', 'enqueue_news_sitemap', 11 );
$this->filter( 'rank_math/metabox/post/values', 'add_metadata', 10, 2 );
}
/**
* Enqueue scripts for the metabox.
*/
public function enqueue_news_sitemap() {
if ( ! $this->can_add_tab() ) {
return;
}
wp_enqueue_script(
'rank-math-pro-news',
RANK_MATH_PRO_URL . 'includes/modules/news-sitemap/assets/js/news-sitemap.js',
[ 'rank-math-pro-editor' ],
rank_math_pro()->version,
true
);
}
/**
* Add meta data to use in gutenberg.
*
* @param array $values Aray of tabs.
* @param Screen $screen Sceen object.
*
* @return array
*/
public function add_metadata( $values, $screen ) {
$object_id = $screen->get_object_id();
$object_type = $screen->get_object_type();
$robots = $screen->get_meta( $object_type, $object_id, 'rank_math_news_sitemap_robots' );
$values['newsSitemap'] = [
'robots' => $robots ? $robots : 'index',
];
return $values;
}
/**
* Check for relevant post type before invalidation.
*
* @copyright Copyright (C) 2008-2019, Yoast BV
* The following code is a derivative work of the code from the Yoast(https://github.com/Yoast/wordpress-seo/), which is licensed under GPL v3.
*
* @param int $post_id Post ID to possibly invalidate for.
*/
public function save_post( $post_id ) {
if (
wp_is_post_revision( $post_id ) ||
! $this->can_add_tab( get_post_type( $post_id ) ) ||
false === Helper::is_post_indexable( $post_id )
) {
return false;
}
Cache_Watcher::invalidate( 'news' );
}
/**
* Show field check callback.
*
* @param string $post_type Current Post Type.
*
* @return boolean
*/
private function can_add_tab( $post_type = false ) {
if ( Admin_Helper::is_term_profile_page() || Admin_Helper::is_posts_page() ) {
return false;
}
$post_type = $post_type ? $post_type : WordPress::get_post_type();
return in_array(
$post_type,
(array) Helper::get_settings( 'sitemap.news_sitemap_post_type' ),
true
);
}
}

View File

@@ -0,0 +1,300 @@
<?php
/**
* The Sitemap Module
*
* @since 1.0.0
* @package RankMath
* @subpackage RankMathPro
* @author MyThemeShop <admin@mythemeshop.com>
*/
namespace RankMathPro\Sitemap;
use RankMath\Helper;
use RankMath\Sitemap\Router;
use RankMath\Sitemap\Providers\Post_Type;
defined( 'ABSPATH' ) || exit;
/**
* News_Provider class.
*/
class News_Provider extends Post_Type {
/**
* Indicate that this provider should show an empty sitemap instead of a 404.
*
* @var boolean
*/
public $should_show_empty = true;
/**
* Check if provider supports given item type.
*
* @param string $type Type string to check for.
* @return boolean
*/
public function handles_type( $type ) {
return 'news' === $type;
}
/**
* Get set of sitemaps index link data.
*
* @param int $max_entries Entries per sitemap.
* @return array
*/
public function get_index_links( $max_entries ) { // phpcs:ignore
global $wpdb;
$index = [];
$post_types = Helper::get_settings( 'sitemap.news_sitemap_post_type' );
$posts = $this->get_posts( $post_types, 1, 0 );
if ( empty( $posts ) ) {
return $index;
}
$item = $this->do_filter(
'sitemap/index/entry',
[
'loc' => Router::get_base_url( 'news-sitemap.xml' ),
'lastmod' => get_lastpostdate( 'gmt' ),
],
'news',
);
if ( $item ) {
$index[] = $item;
}
return $index;
}
/**
* Get set of sitemap link data.
*
* @param string $type Sitemap type.
* @param int $max_entries Entries per sitemap.
* @param int $current_page Current page of the sitemap.
* @return array
*/
public function get_sitemap_links( $type, $max_entries, $current_page ) { // phpcs:ignore
$links = [];
$post_types = Helper::get_settings( 'sitemap.news_sitemap_post_type' );
$posts = $this->get_posts( $post_types, 1000, 0 );
if ( empty( $posts ) ) {
return $links;
}
foreach ( $posts as $post ) {
if ( ! Helper::is_post_indexable( $post->ID ) ) {
continue;
}
if ( ! News_Sitemap::is_post_indexable( $post->ID ) ) {
continue;
}
$url = $this->get_url( $post );
if ( ! isset( $url['loc'] ) ) {
continue;
}
/**
* Filter URL entry before it gets added to the sitemap.
*
* @param array $url Array of URL parts.
* @param string $type URL type.
* @param object $user Data object for the URL.
*/
$url = $this->do_filter( 'sitemap/entry', $url, 'post', $post );
if ( empty( $url ) ) {
continue;
}
if ( (int) $post->ID === $this->get_page_for_posts_id() || (int) $post->ID === $this->get_page_on_front_id() ) {
array_unshift( $links, $url );
continue;
}
$links[] = $url;
}
return $links;
}
/**
* Retrieve set of posts with optimized query routine.
*
* @param array $post_types Post type to retrieve.
* @param int $count Count of posts to retrieve.
* @param int $offset Starting offset.
*
* @return object[]
*/
protected function get_posts( $post_types, $count, $offset ) { // phpcs:ignore
global $wpdb;
if ( empty( $post_types ) ) {
return [];
}
if ( ! is_array( $post_types ) ) {
$post_types = [ $post_types ];
}
/**
* Get posts for the last two days only.
*/
$sql = "
SELECT *
FROM {$wpdb->posts}
WHERE post_status='publish'
AND ( TIMESTAMPDIFF( MINUTE, post_date_gmt, UTC_TIMESTAMP() ) <= ( 48 * 60 ) )
AND post_type IN ( '" . join( "', '", esc_sql( $post_types ) ) . "' )
";
$terms_query = '';
foreach ( $post_types as $post_type ) {
$terms = current( Helper::get_settings( "sitemap.news_sitemap_exclude_{$post_type}_terms", [] ) );
if ( empty( $terms ) ) {
continue;
}
array_map(
function ( $key ) use ( $terms, &$terms_query, $wpdb ) {
$placeholders = implode( ', ', array_fill( 0, count( $terms[ $key ] ), '%d' ) );
$terms_sql = "
{$wpdb->posts}.ID NOT IN (
SELECT object_id
FROM {$wpdb->term_relationships}
WHERE term_taxonomy_id IN ($placeholders)
)
AND";
$terms_query .= $wpdb->prepare( $terms_sql, $terms[ $key ] ); // phpcs:ignore
},
array_keys( $terms )
);
}
// Remove the last AND, no way to tell the last items.
if ( $terms_query ) {
$terms_query = preg_replace( '/(AND)$/i', '', $terms_query );
$terms_query = "( {$terms_query} )";
$sql .= 'AND ' . $terms_query;
}
$sql .= "
AND post_password = ''
ORDER BY post_date_gmt DESC
LIMIT 0, %d
";
$count = max( 1, min( 1000, $count ) );
return $wpdb->get_results( $wpdb->prepare( $sql, $count ) ); // phpcs:ignore
}
/**
* Produce array of URL parts for given post object.
*
* @param object $post Post object to get URL parts for.
* @return array|boolean
*/
protected function get_url( $post ) {
$url = [];
/**
* Filter the URL Rank Math SEO uses in the XML sitemap.
*
* Note that only absolute local URLs are allowed as the check after this removes external URLs.
*
* @param string $url URL to use in the XML sitemap
* @param object $post Post object for the URL.
*/
$url['loc'] = $this->do_filter( 'sitemap/xml_post_url', get_permalink( $post ), $post );
/**
* Do not include external URLs.
*
* @see https://wordpress.org/plugins/page-links-to/ can rewrite permalinks to external URLs.
*/
if ( 'external' === $this->get_classifier()->classify( $url['loc'] ) ) {
return false;
}
$canonical = Helper::get_post_meta( 'canonical', $post->ID );
if ( '' !== $canonical && $canonical !== $url['loc'] ) {
/*
* Let's assume that if a canonical is set for this page and it's different from
* the URL of this post, that page is either already in the XML sitemap OR is on
* an external site, either way, we shouldn't include it here.
*/
return false;
}
rank_math()->variables->setup();
$url['title'] = $this->get_title( $post );
$url['publication_date'] = $post->post_date_gmt;
return $url;
}
/**
* Get Post Title.
*
* @param WP_Post $post Post Object.
*
* @return string
*/
private function get_title( $post ) {
$title = Helper::get_post_meta( 'title', $post->ID );
return $title ? $title : $post->post_title;
}
/**
* Filter Posts by excluded terms.
*
* @param object $posts News Sitemap Posts.
*
* @return object[]
*/
private function filter_posts_by_terms( $posts ) {
if ( empty( $posts ) ) {
return $posts;
}
foreach ( $posts as $key => $post ) {
$exclude_terms = current( Helper::get_settings( "sitemap.news_sitemap_exclude_{$post->post_type}_terms", [] ) );
if ( empty( $exclude_terms ) ) {
continue;
}
$exclude_terms = array_merge( ...array_values( $exclude_terms ) );
$taxonomies = get_object_taxonomies( $post->post_type, 'names' );
if ( empty( $taxonomies ) ) {
continue;
}
$terms = wp_get_object_terms(
$post->ID,
$taxonomies,
[
'fields' => 'ids',
]
);
if ( ! empty( array_intersect( $terms, $exclude_terms ) ) ) {
unset( $posts[ $key ] );
}
}
return $posts;
}
}

View File

@@ -0,0 +1,323 @@
<?php
/**
* The News Sitemap Module
*
* @since 1.0.0
* @package RankMath
* @subpackage RankMathPro
* @author MyThemeShop <admin@mythemeshop.com>
*/
namespace RankMathPro\Sitemap;
use RankMath\KB;
use RankMath\Helper;
use RankMath\Helpers\Locale;
use RankMath\Sitemap\Cache_Watcher;
use RankMath\Traits\Hooker;
use RankMath\Sitemap\Router;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* News_Sitemap class.
*/
class News_Sitemap {
use Hooker;
/**
* NEWS Publication.
*
* @var string
*/
protected $news_publication = null;
/**
* The Constructor.
*/
public function __construct() {
if ( is_admin() ) {
$this->filter( 'rank_math/settings/sitemap', 'add_settings', 11 );
}
new News_Metabox();
$this->action( 'rank_math/head', 'robots', 10 );
$this->filter( 'rank_math/sitemap/providers', 'add_provider' );
$this->filter( 'rank_math/sitemap/news_urlset', 'xml_urlset' );
$this->filter( 'rank_math/sitemap/xsl_news', 'sitemap_xsl' );
$this->filter( 'rank_math/sitemap/news_stylesheet_url', 'stylesheet_url' );
$this->filter( 'rank_math/sitemap/news_sitemap_url', 'sitemap_url', 10, 2 );
$this->filter( 'rank_math/schema/default_type', 'change_default_schema_type', 10, 3 );
$this->filter( 'rank_math/snippet/rich_snippet_article_entity', 'add_copyrights_data' );
$this->action( 'admin_post_rank-math-options-sitemap', 'save_exclude_terms_data', 9 );
$this->action( 'transition_post_status', 'status_transition', 10, 3 );
}
/**
* Function to pass empty array to exclude terms data when term is not selected for a Post type.
* This code is needed to save empty group value since CMB2 doesn't allow it.
*
* @since 2.8.1
* @return void
*/
public function save_exclude_terms_data() {
$post_types = Helper::get_settings( 'sitemap.news_sitemap_post_type', [] );
if ( empty( $post_types ) ) {
return;
}
foreach ( $post_types as $post_type ) {
if ( ! isset( $_POST["news_sitemap_exclude_{$post_type}_terms"] ) ) { //phpcs:ignore
$_POST["news_sitemap_exclude_{$post_type}_terms"] = []; //phpcs:ignore
}
}
}
/**
* Output the meta robots tag.
*/
public function robots() {
if ( ! is_singular() ) {
return;
}
$post = get_post();
/**
* Filter: 'rank_math/sitemap/news/noindex' - Allow preventing of outputting noindex tag.
*
* @api string $meta_robots The noindex tag.
*
* @param object $post The post.
*/
if ( ! $this->do_filter( 'sitemap/news/noindex', true, $post ) || self::is_post_indexable( $post->ID ) ) {
return;
}
echo '<meta name="Googlebot-News" content="noindex" />' . "\n";
}
/**
* Check if post is indexable.
*
* @param int $post_id Post ID to check.
*
* @return boolean
*/
public static function is_post_indexable( $post_id ) {
$robots = get_post_meta( $post_id, 'rank_math_news_sitemap_robots', true );
if ( ! empty( $robots ) && 'noindex' === $robots ) {
return false;
}
return true;
}
/**
* Add module settings into general optional panel.
*
* @param array $tabs Array of option panel tabs.
*
* @return array
*/
public function add_settings( $tabs ) {
$sitemap_url = Router::get_base_url( 'news-sitemap.xml' );
$tabs['news-sitemap'] = [
'icon' => 'fa fa-newspaper-o',
'title' => esc_html__( 'News Sitemap', 'rank-math-pro' ),
'icon' => 'rm-icon rm-icon-post',
'desc' => wp_kses_post( sprintf( __( 'News Sitemaps allow you to control which content you submit to Google News. More information: <a href="%s" target="_blank">News Sitemaps overview</a>', 'rank-math-pro' ), KB::get( 'news-sitemap', 'Options Panel Sitemap News Tab' ) ) ),
'file' => dirname( __FILE__ ) . '/settings-news.php',
/* translators: News Sitemap Url */
'after_row' => '<div class="notice notice-alt notice-info info inline rank-math-notice"><p>' . sprintf( esc_html__( 'Your News Sitemap index can be found here: : %s', 'rank-math-pro' ), '<a href="' . $sitemap_url . '" target="_blank">' . $sitemap_url . '</a>' ) . '</p></div>',
];
return $tabs;
}
/**
* Add news sitemap provider.
*
* @param array $providers Sitemap provider registry.
*/
public function add_provider( $providers ) {
$providers[] = new \RankMathPro\Sitemap\News_Provider();
return $providers;
}
/**
* Produce XML output for google news urlset.
*
* @return string
*/
public function xml_urlset() {
return '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
. 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd '
. 'http://www.google.com/schemas/sitemap-news/0.9 http://www.google.com/schemas/sitemap-news/0.9/sitemap-news.xsd" '
. 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" '
. 'xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">' . "\n";
}
/**
* Stylesheet Url for google news.
*
* @param string $url Current stylesheet url.
* @return string
*/
public function stylesheet_url( $url ) { // phpcs:ignore
$stylesheet_url = preg_replace( '/(^http[s]?:)/', '', Router::get_base_url( 'news-sitemap.xsl' ) );
return '<?xml-stylesheet type="text/xsl" href="' . $stylesheet_url . '"?>';
}
/**
* Stylesheet for google news.
*
* @param string $title Title for stylesheet.
*/
public function sitemap_xsl( $title ) { // phpcs:ignore
require_once 'sitemap-xsl.php';
}
/**
* Build the `<url>` tag for a given URL.
*
* @param array $url Array of parts that make up this entry.
* @param Renderer $renderer Sitemap renderer class object.
* @return string
*/
public function sitemap_url( $url, $renderer ) {
$date = null;
if ( ! empty( $url['publication_date'] ) ) {
// Create a DateTime object date in the correct timezone.
$date = $renderer->timezone->format_date( $url['publication_date'] );
}
$output = $renderer->newline( '<url>', 1 );
$output .= $renderer->newline( '<loc>' . $renderer->encode_url_rfc3986( htmlspecialchars( $url['loc'] ) ) . '</loc>', 2 );
$output .= $renderer->newline( '<news:news>', 2 );
$output .= $this->get_news_publication( $renderer );
$output .= empty( $date ) ? '' : $renderer->newline( '<news:publication_date>' . htmlspecialchars( $date ) . '</news:publication_date>', 3 );
$output .= $renderer->add_cdata( $url['title'], 'news:title', 3 );
$output .= $renderer->newline( '</news:news>', 2 );
$output .= $renderer->newline( '</url>', 1 );
/**
* Filters the output for the sitemap url tag.
*
* @param string $output The output for the sitemap url tag.
* @param array $url The sitemap url array on which the output is based.
*/
return $this->do_filter( 'sitemap_url', $output, $url );
}
/**
* Get News Pub Tags.
*
* @param Renderer $renderer Sitemap renderer class object.
* @return string
*/
private function get_news_publication( $renderer ) {
if ( is_null( $this->news_publication ) ) {
$lang = Locale::get_site_language();
$name = Helper::get_settings( 'sitemap.news_sitemap_publication_name' );
$name = $name ? $name : get_bloginfo( 'name' );
$this->news_publication = '';
$this->news_publication .= $renderer->newline( '<news:publication>', 3 );
$this->news_publication .= $renderer->newline( '<news:name>' . esc_html( $name ) . '</news:name>', 4 );
$this->news_publication .= $renderer->newline( '<news:language>' . $lang . '</news:language>', 4 );
$this->news_publication .= $renderer->newline( '</news:publication>', 3 );
}
return $this->news_publication;
}
/**
* Change default schema type on News Posts.
*
* @param string $schema Default schema type.
* @param string $post_type Current Post Type.
* @param int $post_id Current Post ID.
*
* @return string
*/
public function change_default_schema_type( $schema, $post_type, $post_id ) {
$news_post_types = (array) Helper::get_settings( 'sitemap.news_sitemap_post_type' );
if ( ! in_array( $post_type, $news_post_types, true ) ) {
return $schema;
}
$exclude_terms = (array) Helper::get_settings( "sitemap.news_sitemap_exclude_{$post_type}_terms" );
if ( empty( $exclude_terms[0] ) ) {
return 'NewsArticle';
}
$has_excluded_term = false;
foreach ( $exclude_terms[0] as $taxonomy => $terms ) {
if ( has_term( $terms, $taxonomy, $post_id ) ) {
$has_excluded_term = true;
break;
}
}
return $has_excluded_term ? $schema : 'NewsArticle';
}
/**
* Filter to add Copyrights data in Article Schema on News Posts.
*
* @param array $entity Snippet Data.
* @return array
*/
public function add_copyrights_data( $entity ) {
global $post;
if ( is_null( $post ) ) {
return $entity;
}
$news_post_types = (array) Helper::get_settings( 'sitemap.news_sitemap_post_type' );
if ( ! in_array( $post->post_type, $news_post_types, true ) ) {
return $entity;
}
$entity['copyrightYear'] = get_the_modified_date( 'Y', $post );
if ( ! empty( $entity['publisher'] ) ) {
$entity['copyrightHolder'] = $entity['publisher'];
}
return $entity;
}
/**
* Invalidate News Sitemap cache when a scheduled post is published.
*
* @param string $new_status New Status.
* @param string $old_status Old Status.
* @param object $post Post Object.
*/
public function status_transition( $new_status, $old_status, $post ) {
if ( $old_status === $new_status || 'publish' !== $new_status ) {
return;
}
$news_post_types = (array) Helper::get_settings( 'sitemap.news_sitemap_post_type', [] );
if ( ! in_array( $post->post_type, $news_post_types, true ) ) {
return;
}
if ( false === Helper::is_post_indexable( $post->ID ) ) {
return;
}
Cache_Watcher::invalidate( 'news' );
}
}

View File

@@ -0,0 +1,97 @@
<?php
/**
* Sitemap - News
*
* @since 1.0.0
* @package RankMath
* @subpackage RankMathPro
* @author MyThemeShop <admin@mythemeshop.com>
*/
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
$cmb->add_field(
[
'id' => 'news_sitemap_publication_name',
'type' => 'text',
'name' => esc_html__( 'Google News Publication Name', 'rank-math-pro' ),
'desc' => wp_kses_post( __( 'The name of the news publication. It must match the name exactly as it appears on your articles in news.google.com, omitting any trailing parentheticals. <a href="https://support.google.com/news/publisher-center/answer/9606710" target="_blank">More information at support.google.com</a>', 'rank-math-pro' ) ),
]
);
$post_types = Helper::choices_post_types();
if ( isset( $post_types['attachment'] ) && Helper::get_settings( 'general.attachment_redirect_urls', true ) ) {
unset( $post_types['attachment'] );
}
$cmb->add_field(
[
'id' => 'news_sitemap_post_type',
'type' => 'multicheck_inline',
'name' => esc_html__( 'News Post Type', 'rank-math-pro' ),
'desc' => esc_html__( 'Select the post type you use for News articles.', 'rank-math-pro' ),
'options' => $post_types,
]
);
$post_types = Helper::get_settings( 'sitemap.news_sitemap_post_type', [] );
if ( empty( $post_types ) ) {
return;
}
foreach ( $post_types as $post_type ) {
$taxonomies = Helper::get_object_taxonomies( $post_type, 'objects' );
if ( empty( $taxonomies ) ) {
continue;
}
$post_type_obj = get_post_type_object( $post_type );
$post_type_label = $post_type_obj->labels->singular_name;
$group_field_id = '';
foreach ( $taxonomies as $taxonomy => $data ) {
if ( empty( $data->show_ui ) ) {
continue;
}
$terms = get_terms(
[
'taxonomy' => $taxonomy,
'show_ui' => true,
'fields' => 'id=>name',
'update_term_meta_cache' => false,
]
);
if ( 0 === count( $terms ) ) {
continue;
}
if ( ! $group_field_id ) {
$group_field_id = $cmb->add_field(
[
'id' => "news_sitemap_exclude_{$post_type}_terms",
'type' => 'group',
/* translators: Post Type */
'name' => sprintf( __( 'Exclude %s Terms ', 'rank-math-pro' ), $post_type_label ),
'classes' => 'news-sitemap-exclude-terms cmb-group-text-only',
'repeatable' => false,
]
);
}
$cmb->add_group_field(
$group_field_id,
[
'name' => '',
'id' => $taxonomy,
'type' => 'multicheck_inline',
'options' => $terms,
'classes' => 'cmb-field-list',
/* translators: 1. Taxonomy Name 2. Post Type */
'desc' => sprintf( esc_html__( '%1$s to exclude for %2$s.', 'rank-math-pro' ), $data->label, $post_type_label ),
]
);
}
}

View File

@@ -0,0 +1,184 @@
<?php
/**
* Sitemap stylesheet.
*
* @package RankMath
* @subpackage RankMath\Sitemap
*/
use RankMath\KB;
use RankMath\Sitemap\Router;
use RankMath\Sitemap\Sitemap;
defined( 'ABSPATH' ) || exit;
// Echo so opening tag doesn't get confused for PHP.
echo '<?xml version="1.0" encoding="UTF-8"?>';
?>
<xsl:stylesheet version="2.0"
xmlns:html="http://www.w3.org/TR/REC-html40"
xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><?php echo esc_html( $title ); ?></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body {
font-size: 14px;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
margin: 0;
color: #545353;
}
a {
color: #05809e;
text-decoration: none;
}
h1 {
font-size: 24px;
font-family: Verdana,Geneva,sans-serif;
font-weight: normal;
margin: 0;
}
#description {
background-color: #4275f4;
padding: 20px 40px;
color: #fff;
padding: 30px 30px 20px;
}
#description h1,
#description p,
#description a {
color: #fff;
margin: 0;
font-size: 1.1em;
}
#description h1 {
font-size: 2em;
margin-bottom: 1em;
}
#description p {
margin-top: 5px;
}
#content {
padding: 20px 30px;
background: #fff;
max-width: 75%;
margin: 0 auto;
}
table {
border: none;
border-collapse: collapse;
font-size: .9em;
width: 100%;
}
th {
background-color: #4275f4;
color: #fff;
text-align: left;
padding: 15px;
font-size: 14px;
cursor: pointer;
}
td {
padding: 10px;
border-bottom: 1px solid #ddd;
}
tbody tr:nth-child(even) {
background-color: #f7f7f7;
}
table td a {
display: block;
}
table td a img {
max-height: 30px;
margin: 6px 3px;
}
</style>
</head>
<body>
<div id="description">
<h1><?php esc_html_e( 'XML Sitemap', 'rank-math-pro' ); ?></h1>
<?php if ( false === $this->do_filter( 'sitemap/remove_credit', false ) ) : ?>
<p>
<?php
printf(
wp_kses_post(
/* translators: link to rankmath.com */
__( 'This XML Sitemap is generated by <a href="%s" target="_blank">Rank Math WordPress SEO Plugin</a>. It is what search engines like Google use to crawl and re-crawl posts/pages/products/images/archives on your website.', 'rank-math-pro' )
),
KB::get( 'seo-suite' )
);
?>
</p>
<?php endif; ?>
<p>
<?php
printf(
wp_kses_post(
/* translators: link to rankmath.com */
__( 'Learn more about <a href="%s" target="_blank">XML Sitemaps</a>.', 'rank-math-pro' )
),
'http://sitemaps.org'
);
?>
</p>
</div>
<div id="content">
<p>
<?php
printf(
/* translators: xsl value count */
__( 'This XML Sitemap contains <strong>%s</strong> URLs.', 'rank-math-pro' ),
'<xsl:value-of select="count(sitemap:urlset/sitemap:url)"/>'
);
?>
</p>
<p class="expl">
<?php
printf(
/* translators: xsl value count */
__( '<a href="%s">&#8592; Sitemap Index</a>', 'rank-math-pro' ),
esc_url( Router::get_base_url( Sitemap::get_sitemap_index_slug() . '.xml' ) )
);
?>
</p>
<table id="sitemap" cellpadding="3">
<thead>
<tr>
<th width="40%">Title</th>
<th width="15%"><?php esc_html_e( 'Publication Date', 'rank-math-pro' ); ?></th>
</tr>
</thead>
<tbody>
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:for-each select="sitemap:urlset/sitemap:url">
<tr>
<td>
<xsl:variable name="itemURL">
<xsl:value-of select="sitemap:loc"/>
</xsl:variable>
<a href="{$itemURL}">
<xsl:value-of select="news:news/news:title"/>
</a>
</td>
<td>
<xsl:value-of select="concat(substring(news:news/news:publication_date,0,11),concat(' ', substring(news:news/news:publication_date,12,8)),concat(' ', substring(news:news/news:publication_date,20,6)))"/>
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View File

@@ -0,0 +1,202 @@
<?php
/**
* Add Podcasts RSS feed.
*
* @since 3.0.17
* @package RankMath
* @subpackage RankMathPro\Schema
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Podcast;
use RankMath\Helper;
use RankMath\Traits\Hooker;
defined( 'ABSPATH' ) || exit;
/**
* Media_RSS class.
*/
class Podcast_RSS {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
$prefix = 'rss2_podcast';
if ( apply_filters( 'rank_math/podcast/enhance_all_feeds', true ) ) {
$prefix = 'rss2';
}
remove_action( 'rss2_head', 'rss2_site_icon' );
$this->action( "{$prefix}_ns", 'add_namespace' );
$this->action( "{$prefix}_head", 'add_channel_data' );
$this->action( "{$prefix}_item", 'add_podcast_data', 10, 1 );
}
/**
* Add namespace to RSS feed.
*/
public function add_namespace() {
if ( apply_filters( 'rank_math/rss/add_podcasts_namespace', true ) ) {
echo 'xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" ';
}
$this->filter( 'get_wp_title_rss', 'feed_title' );
$this->filter( 'bloginfo_rss', 'feed_description', 10, 2 );
}
/**
* Change the feed title.
*
* @param string $wp_title_rss The current blog title.
*/
public function feed_title( $wp_title_rss ) {
$podcast_title = Helper::get_settings( 'general.podcast_title' );
if ( $podcast_title ) {
return Helper::replace_vars( $podcast_title );
}
return $wp_title_rss;
}
/**
* Change the feed description.
*
* @param string $value RSS container for the blog information.
* @param string $show The type of blog information to retrieve.
*/
public function feed_description( $value, $show ) {
if ( 'description' !== $show ) {
return $value;
}
$podcast_description = Helper::get_settings( 'general.podcast_description' );
if ( $podcast_description ) {
return Helper::replace_vars( $podcast_description );
}
return $value;
}
/**
* Add Podcast channel data
*/
public function add_channel_data() {
$category = Helper::get_settings( 'general.podcast_category' );
if ( $category ) {
$this->newline( '<itunes:category text="' . esc_attr( $category ) . '" />', 1 );
}
$author_name = Helper::get_settings( 'general.podcast_owner' );
$author_email = Helper::get_settings( 'general.podcast_owner_email' );
if ( $author_email ) {
$this->newline( '<itunes:author>' . esc_html( $author_name ) . '</itunes:author>', 1 );
$this->newline( '<itunes:owner>', 1 );
if ( $author_email ) {
$this->newline( '<itunes:name>' . esc_html( $author_name ) . '</itunes:name>', 2 );
}
$this->newline( '<itunes:email>' . esc_html( $author_email ) . '</itunes:email>', 2 );
$this->newline( '</itunes:owner>', 1 );
}
$image = Helper::get_settings( 'general.podcast_image' );
if ( $image ) {
$this->newline( '<itunes:image href="' . esc_url( $image ) . '" />', 1 );
$this->newline( '<image>', 1 );
$this->newline( '<title>' . get_wp_title_rss() . '</title>', 2 );
$this->newline( '<url>' . esc_url( $image ) . '</url>', 2 );
$this->newline( '<link>' . get_bloginfo_rss( 'url' ) . '</link>', 2 );
$this->newline( '</image>', 1 );
}
$title = Helper::get_settings( 'general.podcast_title' );
if ( $title ) {
$this->newline( '<itunes:subtitle>' . esc_html( Helper::replace_vars( $title ) ) . '</itunes:subtitle>', 1 );
}
$summary = Helper::get_settings( 'general.podcast_description' );
if ( $summary ) {
$this->newline( '<itunes:summary>' . esc_html( Helper::replace_vars( $summary ) ) . '</itunes:summary>', 1 );
}
$is_explicit = Helper::get_settings( 'general.podcast_explicit' ) ? 'yes' : 'clean';
$this->newline( '<itunes:explicit>' . $is_explicit . '</itunes:explicit>', 1 );
$copyright = Helper::get_settings( 'general.podcast_copyright_text' );
if ( $copyright ) {
$copyright = str_replace( '©', '&#xA9;', $copyright );
$this->newline( '<copyright>' . esc_html( $copyright ) . '</copyright>', 1 );
}
}
/**
* Add Podcast Data in RSS feed.
*
* @see https://support.google.com/podcast-publishers/answer/9889544
* @see https://podcasters.apple.com/support/823-podcast-requirements
*/
public function add_podcast_data() {
global $post;
$podcast = get_post_meta( $post->ID, 'rank_math_schema_PodcastEpisode', true );
if ( empty( $podcast ) ) {
return;
}
$title = ! empty( $podcast['name'] ) ? Helper::replace_vars( $podcast['name'], $post ) : '';
$description = ! empty( $podcast['description'] ) ? Helper::replace_vars( $podcast['description'], $post ) : '';
$audio_file = $podcast['associatedMedia']['contentUrl'];
$duration = ! empty( $podcast['timeRequired'] ) ? Helper::duration_to_seconds( $podcast['timeRequired'] ) : '';
$image = ! empty( $podcast['thumbnailUrl'] ) ? Helper::replace_vars( $podcast['thumbnailUrl'], $post ) : '';
$author = ! empty( $podcast['author'] ) ? Helper::replace_vars( $podcast['author']['name'], $post ) : '';
$is_explicit = empty( $podcast['isFamilyFriendly'] ) ? 'yes' : 'clean';
$episode_number = ! empty( $podcast['episodeNumber'] ) ? $podcast['episodeNumber'] : '';
$season_number = ! empty( $podcast['partOfSeason'] ) && ! empty( $podcast['partOfSeason']['seasonNumber'] ) ? $podcast['partOfSeason']['seasonNumber'] : '';
if ( $title ) {
$this->newline( '<itunes:title>' . wp_kses_post( $title ) . '</itunes:title>' );
}
if ( $description ) {
$this->newline( '<itunes:summary>' . wp_kses_post( $description ) . '</itunes:summary>', 2 );
}
if ( $image ) {
$this->newline( '<itunes:image href="' . esc_url( $image ) . '" />', 2 );
}
if ( $duration ) {
$this->newline( '<itunes:duration>' . $duration . '</itunes:duration>', 2 );
}
if ( $author ) {
$this->newline( '<itunes:author>' . esc_html( $author ) . '</itunes:author>', 2 );
}
if ( $season_number ) {
$this->newline( '<itunes:season>' . esc_html( $season_number ) . '</itunes:season>', 2 );
}
if ( $episode_number ) {
$this->newline( '<itunes:episode>' . esc_html( $episode_number ) . '</itunes:episode>', 2 );
}
$this->newline( '<itunes:explicit>' . $is_explicit . '</itunes:explicit>', 2 );
$tracking_prefix = Helper::get_settings( 'general.podcast_tracking_prefix' );
$this->newline( '<enclosure url="' . esc_attr( $tracking_prefix ) . esc_url( $audio_file ) . '" length="' . $duration . '" type="audio/mpeg" />', 2 );
}
/**
* Write a newline with indent count.
*
* @param string $content Content to write.
* @param integer $indent Count of indent.
*/
private function newline( $content, $indent = 0 ) {
echo str_repeat( "\t", $indent ) . $content . "\n";
}
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* The Podcast Schema.
*
* @since 3.0.17
* @package RankMath
* @subpackage RankMathPro\Schema
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Podcast;
use RankMath\KB;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use MyThemeShop\Helpers\Arr;
defined( 'ABSPATH' ) || exit;
/**
* Podcast class.
*/
class Podcast {
use Hooker;
/**
* Store podcast feed slug.
*/
private $podcast;
/**
* The Constructor.
*/
public function __construct() {
$this->filter( 'rank_math/settings/general', 'add_settings' );
$this->action( 'init', 'init' );
$this->action( 'rank_math/vars/register_extra_replacements', 'register_replacements' );
}
/**
* Intialize.
*/
public function init() {
$this->podcast = $this->do_filter( 'podcast/feed', 'podcast' );
add_feed( $this->podcast, [ $this, 'podcast_feed' ] );
new Podcast_RSS();
new Publish_Podcast();
}
/**
* Registers variable replacements for Rank Math Pro.
*/
public function register_replacements() {
rank_math_register_var_replacement(
'podcast_image',
[
'name' => esc_html__( 'Podcast Image', 'rank-math-pro' ),
'description' => esc_html__( 'Podcast channel image configured in the Rank Math Settings.', 'rank-math-pro' ),
'variable' => 'podcast_image',
'example' => '',
],
[ $this, 'get_podcast_image' ]
);
}
/**
* Get Podcast image from the Settings.
*
* @return string Podcast image.
*/
public function get_podcast_image() {
return Helper::get_settings( 'general.podcast_image' );
}
/**
* Add module settings in the General Settings panel.
*
* @param array $tabs Array of option panel tabs.
* @return array
*/
public function add_settings( $tabs ) {
Arr::insert(
$tabs,
[
'podcast' => [
'icon' => 'rm-icon rm-icon-podcast',
'title' => esc_html__( 'Podcast', 'rank-math-pro' ),
/* translators: Link to kb article */
'desc' => sprintf( esc_html__( 'Make your podcasts discoverable via Google Podcasts, Apple Podcasts, and similar services. %s.', 'rank-math' ), '<a href="' . KB::get( 'podcast-settings', 'Options Panel Podcast Tab' ) . '" target="_blank">' . esc_html__( 'Learn more', 'rank-math-pro' ) . '</a>' ),
'file' => dirname( __FILE__ ) . '/views/options.php',
/* translators: Link to Podcast RSS feed */
'after_row' => '<div class="notice notice-alt notice-info info inline rank-math-notice"><p>' . sprintf( esc_html__( 'Your Podcast RSS feed can be found here: %s', 'rank-math-pro' ), '<a href="' . get_feed_link( $this->podcast ) . '" target="_blank">' . get_feed_link( $this->podcast ) . '</a>' ) . '</p></div>',
],
],
12
);
return $tabs;
}
/**
* Add all podcasts feed to /feed/podcast.
*/
public function podcast_feed() {
require dirname( __FILE__ ) . '/views/feed-rss2.php';
}
/**
* Get podcasts
*/
public function get_podcasts() {
$post_types = array_filter(
Helper::get_accessible_post_types(),
function( $post_type ) {
return 'attachment' !== $post_type;
}
);
$args = $this->do_filter(
'podcast_args',
[
'post_type' => array_keys( $post_types ),
'posts_per_page' => get_option( 'posts_per_rss' ),
'meta_query' => [
[
'key' => 'rank_math_schema_PodcastEpisode',
'compare' => 'EXISTS',
],
],
]
);
return new \WP_Query( $args );
}
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* Add Podcasts RSS feed.
*
* @since 3.0.17
* @package RankMath
* @subpackage RankMathPro\Schema
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Podcast;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Schema\DB;
defined( 'ABSPATH' ) || exit;
/**
* Publish_Podcast class.
*/
class Publish_Podcast {
use Hooker;
/**
* Has Podcast schema.
*
* @var string
*/
protected $has_podcast_schema = false;
/**
* The Constructor.
*/
public function __construct() {
$this->action( 'rank_math/schema/update', 'publish_podcast' );
$this->action( 'rank_math/pre_update_schema', 'has_podcast_schema' );
$this->action( 'rss2_podcast_head', 'add_hub_urls' );
}
/**
* Check if current post already have a Podcast schema.
*
* @param int $post_id Current Post ID.
*/
public function has_podcast_schema( $post_id ) {
$schema_types = DB::get_schema_types( $post_id );
$this->has_podcast_schema = ! empty( $schema_types ) && in_array( 'PodcastEpisode', explode( ', ', $schema_types ), true );
}
/**
* Publish podcast when a new post is published.
*
* @param int $post_id Current Post ID.
*/
public function publish_podcast( $post_id ) {
if ( $this->has_podcast_schema ) {
return;
}
$podcast = get_post_meta( $post_id, 'rank_math_schema_PodcastEpisode', true );
if ( empty( $podcast ) ) {
return;
}
$hub_urls = $this->get_hub_urls();
if ( empty( $hub_urls ) ) {
return;
}
$user_agent = $this->do_filter( 'podcast/useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) );
$podcast_feed = esc_url( home_url( 'feed/podcast' ) );
$args = [
'timeout' => 100,
'user-agent' => "$user_agent; PubSubHubbub/WebSub",
'body' => "hub.mode=publish&hub.url={$podcast_feed}",
];
foreach ( $hub_urls as $hub_url ) {
wp_remote_post( $hub_url, $args );
}
}
/**
* Add Hub urls to podcast feed.
*/
public function add_hub_urls() {
$hub_urls = $this->get_hub_urls();
if ( empty( $hub_urls ) ) {
return;
}
foreach ( $hub_urls as $hub_url ) {
echo '<atom:link rel="hub" href="' . esc_url( $hub_url ) . '" />';
}
}
/**
* Get podcast Hub URLs.
*/
private function get_hub_urls() {
return $this->do_filter(
'podcast/hub_urls',
[
'https://pubsubhubbub.appspot.com',
'https://pubsubhubbub.superfeedr.com',
'https://websubhub.com/hub',
]
);
}
}

View File

@@ -0,0 +1,134 @@
<?php
/**
* RSS2 Feed Template for displaying RSS2 Posts feed.
* Forked from WordPress Core - feed-rss2.php.
*
* @package WordPress
*/
header( 'Content-Type: ' . feed_content_type( 'rss2' ) . '; charset=' . get_option( 'blog_charset' ), true );
$more = 1;
echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?' . '>';
/**
* Filter to remove Podcast feed credit.
*
* @param boolean Defaults to false.
*/
if ( ! apply_filters( 'rank_math/podcast/remove_credit', false ) ) {
echo "\n<!-- This Podcast feed is generated by Rank Math PRO SEO plugin - rankmath.com -->\n";
}
/**
* Fires between the xml and rss tags in a feed.
*
* @param string $context Type of feed. Possible values include 'rss2', 'rss2-comments',
* 'rdf', 'atom', and 'atom-comments'.
*/
do_action( 'rss_tag_pre', 'rss2' );
?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
<?php
/**
* Fires at the end of the RSS root to add namespaces.
*/
do_action( 'rss2_ns' );
do_action( 'rss2_podcast_ns' );
?>
>
<channel>
<title><?php wp_title_rss(); ?></title>
<atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
<link><?php bloginfo_rss( 'url' ); ?></link>
<description><?php bloginfo_rss( 'description' ); ?></description>
<lastBuildDate><?php echo get_feed_build_date( 'r' ); ?></lastBuildDate>
<language><?php bloginfo_rss( 'language' ); ?></language>
<sy:updatePeriod>
<?php
$duration = 'hourly';
/**
* Filters how often to update the RSS feed.
*
* @param string $duration The update period. Accepts 'hourly', 'daily', 'weekly', 'monthly',
* 'yearly'. Default 'hourly'.
*/
echo apply_filters( 'rss_update_period', $duration );
?>
</sy:updatePeriod>
<sy:updateFrequency>
<?php
$frequency = '1';
/**
* Filters the RSS update frequency.
*
* @param string $frequency An integer passed as a string representing the frequency
* of RSS updates within the update period. Default '1'.
*/
echo apply_filters( 'rss_update_frequency', $frequency );
?>
</sy:updateFrequency>
<?php
/**
* Fires at the end of the RSS2 Feed Header.
*/
do_action( 'rss2_head' );
do_action( 'rss2_podcast_head' );
$podcast_query = $this->get_podcasts();
while ( $podcast_query->have_posts() ) :
$podcast_query->the_post();
?>
<item>
<title><?php the_title_rss(); ?></title>
<link><?php the_permalink_rss(); ?></link>
<?php if ( get_comments_number() || comments_open() ) : ?>
<comments><?php comments_link_feed(); ?></comments>
<?php endif; ?>
<dc:creator><![CDATA[<?php the_author(); ?>]]></dc:creator>
<pubDate><?php echo mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ); ?></pubDate>
<?php the_category_rss( 'rss2' ); ?>
<guid isPermaLink="false"><?php the_guid(); ?></guid>
<?php if ( get_option( 'rss_use_excerpt' ) ) : ?>
<description><![CDATA[<?php the_excerpt(); ?>]]></description>
<?php else : ?>
<description><![CDATA[<?php the_excerpt(); ?>]]></description>
<?php
$content = apply_filters( 'the_content', get_the_content() );
$content = str_replace( ']]>', ']]&gt;', $content );
?>
<?php if ( strlen( $content ) > 0 ) : ?>
<content:encoded><![CDATA[<?php echo $content; ?>]]></content:encoded>
<?php else : ?>
<content:encoded><![CDATA[<?php the_excerpt(); ?>]]></content:encoded>
<?php endif; ?>
<?php endif; ?>
<?php if ( get_comments_number() || comments_open() ) : ?>
<wfw:commentRss><?php echo esc_url( get_post_comments_feed_link( null, 'rss2' ) ); ?></wfw:commentRss>
<slash:comments><?php echo get_comments_number(); ?></slash:comments>
<?php endif; ?>
<?php rss_enclosure(); ?>
<?php
/**
* Fires at the end of each RSS2 feed item.
*/
do_action( 'rss2_item' );
do_action( 'rss2_podcast_item' );
?>
</item>
<?php endwhile; ?>
</channel>
</rss>

Some files were not shown because too many files have changed in this diff Show More