Commit realizado el 12:13:52 08-04-2024
This commit is contained in:
@@ -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' );
|
||||
}
|
||||
}
|
@@ -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' );
|
||||
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user