You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
649 lines
16 KiB
PHP
649 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Analytics Email Reports.
|
|
*
|
|
* @since 1.0.68
|
|
* @package RankMath
|
|
* @subpackage RankMath\modules
|
|
* @author Rank Math <support@rankmath.com>
|
|
*/
|
|
|
|
namespace RankMath\Analytics;
|
|
|
|
use RankMath\KB;
|
|
use RankMath\Helper;
|
|
use RankMath\Traits\Hooker;
|
|
use RankMath\Google\Console;
|
|
use RankMath\Admin\Admin_Helper;
|
|
|
|
use RankMath\Helpers\Param;
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
/**
|
|
* Email_Reports class.
|
|
*/
|
|
class Email_Reports {
|
|
|
|
use Hooker;
|
|
|
|
/**
|
|
* Email content variables.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $variables = [];
|
|
|
|
/**
|
|
* Path to the views directory.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $views_path = '';
|
|
|
|
/**
|
|
* URL to the assets directory.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $assets_url = '';
|
|
|
|
/**
|
|
* Charts Account.
|
|
*
|
|
* @var string
|
|
*/
|
|
private $charts_account = 'rankmath';
|
|
|
|
/**
|
|
* Charts Key.
|
|
*
|
|
* @var string
|
|
*/
|
|
private $charts_key = '10042B42-9193-428A-ABA7-5753F3370F84';
|
|
|
|
/**
|
|
* Graph data.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $graph_data = [];
|
|
|
|
/**
|
|
* Debug mode.
|
|
*
|
|
* @var boolean
|
|
*/
|
|
private $debug = false;
|
|
|
|
/**
|
|
* The constructor.
|
|
*/
|
|
public function __construct() {
|
|
if ( ! Console::is_console_connected() ) {
|
|
return;
|
|
}
|
|
|
|
$directory = dirname( __FILE__ );
|
|
$this->views_path = $directory . '/views/email-reports/';
|
|
|
|
$url = plugin_dir_url( __FILE__ );
|
|
$this->assets_url = $this->do_filter( 'analytics/email_report_assets_url', $url . 'assets/' );
|
|
|
|
$this->hooks();
|
|
}
|
|
|
|
/**
|
|
* Add filter & action hooks.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function hooks() {
|
|
$this->action( 'rank_math/analytics/email_report_event', 'email_report' );
|
|
$this->action( 'template_redirect', 'maybe_debug' );
|
|
|
|
$this->action( 'rank_math/analytics/email_report_html', 'replace_variables' );
|
|
$this->action( 'rank_math/analytics/email_report_html', 'strip_comments' );
|
|
}
|
|
|
|
/**
|
|
* Send Analytics report or error message.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function email_report() {
|
|
$this->setup_variables();
|
|
$this->send_report();
|
|
}
|
|
|
|
/**
|
|
* Collect variables to be used in the Report template.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setup_variables() {
|
|
$stats = $this->get_stats();
|
|
$date = $this->get_date();
|
|
|
|
// Translators: placeholder is "rankmath.com" as a link.
|
|
$footer_text = sprintf( esc_html__( 'This email was sent to you as a registered member of %s.', 'rank-math' ), '<a href="###SITE_URL###">###SITE_URL_SIMPLE###</a>' );
|
|
$footer_text .= ' ';
|
|
|
|
// Translators: placeholder is "click here" as a link.
|
|
$footer_text .= sprintf( esc_html__( 'To update your email preferences, %s.', 'rank-math' ), '<a href="###SETTINGS_URL###">' . esc_html__( 'click here', 'rank-math' ) . '</a>' );
|
|
|
|
$footer_text .= '###ADDRESS###';
|
|
|
|
$this->variables = [
|
|
'site_url' => get_home_url(),
|
|
'site_url_simple' => explode( '://', get_home_url() )[1],
|
|
'settings_url' => Helper::get_admin_url( 'options-general#setting-panel-analytics' ),
|
|
'report_url' => Helper::get_admin_url( 'analytics' ),
|
|
'assets_url' => $this->assets_url,
|
|
'address' => '<br/> [rank_math_contact_info show="address"]',
|
|
'logo_link' => KB::get( 'email-reports-logo', 'Email Report Logo' ),
|
|
|
|
'period_days' => $date['period'],
|
|
'start_date' => $date['start'],
|
|
'end_date' => $date['end'],
|
|
|
|
'stats_clicks' => $stats['clicks'],
|
|
'stats_clicks_diff' => $stats['clicks_diff'],
|
|
'stats_traffic' => $stats['traffic'],
|
|
'stats_traffic_diff' => $stats['traffic_diff'],
|
|
'stats_impressions' => $stats['impressions'],
|
|
'stats_impressions_diff' => $stats['impressions_diff'],
|
|
'stats_keywords' => $stats['keywords'],
|
|
'stats_keywords_diff' => $stats['keywords_diff'],
|
|
'stats_position' => $stats['position'],
|
|
'stats_position_diff' => $stats['position_diff'],
|
|
'stats_top_3_positions' => $stats['top_3_positions'],
|
|
'stats_top_3_positions_diff' => $stats['top_3_positions_diff'],
|
|
'stats_top_10_positions' => $stats['top_10_positions'],
|
|
'stats_top_10_positions_diff' => $stats['top_10_positions_diff'],
|
|
'stats_top_50_positions' => $stats['top_50_positions'],
|
|
'stats_top_50_positions_diff' => $stats['top_50_positions_diff'],
|
|
'stats_invalid_data' => $stats['invalid_data'],
|
|
'footer_html' => $footer_text,
|
|
];
|
|
|
|
$this->variables = $this->do_filter( 'analytics/email_report_variables', $this->variables );
|
|
}
|
|
|
|
/**
|
|
* Get date data.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_date() {
|
|
$period = self::get_period_from_frequency();
|
|
|
|
// Shift 3 days prior.
|
|
$subtract = DAY_IN_SECONDS * 3;
|
|
$start = strtotime( '-' . $period . ' days' ) - $subtract;
|
|
$end = strtotime( $this->do_filter( 'analytics/report_end_date', 'today' ) ) - $subtract;
|
|
|
|
$start = date_i18n( 'd M Y', $start );
|
|
$end = date_i18n( 'd M Y', $end );
|
|
return compact( 'start', 'end', 'period' );
|
|
}
|
|
|
|
/**
|
|
* Get Analytics stats.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_stats() {
|
|
$period = self::get_period_from_frequency();
|
|
$stats = Stats::get();
|
|
$stats->set_date_range( "-{$period} days" );
|
|
|
|
// Basic stats.
|
|
$data = (array) $stats->get_analytics_summary();
|
|
|
|
$analytics = get_option( 'rank_math_google_analytic_options' );
|
|
$is_analytics_connected = ! empty( $analytics ) && ! empty( $analytics['view_id'] );
|
|
|
|
$out = [];
|
|
|
|
$out['impressions'] = $data['impressions']['total'];
|
|
$out['impressions_diff'] = $data['impressions']['difference'];
|
|
|
|
$out['traffic'] = 0;
|
|
$out['traffic_diff'] = 0;
|
|
if ( $is_analytics_connected && defined( 'RANK_MATH_PRO_FILE' ) && isset( $data['pageviews'] ) ) {
|
|
$out['traffic'] = $data['pageviews']['total'];
|
|
$out['traffic_diff'] = $data['pageviews']['difference'];
|
|
}
|
|
|
|
$out['clicks'] = 0;
|
|
$out['clicks_diff'] = 0;
|
|
if ( ! $is_analytics_connected || ( $is_analytics_connected && ! defined( 'RANK_MATH_PRO_FILE' ) ) ) {
|
|
$out['clicks'] = $data['clicks']['total'];
|
|
$out['clicks_diff'] = $data['clicks']['difference'];
|
|
}
|
|
|
|
$out['keywords'] = $data['keywords']['total'];
|
|
$out['keywords_diff'] = $data['keywords']['difference'];
|
|
|
|
$out['position'] = $data['position']['total'];
|
|
$out['position_diff'] = $data['position']['difference'];
|
|
|
|
// Keyword stats.
|
|
$kw_data = (array) $stats->get_top_keywords();
|
|
|
|
$out['top_3_positions'] = $kw_data['top3']['total'];
|
|
$out['top_3_positions_diff'] = $kw_data['top3']['difference'];
|
|
|
|
$out['top_10_positions'] = $kw_data['top10']['total'];
|
|
$out['top_10_positions_diff'] = $kw_data['top10']['difference'];
|
|
|
|
$out['top_50_positions'] = $kw_data['top50']['total'];
|
|
$out['top_50_positions_diff'] = $kw_data['top50']['difference'];
|
|
|
|
$out['invalid_data'] = false;
|
|
if ( ! count( array_filter( $out ) ) ) {
|
|
$out['invalid_data'] = true;
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Get date period (days) from the frequency option.
|
|
*
|
|
* @param string $frequency Frequency string.
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function get_period_from_frequency( $frequency = null ) {
|
|
$periods = [
|
|
'monthly' => 30,
|
|
];
|
|
|
|
$periods = apply_filters( 'rank_math/analytics/email_report_periods', $periods );
|
|
|
|
if ( empty( $frequency ) ) {
|
|
$frequency = self::get_setting( 'frequency', 'monthly' );
|
|
}
|
|
|
|
if ( isset( $periods[ $frequency ] ) ) {
|
|
return absint( $periods[ $frequency ] );
|
|
}
|
|
|
|
return absint( reset( $periods ) );
|
|
}
|
|
|
|
/**
|
|
* Send report data.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function send_report() {
|
|
$account = Admin_Helper::get_registration_data();
|
|
$report_email = [
|
|
'to' => $account['email'],
|
|
'subject' => sprintf(
|
|
// Translators: placeholder is the site URL.
|
|
__( 'Rank Math [SEO Report] - %s', 'rank-math' ),
|
|
explode( '://', get_home_url() )[1]
|
|
),
|
|
'message' => $this->get_template( 'report' ),
|
|
'headers' => 'Content-Type: text/html; charset=UTF-8',
|
|
];
|
|
|
|
/**
|
|
* Filter: rank_math/analytics/email_report_parameters
|
|
* Filters the report email parameters.
|
|
*/
|
|
$report_email = $this->do_filter( 'analytics/email_report_parameters', $report_email );
|
|
|
|
wp_mail(
|
|
$report_email['to'],
|
|
$report_email['subject'],
|
|
$report_email['message'],
|
|
$report_email['headers']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get full HTML template for email.
|
|
*
|
|
* @param string $template Template name.
|
|
* @return string
|
|
*/
|
|
private function get_template( $template ) {
|
|
$file = $this->locate_template( $template );
|
|
|
|
/**
|
|
* Filter template file.
|
|
*/
|
|
$file = $this->do_filter( 'analytics/email_report_template', $file, $template );
|
|
|
|
if ( ! file_exists( $file ) ) {
|
|
return '';
|
|
}
|
|
|
|
ob_start();
|
|
include_once $file;
|
|
$content = ob_get_clean();
|
|
|
|
/**
|
|
* Filter template HTML.
|
|
*/
|
|
return $this->do_filter( 'analytics/email_report_html', $content );
|
|
}
|
|
|
|
/**
|
|
* Locate and include template part.
|
|
*
|
|
* @param string $part Template part.
|
|
* @param array $args Template arguments.
|
|
* @return mixed
|
|
*/
|
|
private function template_part( $part, $args = [] ) {
|
|
$file = $this->locate_template( $part );
|
|
|
|
/**
|
|
* Filter template part.
|
|
*/
|
|
$file = $this->do_filter( 'analytics/email_report_template_part', $file, $part, $args );
|
|
|
|
if ( ! file_exists( $file ) ) {
|
|
return '';
|
|
}
|
|
|
|
extract( $args, EXTR_SKIP ); // phpcs:ignore
|
|
include $file;
|
|
}
|
|
|
|
/**
|
|
* Replace variables in content.
|
|
*
|
|
* @param string $content Email content.
|
|
* @param string $recursion Recursion count, to account for double-encoded variables.
|
|
* @return string
|
|
*/
|
|
public function replace_variables( $content, $recursion = 1 ) {
|
|
foreach ( $this->variables as $key => $value ) {
|
|
if ( ! is_scalar( $value ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Variables must be uppercase.
|
|
$key = mb_strtoupper( $key );
|
|
|
|
$content = str_replace( "###$key###", $value, $content );
|
|
}
|
|
|
|
if ( $recursion ) {
|
|
$recursion--;
|
|
$content = $this->replace_variables( $content, $recursion );
|
|
}
|
|
|
|
return do_shortcode( $content );
|
|
}
|
|
|
|
/**
|
|
* Strip HTML & CSS comments.
|
|
*
|
|
* @param string $content Email content.
|
|
* @return string
|
|
*/
|
|
public function strip_comments( $content ) {
|
|
$content = preg_replace( '[(<!--(.*)-->|/\*(.*)\*/)]isU', '', $content );
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Init debug mode if requested and allowed.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function maybe_debug() {
|
|
if ( 1 !== absint( Param::get( 'rank_math_analytics_report_preview' ) ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! Helper::has_cap( 'analytics' ) ) {
|
|
return;
|
|
}
|
|
|
|
$send = boolval( Param::get( 'send' ) );
|
|
$values = boolval( Param::get( 'values', '1' ) );
|
|
|
|
$this->debug( $send, $values );
|
|
}
|
|
|
|
/**
|
|
* Send or output the report email.
|
|
*
|
|
* @param boolean $send Send email or output to browser.
|
|
* @param boolean $values Replace variables with actual values.
|
|
* @return void
|
|
*/
|
|
private function debug( $send = false, $values = true ) {
|
|
$this->debug = true;
|
|
|
|
if ( $values ) {
|
|
$this->setup_variables();
|
|
}
|
|
|
|
if ( $send ) {
|
|
// Send it now.
|
|
$this->send_report();
|
|
$url = remove_query_arg(
|
|
[
|
|
'rank_math_analytics_report_preview',
|
|
'send',
|
|
'values',
|
|
]
|
|
);
|
|
Helper::redirect( $url );
|
|
exit;
|
|
}
|
|
|
|
// Output it to the browser.
|
|
echo $this->get_template( 'report' ); // phpcs:ignore
|
|
die();
|
|
}
|
|
|
|
/**
|
|
* Variable getter, whenever the value is needed in PHP.
|
|
*
|
|
* @param string $name Variable name.
|
|
* @return mixed
|
|
*/
|
|
public function get_variable( $name ) {
|
|
if ( isset( $this->variables[ $name ] ) ) {
|
|
return $this->variables[ $name ];
|
|
}
|
|
|
|
return "###$name###";
|
|
}
|
|
|
|
/**
|
|
* Setting getter.
|
|
*
|
|
* @param string $option Option name.
|
|
* @param mixed $default Default value.
|
|
* @return mixed
|
|
*/
|
|
public static function get_setting( $option, $default = false ) {
|
|
return Helper::get_settings( 'general.console_email_' . $option, $default );
|
|
}
|
|
|
|
/**
|
|
* Output image inside the email template.
|
|
*
|
|
* @param string $url Image URL.
|
|
* @param string $width Image width.
|
|
* @param string $height Image height.
|
|
* @param string $alt ALT text.
|
|
* @param array $attr Additional attributes.
|
|
* @return void
|
|
*/
|
|
public function image( $url, $width = 0, $height = 0, $alt = '', $attr = [] ) {
|
|
$atts = $attr;
|
|
$atts['border'] = '0';
|
|
|
|
if ( ! isset( $atts['src'] ) ) {
|
|
$atts['src'] = $url;
|
|
}
|
|
|
|
if ( ! isset( $atts['width'] ) && $width ) {
|
|
$atts['width'] = $width;
|
|
}
|
|
|
|
if ( ! isset( $atts['height'] ) && $height ) {
|
|
$atts['height'] = $height;
|
|
}
|
|
|
|
if ( ! isset( $atts['alt'] ) ) {
|
|
$atts['alt'] = $alt;
|
|
}
|
|
|
|
if ( ! isset( $atts['style'] ) ) {
|
|
$atts['style'] = 'border: 0; outline: none; text-decoration: none; display: inline-block;';
|
|
}
|
|
|
|
if ( substr( $atts['src'], 0, 4 ) !== 'http' && substr( $atts['src'], 0, 3 ) !== '###' ) {
|
|
$atts['src'] = $this->assets_url . 'img/' . $atts['src'];
|
|
}
|
|
|
|
$atts = $this->do_filter( 'analytics/email_report_image_atts', $atts, $url, $width, $height, $alt, $attr );
|
|
|
|
$attributes = '';
|
|
foreach ( $atts as $name => $value ) {
|
|
if ( ! empty( $value ) ) {
|
|
$value = ( 'src' === $name ) ? esc_url_raw( $value ) : esc_attr( $value );
|
|
$attributes .= ' ' . $name . '="' . $value . '"';
|
|
}
|
|
}
|
|
|
|
$image = "<img $attributes>";
|
|
$image = $this->do_filter( 'analytics/email_report_image_html', $image, $url, $width, $height, $alt, $attr );
|
|
|
|
echo $image; // phpcs:ignore
|
|
}
|
|
|
|
/**
|
|
* Gets template path.
|
|
*
|
|
* @param string $template_name Template name.
|
|
* @param bool $return_full_path Return the full path or not.
|
|
* @return string
|
|
*/
|
|
public function locate_template( $template_name, $return_full_path = true ) {
|
|
$default_paths = [ $this->views_path ];
|
|
$template_paths = $this->do_filter( 'analytics/email_report_template_paths', $default_paths );
|
|
|
|
$paths = array_reverse( $template_paths );
|
|
$located = '';
|
|
$path_partial = '';
|
|
foreach ( $paths as $path ) {
|
|
if ( file_exists( $full_path = trailingslashit( $path ) . $template_name . '.php' ) ) { // phpcs:ignore
|
|
$located = $full_path;
|
|
$path_partial = $path;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $return_full_path ? $located : $path_partial;
|
|
}
|
|
|
|
/**
|
|
* Load all graph data into memory.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function load_graph_data() {
|
|
$period = self::get_period_from_frequency();
|
|
$stats = Stats::get();
|
|
$stats->set_date_range( "-{$period} days" );
|
|
$this->graph_data = (array) $stats->get_analytics_summary_graph();
|
|
}
|
|
|
|
/**
|
|
* Get data points for graph.
|
|
*
|
|
* @param string $chart Chart to get data for.
|
|
* @return array
|
|
*/
|
|
public function get_graph_data( $chart ) {
|
|
if ( empty( $this->graph_data ) ) {
|
|
$this->load_graph_data();
|
|
}
|
|
|
|
$data = [];
|
|
$group = 'merged';
|
|
$prop = $chart;
|
|
if ( 'traffic' === $chart ) {
|
|
$group = 'traffic';
|
|
$prop = 'pageviews';
|
|
}
|
|
|
|
if ( empty( $this->graph_data[ $group ] ) ) {
|
|
return $data;
|
|
}
|
|
|
|
foreach ( (array) $this->graph_data[ $group ] as $range_data ) {
|
|
$range_data = (array) $range_data;
|
|
if ( isset( $range_data[ $prop ] ) ) {
|
|
$data[] = $range_data[ $prop ];
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Charts API sign request.
|
|
*
|
|
* @param string $query Query.
|
|
* @param string $code Code.
|
|
* @return string
|
|
*/
|
|
private function charts_api_sign( $query, $code ) {
|
|
return hash_hmac( 'sha256', $query, $code );
|
|
}
|
|
|
|
/**
|
|
* Generate URL for the Charts API image.
|
|
*
|
|
* @param array $graph_data Graph data points.
|
|
* @param int $width Image height.
|
|
* @param int $height Image width.
|
|
*
|
|
* @return string
|
|
*/
|
|
private function charts_api_url( $graph_data, $width = 192, $height = 102 ) {
|
|
$params = [
|
|
'chco' => '80ace7',
|
|
'chds' => 'a',
|
|
'chf' => 'bg,s,f7f9fb',
|
|
'chls' => 4,
|
|
'chm' => 'B,e2eeff,0,0,0',
|
|
'chs' => "{$width}x{$height}",
|
|
'cht' => 'ls',
|
|
'chd' => 'a:' . join( ',', $graph_data ),
|
|
'icac' => $this->charts_account,
|
|
];
|
|
|
|
$query_string = urldecode( http_build_query( $params ) );
|
|
$signature = $this->charts_api_sign( $query_string, $this->charts_key );
|
|
|
|
return 'https://charts.rankmath.com/chart?' . $query_string . '&ichm=' . $signature;
|
|
}
|
|
|
|
/**
|
|
* Check if fields should be hidden.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function are_fields_hidden() {
|
|
return apply_filters( 'rank_math/analytics/hide_email_report_options', false );
|
|
}
|
|
}
|