Commit realizado el 12:13:52 08-04-2024
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
/**
|
||||
* Workflow Base.
|
||||
*
|
||||
* @since 1.0.54
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Analytics\Workflow;
|
||||
|
||||
use RankMath\Helper;
|
||||
use function has_filter;
|
||||
use RankMath\Analytics\DB;
|
||||
use RankMath\Traits\Hooker;
|
||||
use function as_schedule_single_action;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Base class.
|
||||
*/
|
||||
abstract class Base {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Start fetching process.
|
||||
*
|
||||
* @param integer $days Number of days to fetch from past.
|
||||
* @param string $action Action to perform.
|
||||
* @return integer
|
||||
*/
|
||||
public function create_jobs( $days = 90, $action = 'console' ) {
|
||||
$count = $this->add_data_pull( $days + 3, $action );
|
||||
$time_gap = $this->get_schedule_gap();
|
||||
|
||||
Workflow::add_clear_cache( time() + ( $time_gap * ( $count + 1 ) ) );
|
||||
|
||||
update_option( 'rank_math_analytics_first_fetch', 'fetching' );
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data pull jobs.
|
||||
*
|
||||
* @param integer $days Number of days to fetch from past.
|
||||
* @param string $action Action to perform.
|
||||
* @return integer
|
||||
*/
|
||||
private function add_data_pull( $days, $action = 'console' ) {
|
||||
$count = 1;
|
||||
$start = Helper::get_midnight( time() + DAY_IN_SECONDS );
|
||||
$interval = $this->get_data_interval();
|
||||
$time_gap = $this->get_schedule_gap();
|
||||
|
||||
$hook = "get_{$action}_data";
|
||||
if ( 1 === $interval ) {
|
||||
for ( $current = 1; $current <= $days; $current++ ) {
|
||||
$date = date_i18n( 'Y-m-d', $start - ( DAY_IN_SECONDS * $current ) );
|
||||
if ( ! DB::date_exists( $date, $action ) ) {
|
||||
$count++;
|
||||
as_schedule_single_action(
|
||||
time() + ( $time_gap * $count ),
|
||||
'rank_math/analytics/' . $hook,
|
||||
[ $date ],
|
||||
'rank-math'
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( $current = 1; $current <= $days; $current = $current + $interval ) {
|
||||
for ( $j = 0; $j < $interval; $j++ ) {
|
||||
$date = date_i18n( 'Y-m-d', $start - ( DAY_IN_SECONDS * ( $current + $j ) ) );
|
||||
if ( ! DB::date_exists( $date, $action ) ) {
|
||||
$count++;
|
||||
as_schedule_single_action(
|
||||
time() + ( $time_gap * $count ),
|
||||
'rank_math/analytics/' . $hook,
|
||||
[ $date ],
|
||||
'rank-math'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data interval.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function get_data_interval() {
|
||||
$is_custom = has_filter( 'rank_math/analytics/app_url' );
|
||||
|
||||
return $is_custom ? $this->do_filter( 'analytics/data_interval', 7 ) : 7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schedule gap.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function get_schedule_gap() {
|
||||
return $this->do_filter( 'analytics/schedule_gap', 30 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if google profile is updated.
|
||||
*
|
||||
* @param string $param Google profile param name.
|
||||
* @param string $prev Previous profile data.
|
||||
* @param string $new New posted profile data.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_profile_updated( $param, $prev, $new ) {
|
||||
if (
|
||||
! is_null( $prev ) &&
|
||||
! is_null( $new ) &&
|
||||
isset( $prev[ $param ] ) &&
|
||||
isset( $new[ $param ] ) &&
|
||||
$prev[ $param ] === $new[ $param ]
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get the dates.
|
||||
*
|
||||
* @param int $days Number of days.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_dates( $days = 90 ) {
|
||||
$end = Helper::get_midnight( strtotime( '-1 day', time() ) );
|
||||
$start = strtotime( '-' . $days . ' day', $end );
|
||||
|
||||
return [
|
||||
'start_date' => date_i18n( 'Y-m-d', $start ),
|
||||
'end_date' => date_i18n( 'Y-m-d', $end ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule single action
|
||||
*
|
||||
* @param int $days Number of days.
|
||||
* @param string $action Name of the action hook.
|
||||
* @param array $args Arguments to pass to callbacks when the hook triggers.
|
||||
* @param string $group The group to assign this job to.
|
||||
* @param boolean $unique Whether the action should be unique.
|
||||
*/
|
||||
public function schedule_single_action( $days = 90, $action = '', $args = [], $group = 'rank-math', $unique = false ) {
|
||||
$timestamp = get_option( 'rank_math_analytics_last_single_action_schedule_time', time() );
|
||||
$time_gap = $this->get_schedule_gap();
|
||||
|
||||
$dates = self::get_dates( $days );
|
||||
|
||||
// Get the analytics dates in which analytics data is actually available.
|
||||
$days = apply_filters(
|
||||
'rank_math/analytics/get_' . $action . '_days',
|
||||
[
|
||||
'start_date' => $dates['start_date'],
|
||||
'end_date' => $dates['end_date'],
|
||||
]
|
||||
);
|
||||
|
||||
// No days then don't schedule the action.
|
||||
if ( empty( $days ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $days as $day ) {
|
||||
|
||||
// Next schedule time.
|
||||
$timestamp = $timestamp + $time_gap;
|
||||
|
||||
$args = wp_parse_args(
|
||||
[
|
||||
'start_date' => $day['start_date'],
|
||||
'end_date' => $day['end_date'],
|
||||
],
|
||||
$args
|
||||
);
|
||||
|
||||
as_schedule_single_action(
|
||||
$timestamp,
|
||||
'rank_math/analytics/get_' . $action . '_data',
|
||||
$args,
|
||||
$group,
|
||||
$unique
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
Workflow::add_clear_cache( $timestamp );
|
||||
|
||||
// Update timestamp.
|
||||
update_option( 'rank_math_analytics_last_single_action_schedule_time', $timestamp );
|
||||
}
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* Google Search Console.
|
||||
*
|
||||
* @since 1.0.49
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Analytics
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Analytics\Workflow;
|
||||
|
||||
use Exception;
|
||||
use RankMath\Helpers\DB;
|
||||
use RankMath\Google\Console as GoogleConsole;
|
||||
use function as_unschedule_all_actions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Console class.
|
||||
*/
|
||||
class Console extends Base {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->create_tables();
|
||||
|
||||
// If console is not connected, ignore all no need to proceed.
|
||||
if ( ! GoogleConsole::is_console_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->action( 'rank_math/analytics/workflow/console', 'kill_jobs', 5, 0 );
|
||||
$this->action( 'rank_math/analytics/workflow/create_tables', 'create_tables' );
|
||||
$this->action( 'rank_math/analytics/workflow/console', 'create_tables', 6, 0 );
|
||||
$this->action( 'rank_math/analytics/workflow/console', 'create_data_jobs', 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule all console data fetch action.
|
||||
*
|
||||
* Stop processing queue items, clear cronjob and delete all batches.
|
||||
*/
|
||||
public function kill_jobs() {
|
||||
as_unschedule_all_actions( 'rank_math/analytics/get_console_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tables.
|
||||
*/
|
||||
public function create_tables() {
|
||||
global $wpdb;
|
||||
|
||||
$collate = $wpdb->get_charset_collate();
|
||||
$table = 'rank_math_analytics_gsc';
|
||||
|
||||
// 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,
|
||||
query varchar(1000) NOT NULL,
|
||||
page varchar(500) NOT NULL,
|
||||
clicks mediumint(6) NOT NULL,
|
||||
impressions mediumint(6) NOT NULL,
|
||||
position double NOT NULL,
|
||||
ctr double NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY analytics_query (query(190)),
|
||||
KEY analytics_page (page(190)),
|
||||
KEY clicks (clicks),
|
||||
KEY rank_position (position)
|
||||
) $collate;";
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
try {
|
||||
dbDelta( $schema );
|
||||
} catch ( Exception $e ) { // phpcs:ignore
|
||||
// Will log.
|
||||
}
|
||||
|
||||
// Make sure that collations match the objects table.
|
||||
$objects_coll = DB::get_table_collation( 'rank_math_analytics_objects' );
|
||||
DB::check_collation( $table, 'all', $objects_coll );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
// Early bail if saved & new profile are same.
|
||||
if ( ! $this->is_profile_updated( 'profile', $prev, $new ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_option( 'rank_math_analytics_first_fetch', 'fetching' );
|
||||
|
||||
// Fetch now.
|
||||
$this->schedule_single_action( $days, 'console' );
|
||||
}
|
||||
}
|
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* Google Search Console.
|
||||
*
|
||||
* @since 1.0.49
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Analytics
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Analytics\Workflow;
|
||||
|
||||
use Exception;
|
||||
use RankMath\Helpers\DB;
|
||||
use RankMath\Traits\Hooker;
|
||||
use RankMath\Analytics\DB as AnalyticsDB;
|
||||
use RankMath\Analytics\Url_Inspection;
|
||||
use RankMath\Google\Console;
|
||||
|
||||
use function as_unschedule_all_actions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Inspections class.
|
||||
*/
|
||||
class Inspections {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* API Limit.
|
||||
* 600 requests per minute, 2000 per day.
|
||||
* We can ignore the per-minute limit, since we will use a few seconds delay after each request.
|
||||
*/
|
||||
const API_LIMIT = 2000;
|
||||
|
||||
/**
|
||||
* Interval between requests.
|
||||
*/
|
||||
const REQUEST_GAP_SECONDS = 7;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->create_tables();
|
||||
|
||||
// If console is not connected, ignore all, no need to proceed.
|
||||
if ( ! Console::is_console_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->action( 'rank_math/analytics/workflow/create_tables', 'create_tables' );
|
||||
$this->action( 'rank_math/analytics/workflow/inspections', 'create_tables', 6, 0 );
|
||||
$this->action( 'rank_math/analytics/workflow/inspections', 'create_data_jobs', 10, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule all inspections data fetch action.
|
||||
*
|
||||
* Stop processing queue items, clear cronjob and delete all batches.
|
||||
*/
|
||||
public static function kill_jobs() {
|
||||
as_unschedule_all_actions( 'rank_math/analytics/get_inspections_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tables.
|
||||
*/
|
||||
public function create_tables() {
|
||||
global $wpdb;
|
||||
|
||||
$collate = $wpdb->get_charset_collate();
|
||||
$table = 'rank_math_analytics_inspections';
|
||||
|
||||
// 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,
|
||||
index_verdict varchar(64) NOT NULL, /* PASS, PARTIAL, FAIL, NEUTRAL, VERDICT_UNSPECIFIED */
|
||||
indexing_state varchar(64) NOT NULL, /* INDEXING_ALLOWED, BLOCKED_BY_META_TAG, BLOCKED_BY_HTTP_HEADER, BLOCKED_BY_ROBOTS_TXT, INDEXING_STATE_UNSPECIFIED */
|
||||
coverage_state text NOT NULL, /* String, e.g. 'Submitted and indexed'. */
|
||||
page_fetch_state varchar(64) NOT NULL, /* SUCCESSFUL, SOFT_404, BLOCKED_ROBOTS_TXT, NOT_FOUND, ACCESS_DENIED, SERVER_ERROR, REDIRECT_ERROR, ACCESS_FORBIDDEN, BLOCKED_4XX, INTERNAL_CRAWL_ERROR, INVALID_URL, PAGE_FETCH_STATE_UNSPECIFIED */
|
||||
robots_txt_state varchar(64) NOT NULL, /* ALLOWED, DISALLOWED, ROBOTS_TXT_STATE_UNSPECIFIED */
|
||||
mobile_usability_verdict varchar(64) NOT NULL, /* PASS, PARTIAL, FAIL, NEUTRAL, VERDICT_UNSPECIFIED */
|
||||
mobile_usability_issues longtext NOT NULL, /* JSON */
|
||||
rich_results_verdict varchar(64) NOT NULL, /* PASS, PARTIAL, FAIL, NEUTRAL, VERDICT_UNSPECIFIED */
|
||||
rich_results_items longtext NOT NULL, /* JSON */
|
||||
last_crawl_time timestamp NOT NULL,
|
||||
crawled_as varchar(64) NOT NULL, /* DESKTOP, MOBILE, CRAWLING_USER_AGENT_UNSPECIFIED */
|
||||
google_canonical text NOT NULL, /* Google-chosen canonical URL. */
|
||||
user_canonical text NOT NULL, /* Canonical URL declared on-page. */
|
||||
sitemap text NOT NULL, /* Sitemap URL. */
|
||||
referring_urls longtext NOT NULL, /* JSON */
|
||||
raw_api_response longtext NOT NULL, /* JSON */
|
||||
PRIMARY KEY (id),
|
||||
KEY analytics_object_page (page(190)),
|
||||
KEY created (created),
|
||||
KEY index_verdict (index_verdict),
|
||||
KEY page_fetch_state (page_fetch_state),
|
||||
KEY robots_txt_state (robots_txt_state),
|
||||
KEY mobile_usability_verdict (mobile_usability_verdict),
|
||||
KEY rich_results_verdict (rich_results_verdict)
|
||||
) $collate;";
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
try {
|
||||
dbDelta( $schema );
|
||||
} catch ( Exception $e ) { // phpcs:ignore
|
||||
// Will log.
|
||||
}
|
||||
|
||||
// Make sure that collations match the objects table.
|
||||
$objects_coll = DB::get_table_collation( 'rank_math_analytics_objects' );
|
||||
DB::check_collation( $table, 'all', $objects_coll );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create jobs to fetch data.
|
||||
*/
|
||||
public function create_data_jobs() {
|
||||
// If there are jobs left from the previous queue, don't create new jobs.
|
||||
if ( as_has_scheduled_action( 'rank_math/analytics/get_inspections_data' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the option is disabled, don't create jobs.
|
||||
if ( ! Url_Inspection::is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$inspections_table = AnalyticsDB::inspections()->table;
|
||||
$objects_table = AnalyticsDB::objects()->table;
|
||||
|
||||
$objects = AnalyticsDB::objects()
|
||||
->select( [ "$objects_table.id", "$objects_table.page", "$inspections_table.created" ] )
|
||||
->leftJoin( $inspections_table, "$inspections_table.page", "$objects_table.page" )
|
||||
->where( "$objects_table.is_indexable", 1 )
|
||||
->orderBy( "$inspections_table.created", 'ASC' )
|
||||
->get();
|
||||
|
||||
$count = 0;
|
||||
foreach ( $objects as $object ) {
|
||||
$count++;
|
||||
$time = time() + ( $count * self::REQUEST_GAP_SECONDS );
|
||||
if ( $count > self::API_LIMIT ) {
|
||||
$delay_days = floor( $count / self::API_LIMIT );
|
||||
$time = strtotime( "+{$delay_days} days", $time );
|
||||
}
|
||||
|
||||
as_schedule_single_action( $time, 'rank_math/analytics/get_inspections_data', [ $object->page ], 'rank-math' );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
/**
|
||||
* Jobs.
|
||||
*
|
||||
* @since 1.0.54
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Analytics\Workflow;
|
||||
|
||||
use Exception;
|
||||
use RankMath\Helper;
|
||||
use RankMath\Google\Api;
|
||||
use RankMath\Google\Console;
|
||||
use RankMath\Google\Url_Inspection;
|
||||
use RankMath\Analytics\DB;
|
||||
use RankMath\Traits\Cache;
|
||||
use RankMath\Traits\Hooker;
|
||||
use RankMath\Analytics\Stats;
|
||||
use RankMath\Analytics\Watcher;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Jobs class.
|
||||
*/
|
||||
class Jobs {
|
||||
|
||||
use Hooker, Cache;
|
||||
|
||||
/**
|
||||
* 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->action( 'rank_math/analytics/flat_posts', 'do_flat_posts' );
|
||||
$this->action( 'rank_math/analytics/flat_posts_completed', 'flat_posts_completed' );
|
||||
add_action( 'rank_math/analytics/sync_sitemaps', [ Api::get(), 'sync_sitemaps' ] );
|
||||
|
||||
if ( Console::is_console_connected() ) {
|
||||
$this->action( 'rank_math/analytics/clear_cache', 'clear_cache', 99 );
|
||||
|
||||
// Fetch missing google data action.
|
||||
$this->action( 'rank_math/analytics/data_fetch', 'data_fetch' );
|
||||
|
||||
// Console data fetch.
|
||||
$this->filter( 'rank_math/analytics/get_console_days', 'get_console_days' );
|
||||
$this->action( 'rank_math/analytics/get_console_data', 'get_console_data' );
|
||||
$this->action( 'rank_math/analytics/handle_console_response', 'handle_console_response' );
|
||||
|
||||
// Inspections data fetch.
|
||||
$this->action( 'rank_math/analytics/get_inspections_data', 'get_inspections_data' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch missing console data.
|
||||
*/
|
||||
public function data_fetch() {
|
||||
$this->check_for_missing_dates( 'console' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post check.
|
||||
*/
|
||||
public function flat_posts_completed() {
|
||||
$rows = DB::objects()
|
||||
->selectCount( 'id' )
|
||||
->getVar();
|
||||
|
||||
Workflow::kill_workflows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/update posts info from objects table.
|
||||
*
|
||||
* @param array $ids Posts ids to process.
|
||||
*/
|
||||
public function do_flat_posts( $ids ) {
|
||||
Inspections::kill_jobs();
|
||||
|
||||
foreach ( $ids as $id ) {
|
||||
Watcher::get()->update_post_info( $id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache.
|
||||
*/
|
||||
public function clear_cache() {
|
||||
global $wpdb;
|
||||
|
||||
// Delete all useless data from console data table.
|
||||
$wpdb->get_results( "DELETE FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE page NOT IN ( SELECT page from {$wpdb->prefix}rank_math_analytics_objects )" );
|
||||
|
||||
// Delete useless data from inspections table too.
|
||||
$wpdb->get_results( "DELETE FROM {$wpdb->prefix}rank_math_analytics_inspections WHERE page NOT IN ( SELECT page from {$wpdb->prefix}rank_math_analytics_objects )" );
|
||||
|
||||
delete_transient( 'rank_math_analytics_data_info' );
|
||||
DB::purge_cache();
|
||||
DB::delete_data_log();
|
||||
$this->calculate_stats();
|
||||
|
||||
update_option( 'rank_math_analytics_last_updated', time() );
|
||||
|
||||
Workflow::do_workflow( 'inspections' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the console start and end dates.
|
||||
*
|
||||
* @param array $args Args containing start and end date.
|
||||
*/
|
||||
public function get_console_days( $args = [] ) {
|
||||
set_time_limit( 300 );
|
||||
|
||||
$rows = Api::get()->get_search_analytics(
|
||||
[
|
||||
'start_date' => $args['start_date'],
|
||||
'end_date' => $args['end_date'],
|
||||
'dimensions' => [ 'date' ],
|
||||
]
|
||||
);
|
||||
|
||||
if ( empty( $rows ) || is_wp_error( $rows ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$empty_dates = get_option( 'rank_math_console_empty_dates', [] );
|
||||
|
||||
$dates = [];
|
||||
|
||||
foreach ( $rows as $row ) {
|
||||
|
||||
// Have at least few impressions.
|
||||
if ( $row['impressions'] ) {
|
||||
$date = $row['keys'][0];
|
||||
|
||||
if ( ! DB::date_exists( $date, 'console' ) && ! in_array( $date, $empty_dates, true ) ) {
|
||||
$dates[] = [
|
||||
'start_date' => $date,
|
||||
'end_date' => $date,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get console data.
|
||||
*
|
||||
* @param string $date Date to fetch data for.
|
||||
*/
|
||||
public function get_console_data( $date ) {
|
||||
set_time_limit( 300 );
|
||||
|
||||
$rows = Api::get()->get_search_analytics(
|
||||
[
|
||||
'start_date' => $date,
|
||||
'end_date' => $date,
|
||||
'dimensions' => [ 'query', 'page' ],
|
||||
]
|
||||
);
|
||||
|
||||
if ( empty( $rows ) || is_wp_error( $rows ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = \array_map( [ $this, 'normalize_query_page_data' ], $rows );
|
||||
|
||||
try {
|
||||
DB::add_query_page_bulk( $date, $rows );
|
||||
|
||||
// Clear the cache here.
|
||||
$this->cache_flush_group( 'rank_math_rest_keywords_rows' );
|
||||
$this->cache_flush_group( 'rank_math_posts_rows_by_objects' );
|
||||
$this->cache_flush_group( 'rank_math_analytics_summary' );
|
||||
|
||||
return $rows;
|
||||
} catch ( Exception $e ) {} // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Handlle console response.
|
||||
*
|
||||
* @param array $data API request and response data.
|
||||
*/
|
||||
public function handle_console_response( $data = [] ) {
|
||||
if ( 200 !== $data['code'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $data['formatted_response']['rows'] ) && ! empty( $data['formatted_response']['rows'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $data['args']['startDate'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dates = get_option( 'rank_math_console_empty_dates', [] );
|
||||
if ( ! $dates ) {
|
||||
$dates = [];
|
||||
}
|
||||
|
||||
$dates[] = $data['args']['startDate'];
|
||||
$dates[] = $data['args']['endDate'];
|
||||
|
||||
$dates = array_unique( $dates );
|
||||
|
||||
update_option( 'rank_math_console_empty_dates', $dates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inspection results from the API and store them in the database.
|
||||
*
|
||||
* @param string $page URI to fetch data for.
|
||||
*/
|
||||
public function get_inspections_data( $page ) {
|
||||
// If the option is disabled, don't fetch data.
|
||||
if ( ! \RankMath\Analytics\Url_Inspection::is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$inspection = Url_Inspection::get()->get_inspection_data( $page );
|
||||
if ( empty( $inspection ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DB::store_inspection( $inspection );
|
||||
} catch ( Exception $e ) {} // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for missing dates.
|
||||
*
|
||||
* @param string $action Action to perform.
|
||||
*/
|
||||
public function check_for_missing_dates( $action ) {
|
||||
$days = Helper::get_settings( 'general.console_caching_control', 90 );
|
||||
|
||||
Workflow::do_workflow(
|
||||
$action,
|
||||
$days,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate stats.
|
||||
*/
|
||||
private function calculate_stats() {
|
||||
$ranges = [
|
||||
'-7 days',
|
||||
'-15 days',
|
||||
'-30 days',
|
||||
'-3 months',
|
||||
'-6 months',
|
||||
'-1 year',
|
||||
];
|
||||
|
||||
foreach ( $ranges as $range ) {
|
||||
Stats::get()->set_date_range( $range );
|
||||
Stats::get()->get_top_keywords();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize console data.
|
||||
*
|
||||
* @param array $row Single row item.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function normalize_query_page_data( $row ) {
|
||||
$row = $this->normalize_data( $row );
|
||||
$row['query'] = $row['keys'][0];
|
||||
$row['page'] = $row['keys'][1];
|
||||
|
||||
unset( $row['keys'] );
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize console data.
|
||||
*
|
||||
* @param array $row Single row item.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function normalize_data( $row ) {
|
||||
$row['ctr'] = round( $row['ctr'] * 100, 2 );
|
||||
$row['position'] = round( $row['position'], 2 );
|
||||
|
||||
return $row;
|
||||
}
|
||||
}
|
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* Authentication workflow.
|
||||
*
|
||||
* @since 1.0.55
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Analytics
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Analytics\Workflow;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Google\Api;
|
||||
use RankMath\Traits\Hooker;
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Helpers\Param;
|
||||
use RankMath\Helpers\Security;
|
||||
use RankMath\Analytics\DB;
|
||||
use RankMath\Google\Permissions;
|
||||
use RankMath\Google\Authentication;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* OAuth class.
|
||||
*/
|
||||
class OAuth {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->action( 'admin_init', 'process_oauth' );
|
||||
$this->action( 'admin_init', 'reconnect_google' );
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth reply back
|
||||
*/
|
||||
public function process_oauth() {
|
||||
$process_oauth = Param::get( 'process_oauth', 0, FILTER_VALIDATE_INT );
|
||||
$access_token = Param::get( 'access_token', '', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK );
|
||||
$security = Param::get( 'rankmath_security', '', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK );
|
||||
|
||||
// Early Bail!!
|
||||
if ( empty( $security ) || ( $process_oauth < 1 && empty( $access_token ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( $security, 'rank_math_oauth_token' ) ) {
|
||||
wp_nonce_ays( 'rank_math_oauth_token' );
|
||||
die();
|
||||
}
|
||||
|
||||
$redirect = false;
|
||||
// Backward compatibility.
|
||||
if ( ! empty( $process_oauth ) ) {
|
||||
$redirect = $this->get_tokens_from_server();
|
||||
}
|
||||
|
||||
// New version.
|
||||
if ( ! empty( $access_token ) ) {
|
||||
$redirect = $this->get_tokens_from_url();
|
||||
}
|
||||
|
||||
// Remove possible admin notice if we have new access token.
|
||||
delete_option( 'rankmath_google_api_failed_attempts_data' );
|
||||
delete_option( 'rankmath_google_api_reconnect' );
|
||||
|
||||
Permissions::fetch();
|
||||
|
||||
if ( ! empty( $redirect ) ) {
|
||||
Helper::redirect( $redirect );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect Google.
|
||||
*/
|
||||
public function reconnect_google() {
|
||||
if ( ! isset( $_GET['reconnect'] ) || 'google' !== $_GET['reconnect'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rank_math_reconnect_google' ) ) {
|
||||
wp_nonce_ays( 'rank_math_reconnect_google' );
|
||||
die();
|
||||
}
|
||||
|
||||
if ( ! Helper::has_cap( 'analytics' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = DB::objects()
|
||||
->selectCount( 'id' )
|
||||
->getVar();
|
||||
|
||||
if ( empty( $rows ) ) {
|
||||
delete_option( 'rank_math_analytics_installed' );
|
||||
}
|
||||
|
||||
Api::get()->revoke_token();
|
||||
Workflow::kill_workflows();
|
||||
|
||||
wp_redirect( Authentication::get_auth_url() ); // phpcs:ignore
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access token from url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_tokens_from_url() {
|
||||
$data = [
|
||||
'access_token' => urldecode( Param::get( 'access_token', '', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK ) ),
|
||||
'refresh_token' => urldecode( Param::get( 'refresh_token', '', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK ) ),
|
||||
'expire' => urldecode( Param::get( 'expire', 0, FILTER_VALIDATE_INT ) ),
|
||||
];
|
||||
|
||||
Authentication::tokens( $data );
|
||||
|
||||
$current_request = remove_query_arg(
|
||||
[
|
||||
'access_token',
|
||||
'refresh_token',
|
||||
'expire',
|
||||
'security',
|
||||
]
|
||||
);
|
||||
|
||||
return $current_request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access token from rankmath server.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_tokens_from_server() {
|
||||
// Bail if the user is not authenticated at all yet.
|
||||
$id = Param::get( 'process_oauth', 0, FILTER_VALIDATE_INT );
|
||||
if ( $id < 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = wp_remote_get( Authentication::get_auth_app_url() . '/get.php?id=' . $id );
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = wp_remote_retrieve_body( $response );
|
||||
if ( empty( $response ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = \json_decode( $response, true );
|
||||
unset( $response['id'] );
|
||||
|
||||
// Save new token.
|
||||
Authentication::tokens( $response );
|
||||
|
||||
$redirect = Security::remove_query_arg_raw( [ 'process_oauth', 'security' ] );
|
||||
if ( Str::contains( 'rank-math-options-general', $redirect ) ) {
|
||||
$redirect .= '#setting-panel-analytics';
|
||||
}
|
||||
|
||||
Helper::remove_notification( 'rank_math_analytics_reauthenticate' );
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
}
|
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Install objects.
|
||||
*
|
||||
* @since 1.0.49
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Analytics\Workflow;
|
||||
|
||||
use Exception;
|
||||
use RankMath\Helper;
|
||||
use RankMath\Helpers\DB;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Objects class.
|
||||
*/
|
||||
class Objects extends Base {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$done = \boolval( get_option( 'rank_math_analytics_installed' ) );
|
||||
if ( $done ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->create_tables();
|
||||
$this->create_data_job();
|
||||
$this->flat_posts();
|
||||
|
||||
update_option( 'rank_math_analytics_installed', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tables.
|
||||
*/
|
||||
public function create_tables() {
|
||||
global $wpdb;
|
||||
|
||||
$collate = $wpdb->get_charset_collate();
|
||||
$table = 'rank_math_analytics_objects';
|
||||
|
||||
// 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,
|
||||
title text NOT NULL,
|
||||
page varchar(500) NOT NULL,
|
||||
object_type varchar(100) NOT NULL,
|
||||
object_subtype varchar(100) NOT NULL,
|
||||
object_id bigint(20) unsigned NOT NULL,
|
||||
primary_key varchar(255) NOT NULL,
|
||||
seo_score tinyint NOT NULL default 0,
|
||||
page_score tinyint NOT NULL default 0,
|
||||
is_indexable tinyint(1) NOT NULL default 1,
|
||||
schemas_in_use varchar(500),
|
||||
desktop_interactive double default 0,
|
||||
desktop_pagescore double default 0,
|
||||
mobile_interactive double default 0,
|
||||
mobile_pagescore double default 0,
|
||||
pagespeed_refreshed timestamp,
|
||||
PRIMARY KEY (id),
|
||||
KEY analytics_object_page (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.
|
||||
*/
|
||||
public function create_data_job() {
|
||||
// Clear old schedule.
|
||||
wp_clear_scheduled_hook( 'rank_math/analytics/get_analytics' );
|
||||
|
||||
// Add action for scheduler.
|
||||
$task_name = 'rank_math/analytics/data_fetch';
|
||||
$fetch_gap = apply_filters( 'rank_math/analytics/fetch_gap', 7, 'objects' );
|
||||
|
||||
// Schedule new action only when there is no existing action.
|
||||
if ( false === as_next_scheduled_action( $task_name ) ) {
|
||||
$schedule_in_minute = wp_rand( 3, defined( 'RANK_MATH_PRO_FILE' ) ? 1380 : 4320 );
|
||||
$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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flat posts
|
||||
*/
|
||||
public function flat_posts() {
|
||||
$post_types = $this->do_filter( 'analytics/post_types', Helper::get_accessible_post_types() );
|
||||
unset( $post_types['attachment'] );
|
||||
|
||||
$ids = get_posts(
|
||||
[
|
||||
'post_type' => array_keys( $post_types ),
|
||||
'post_status' => 'publish',
|
||||
'fields' => 'ids',
|
||||
'posts_per_page' => -1,
|
||||
]
|
||||
);
|
||||
|
||||
$counter = 0;
|
||||
$chunks = \array_chunk( $ids, 50 );
|
||||
foreach ( $chunks as $chunk ) {
|
||||
$counter++;
|
||||
as_schedule_single_action(
|
||||
time() + ( 60 * ( $counter / 2 ) ),
|
||||
'rank_math/analytics/flat_posts',
|
||||
[ $chunk ],
|
||||
'rank-math'
|
||||
);
|
||||
}
|
||||
|
||||
// Check for posts.
|
||||
as_schedule_single_action(
|
||||
time() + ( 60 * ( ( $counter + 1 ) / 2 ) ),
|
||||
'rank_math/analytics/flat_posts_completed',
|
||||
[],
|
||||
'rank-math'
|
||||
);
|
||||
|
||||
// Clear cache.
|
||||
Workflow::add_clear_cache( time() + ( 60 * ( ( $counter + 2 ) / 2 ) ) );
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/**
|
||||
* Workflow.
|
||||
*
|
||||
* @since 1.0.54
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\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', 'start_workflow', 10, 4 );
|
||||
$this->action( 'rank_math/analytics/workflow/create_tables', 'create_tables_only', 5 );
|
||||
|
||||
// Console.
|
||||
$this->action( 'rank_math/analytics/workflow/console', 'init_console_workflow', 5, 0 );
|
||||
|
||||
// Inspections.
|
||||
$this->action( 'rank_math/analytics/workflow/inspections', 'init_inspections_workflow', 5, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe first install.
|
||||
*/
|
||||
public function maybe_first_install() {
|
||||
new Objects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init Console workflow
|
||||
*/
|
||||
public function init_console_workflow() {
|
||||
new Console();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init Inspections workflow.
|
||||
*/
|
||||
public function init_inspections_workflow() {
|
||||
new Inspections();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tables only.
|
||||
*/
|
||||
public function create_tables_only() {
|
||||
( new Objects() )->create_tables();
|
||||
( new Inspections() )->create_tables();
|
||||
new Console();
|
||||
}
|
||||
|
||||
/**
|
||||
* Service workflow
|
||||
*
|
||||
* @param string $action Action to perform.
|
||||
* @param integer $days Number of days to fetch from past.
|
||||
* @param string $prev Previous saved value.
|
||||
* @param string $new New posted value.
|
||||
*/
|
||||
public function start_workflow( $action, $days = 0, $prev = null, $new = null ) {
|
||||
do_action(
|
||||
'rank_math/analytics/workflow/' . $action,
|
||||
$days,
|
||||
$prev,
|
||||
$new
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Service workflow
|
||||
*
|
||||
* @param string $action Action to perform.
|
||||
* @param integer $days Number of days to fetch from past.
|
||||
* @param string $prev Previous saved value.
|
||||
* @param string $new New posted value.
|
||||
*/
|
||||
public static function do_workflow( $action, $days = 0, $prev = null, $new = null ) {
|
||||
as_enqueue_async_action(
|
||||
'rank_math/analytics/workflow',
|
||||
[
|
||||
$action,
|
||||
$days,
|
||||
$prev,
|
||||
$new,
|
||||
],
|
||||
'rank-math'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill all workflows
|
||||
*
|
||||
* Stop processing queue items, clear cronjob and delete all batches.
|
||||
*/
|
||||
public static function kill_workflows() {
|
||||
as_unschedule_all_actions( 'rank_math/analytics/workflow' );
|
||||
as_unschedule_all_actions( 'rank_math/analytics/clear_cache' );
|
||||
as_unschedule_all_actions( 'rank_math/analytics/get_console_data' );
|
||||
as_unschedule_all_actions( 'rank_math/analytics/get_analytics_data' );
|
||||
as_unschedule_all_actions( 'rank_math/analytics/get_adsense_data' );
|
||||
as_unschedule_all_actions( 'rank_math/analytics/get_inspections_data' );
|
||||
|
||||
do_action( 'rank_math/analytics/clear_cache' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add clear cache job.
|
||||
*
|
||||
* @param int $time Timestamp to add job for.
|
||||
*/
|
||||
public static function add_clear_cache( $time ) {
|
||||
as_unschedule_all_actions( 'rank_math/analytics/clear_cache' );
|
||||
as_schedule_single_action(
|
||||
$time,
|
||||
'rank_math/analytics/clear_cache',
|
||||
[],
|
||||
'rank-math'
|
||||
);
|
||||
|
||||
delete_option( 'rank_math_analytics_last_single_action_schedule_time' );
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user