1211 lines
37 KiB
PHP
1211 lines
37 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Email Summaries main class.
|
|
*
|
|
* Handles the generation and sending of monthly email summaries containing
|
|
* analytics data and insights for the website.
|
|
*
|
|
* @package MonsterInsights
|
|
* @subpackage Emails
|
|
* @since 8.19.0
|
|
*/
|
|
class MonsterInsights_Email_Summaries {
|
|
/**
|
|
* The source URL for the email summaries.
|
|
* This will be used to compute the assets URL for the email summaries.
|
|
*
|
|
* @since 9.4.0
|
|
* @access public
|
|
* @var string
|
|
*/
|
|
const SOURCE_URL = 'https://plugin-cdn.monsterinsights.com';
|
|
|
|
/**
|
|
* The email template slug used for regular summary emails.
|
|
*
|
|
* @since 8.19.0
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $email_template = 'summaries';
|
|
|
|
/**
|
|
* The email template slug used for test summary emails.
|
|
*
|
|
* @since 8.19.0
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $test_email_template = 'summaries-test';
|
|
|
|
/**
|
|
* Stores email configuration options.
|
|
*
|
|
* Contains settings like enabled status, HTML template preference,
|
|
* carbon copy settings, recipient addresses, and header image.
|
|
*
|
|
* @since 8.19.0
|
|
* @access private
|
|
* @var array
|
|
*/
|
|
private $email_options;
|
|
|
|
/**
|
|
* Start date for summaries.
|
|
*
|
|
* @since 8.19.0
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $summary_start_date;
|
|
|
|
/**
|
|
* End date for summaries.
|
|
*
|
|
* @since 8.19.0
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $summary_end_date;
|
|
|
|
/**
|
|
* Whether email summaries are enabled.
|
|
*
|
|
* @since 9.4.0
|
|
* @access private
|
|
* @var boolean
|
|
*/
|
|
private $is_enabled;
|
|
|
|
/**
|
|
* Initialize the email summaries functionality.
|
|
*
|
|
* Sets up options, hooks, and schedules the monthly cron job
|
|
* for sending summary emails.
|
|
*
|
|
* @since 8.19.0
|
|
*/
|
|
public function __construct() {
|
|
$options = array();
|
|
$email_summaries = monsterinsights_get_option( 'email_summaries', 'on' );
|
|
$options['email_summaries'] = $email_summaries;
|
|
$options['summaries_html_template'] = monsterinsights_get_option( 'summaries_html_template', 'yes' );
|
|
$options['summaries_carbon_copy'] = 'no';
|
|
$options['summaries_email_addresses'] = array(get_option('admin_email'));
|
|
$options['summaries_header_image'] = false;
|
|
|
|
$this->email_options = $options;
|
|
$this->hooks();
|
|
|
|
// Remove weekly cron job.
|
|
wp_clear_scheduled_hook( 'monsterinsights_email_summaries_weekly' );
|
|
|
|
// Schedule or clear Monthly cron job.
|
|
if ( ! empty( $email_summaries ) && 'on' !== $email_summaries && wp_next_scheduled( 'monsterinsights_email_summaries_cron' ) ) {
|
|
wp_clear_scheduled_hook( 'monsterinsights_email_summaries_cron' );
|
|
}
|
|
|
|
if ( ! empty( $email_summaries ) && 'on' === $email_summaries && ! wp_next_scheduled( 'monsterinsights_email_summaries_cron' ) ) {
|
|
wp_schedule_event( $this->get_first_cron_date(), 'monsterinsights_email_summaries_monthly', 'monsterinsights_email_summaries_cron' );
|
|
}
|
|
|
|
$this->summary_start_date = $this->get_summaries_start_date();
|
|
$this->summary_end_date = $this->get_summaries_end_date();
|
|
}
|
|
|
|
/**
|
|
* Register hooks and callbacks for email summaries.
|
|
*
|
|
* Sets up admin scripts, preview functionality, template paths,
|
|
* cron schedules and other required hooks.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*/
|
|
public function hooks() {
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
|
|
|
|
if ( ! empty( $this->email_options['email_summaries'] ) && 'on' === $this->email_options['email_summaries'] ) {
|
|
add_action( 'init', array( $this, 'preview' ) );
|
|
add_filter( 'monsterinsights_email_template_paths', array( $this, 'add_email_template_path' ) );
|
|
add_filter( 'monsterinsights_emails_templates_set_initial_args', array( $this, 'set_template_args' ) );
|
|
add_filter( 'cron_schedules', array( $this, 'add_monthly_cron_schedule' ) );
|
|
add_action( 'monsterinsights_email_summaries_cron', array( $this, 'cron' ) );
|
|
add_action( 'wp_ajax_monsterinsights_send_test_email', array( $this, 'send_test_email' ) );
|
|
add_action( 'monsterinsights_after_update_settings', array(
|
|
$this,
|
|
'reset_email_summaries_options'
|
|
), 10, 2 );
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Enqueue required admin scripts for email summaries.
|
|
*
|
|
* Loads the WordPress media uploader scripts when on the settings page.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*/
|
|
public function admin_scripts() {
|
|
if ( monsterinsights_is_settings_page() ) {
|
|
// This will load the required dependencies for the WordPress media uploader
|
|
wp_enqueue_media();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if email summaries functionality is enabled.
|
|
*
|
|
* Verifies that summaries are enabled in settings and all required
|
|
* data and recipients are configured.
|
|
*
|
|
* @since 8.19.0
|
|
* @access protected
|
|
*
|
|
* @return boolean True if email summaries are enabled and properly configured.
|
|
*/
|
|
protected function is_enabled() {
|
|
if ( ! isset( $this->is_enabled ) ) {
|
|
$this->is_enabled = false;
|
|
|
|
if ( ! $this->is_preview() ) {
|
|
|
|
$info_block = new MonsterInsights_Summaries_InfoBlocks();
|
|
$info_block = $info_block->fetch_data();
|
|
$email_addresses = $this->get_email_addresses();
|
|
|
|
if ( ! empty( $info_block ) ) {
|
|
if ( 'on' === $this->email_options['email_summaries'] && ! empty( $email_addresses ) && true === $info_block['status'] ) {
|
|
$this->is_enabled = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return apply_filters( 'monsterinsights_emails_summaries_is_enabled', $this->is_enabled );
|
|
}
|
|
|
|
/**
|
|
* Preview the email summary template.
|
|
*
|
|
* Generates and displays a preview of the email summary template for administrators.
|
|
* Handles both HTML and plain text template formats.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*/
|
|
public function preview() {
|
|
|
|
if ( ! current_user_can( 'monsterinsights_save_settings' ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! $this->is_preview() ) {
|
|
return;
|
|
}
|
|
|
|
// initiate email class.
|
|
$emails = new MonsterInsights_WP_Emails( $this->email_template );
|
|
|
|
// check if html template option is enabled
|
|
if ( ! $this->is_enabled_html_template() ) {
|
|
$emails->__set( 'html', false );
|
|
}
|
|
|
|
$content = $emails->build_email();
|
|
|
|
if ( ! $this->is_enabled_html_template() ) {
|
|
$content = wpautop( $content );
|
|
}
|
|
|
|
echo $content; // phpcs:ignore
|
|
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Check if the current request is for an email preview.
|
|
*
|
|
* Determines if the current page request is for previewing an email summary template.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @return boolean True if current request is for email preview.
|
|
*/
|
|
public function is_preview() {
|
|
if ( isset( $_GET['monsterinsights_email_preview'], $_GET['monsterinsights_email_template'] ) && 'summary' === $_GET['monsterinsights_email_template'] ) { // phpcs:ignore
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the email header logo.
|
|
*
|
|
* @return string The email header logo.
|
|
* @since 8.19.0
|
|
*/
|
|
public function get_header_logo() {
|
|
$default = self::SOURCE_URL . '/assets/img/logo-monsterinsights-white.png';
|
|
|
|
return apply_filters( 'monsterinsights_email_header_logo', $default );
|
|
}
|
|
|
|
/**
|
|
* Get the email header image.
|
|
* This is deprecated and will be removed in a future version.
|
|
*
|
|
* @return string The email from address.
|
|
* @since 8.19.0
|
|
* @deprecated 9.4.0 Use {@see 'monsterinsights_email_header_logo'} instead.
|
|
*
|
|
*/
|
|
public function get_header_image() {
|
|
$new_logo = $this->get_header_logo();
|
|
|
|
// keep this for backwards compatibility.
|
|
$img = array(
|
|
'url' => $new_logo,
|
|
'2x' => $new_logo,
|
|
);
|
|
|
|
if ( ! empty( $this->email_options['summaries_header_image'] ) ) {
|
|
$img['url'] = $this->email_options['summaries_header_image'];
|
|
$img['2x'] = '';
|
|
}
|
|
|
|
/**
|
|
* Filters the logo image for the email header.
|
|
*
|
|
* @since 8.19.0
|
|
* @deprecated 9.4.0 Use {@see 'monsterinsights_email_header_logo'} instead.
|
|
*
|
|
* @param array $img The email header logo.
|
|
*/
|
|
$old_value = apply_filters_deprecated(
|
|
'monsterinsights_email_header_image',
|
|
$img,
|
|
'9.4.0',
|
|
'monsterinsights_email_header_logo',
|
|
__( 'This filter is deprecated. Use monsterinsights_email_header_logo instead.' )
|
|
);
|
|
|
|
return $old_value;
|
|
}
|
|
|
|
/**
|
|
* Calculate the first cron execution date.
|
|
*
|
|
* Generates a random future date for the first email summary cron job.
|
|
* Randomizes day, hour, minute and second to distribute load.
|
|
*
|
|
* @since 8.19.0
|
|
* @access protected
|
|
*
|
|
* @return int Unix timestamp for first cron execution.
|
|
*/
|
|
protected function get_first_cron_date() {
|
|
$schedule = array();
|
|
$schedule['day'] = wp_rand( 0, 1 );
|
|
$schedule['hour'] = wp_rand( 0, 23 );
|
|
$schedule['minute'] = wp_rand( 0, 59 );
|
|
$schedule['second'] = wp_rand( 0, 59 );
|
|
$schedule['offset'] = ( $schedule['day'] * DAY_IN_SECONDS ) +
|
|
( $schedule['hour'] * HOUR_IN_SECONDS ) +
|
|
( $schedule['minute'] * MINUTE_IN_SECONDS ) +
|
|
$schedule['second'];
|
|
$date = strtotime( 'next saturday' ) + $schedule['offset'];
|
|
|
|
return $date;
|
|
}
|
|
|
|
/**
|
|
* Add the email templates directory path.
|
|
*
|
|
* Adds the plugin's email templates directory to the template search path.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @param array $file_paths Existing template file paths.
|
|
* @return array Modified template file paths.
|
|
*/
|
|
public function add_email_template_path( $file_paths ) {
|
|
$file_paths['1000'] = MONSTERINSIGHTS_PLUGIN_DIR . 'lite/includes/emails/templates';
|
|
|
|
return $file_paths;
|
|
}
|
|
|
|
/**
|
|
* Register monthly cron schedule.
|
|
*
|
|
* Adds a monthly interval to WordPress cron schedules for email summaries.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @param array $schedules WordPress cron schedules.
|
|
* @return array Modified cron schedules with monthly interval added.
|
|
*/
|
|
public function add_monthly_cron_schedule( $schedules ) {
|
|
$schedules['monsterinsights_email_summaries_monthly'] = array(
|
|
'interval' => MONTH_IN_SECONDS,
|
|
'display' => esc_html__( 'Monthly MonsterInsights Email Summaries', 'google-analytics-for-wordpress' ),
|
|
);
|
|
|
|
return $schedules;
|
|
}
|
|
|
|
/**
|
|
* Get the email subject line.
|
|
*
|
|
* Generates the email subject line including the site domain name.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @return string Formatted email subject line.
|
|
*/
|
|
public function get_email_subject() {
|
|
$site_url = get_site_url();
|
|
$site_url_parsed = parse_url( $site_url );// Can't use wp_parse_url as that was added in WP 4.4 and we still support 3.8.
|
|
$site_url = isset( $site_url_parsed['host'] ) ? $site_url_parsed['host'] : $site_url;
|
|
|
|
// Translators: The domain of the site is appended to the subject.
|
|
$subject = sprintf( __( 'MonsterInsights Summary - %s', 'google-analytics-for-wordpress' ), $site_url );
|
|
|
|
return apply_filters( 'monsterinsights_emails_summaries_cron_subject', $subject );
|
|
}
|
|
|
|
/**
|
|
* Get configured email recipient addresses.
|
|
*
|
|
* Retrieves the list of email addresses that should receive the summary emails.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @return array List of recipient email addresses.
|
|
*/
|
|
public function get_email_addresses() {
|
|
$emails = $this->email_options['summaries_email_addresses'];
|
|
return apply_filters( 'monsterinsights_email_addresses_to_send', $emails );
|
|
}
|
|
|
|
/**
|
|
* Check if carbon copy (CC) is enabled for email summaries.
|
|
*
|
|
* Determines if recipients should be CC'd on the email summaries
|
|
* rather than being added as individual recipients.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @return boolean True if CC is enabled, false otherwise.
|
|
*/
|
|
public function is_cc_enabled() {
|
|
$value = false;
|
|
if ( 'yes' === $this->email_options['summaries_carbon_copy'] ) {
|
|
$value = true;
|
|
}
|
|
|
|
return apply_filters( 'monsterinsights_email_cc_enabled', $value, $this );
|
|
}
|
|
|
|
/**
|
|
* Check if HTML email template is enabled.
|
|
*
|
|
* Determines if email summaries should be sent in HTML format
|
|
* rather than plain text.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @return boolean True if HTML template is enabled, false otherwise.
|
|
*/
|
|
public function is_enabled_html_template() {
|
|
$value = true;
|
|
if ( 'no' === $this->email_options['summaries_html_template'] ) {
|
|
$value = false;
|
|
}
|
|
|
|
return apply_filters( 'monsterinsights_email_html_template', $value, $this );
|
|
}
|
|
|
|
/**
|
|
* Process and send the scheduled email summary.
|
|
*
|
|
* Handles the monthly cron job that generates and sends email summaries.
|
|
* Checks authentication status and processes data before sending.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*/
|
|
public function cron() {
|
|
|
|
if ( ! $this->is_enabled() ) {
|
|
return;
|
|
}
|
|
|
|
if( !monsterinsights_is_authed() ){
|
|
return;
|
|
}
|
|
|
|
$email = array();
|
|
$email['subject'] = $this->get_email_subject();
|
|
$email['address'] = $this->get_email_addresses();
|
|
$email['address'] = array_map( 'sanitize_email', $email['address'] );
|
|
|
|
// Create new email.
|
|
$emails = new MonsterInsights_WP_Emails( $this->email_template );
|
|
|
|
// Maybe include CC.
|
|
if ( $this->is_cc_enabled() ) {
|
|
$emails->__set( 'cc', implode( ',', $this->get_email_addresses() ) );
|
|
}
|
|
|
|
// check if html template option is enabled
|
|
if ( ! $this->is_enabled_html_template() ) {
|
|
$emails->__set( 'html', false );
|
|
}
|
|
|
|
$info_blocks = new MonsterInsights_Summaries_InfoBlocks();
|
|
$next_block = $info_blocks->get_next();
|
|
|
|
// Go.
|
|
if( !empty( $email['address'] ) ){
|
|
foreach ( $email['address'] as $address ) {
|
|
$sent = $emails->send( trim( $address ), $email['subject'] );
|
|
|
|
if ( true === $sent && ! empty( $next_block ) ) {
|
|
$info_blocks->register_sent( $next_block );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a test email summary.
|
|
*
|
|
* Handles AJAX request to send a test email summary to verify configuration.
|
|
* Requires admin capabilities and nonce verification.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*/
|
|
public function send_test_email() {
|
|
// Run a security check first.
|
|
check_ajax_referer( 'mi-admin-nonce', 'nonce' );
|
|
|
|
if ( ! current_user_can( 'monsterinsights_save_settings' ) ) {
|
|
return;
|
|
}
|
|
|
|
$email = array();
|
|
$email['subject'] = '[Test email] MonsterInsights Summary';
|
|
$email['address'] = $this->get_email_addresses();
|
|
$email['address'] = array_map( 'sanitize_email', $email['address'] );
|
|
|
|
// Create new email.
|
|
$emails = new MonsterInsights_WP_Emails( $this->test_email_template );
|
|
|
|
// Maybe include CC.
|
|
if ( $this->is_cc_enabled() ) {
|
|
$emails->__set( 'cc', implode( ',', $this->get_email_addresses() ) );
|
|
}
|
|
|
|
// check if html template option is enabled
|
|
if ( ! $this->is_enabled_html_template() ) {
|
|
$emails->__set( 'html', false );
|
|
}
|
|
|
|
// Go.
|
|
if(!empty($email['address'])){
|
|
foreach ( $email['address'] as $address ) {
|
|
if ( ! $emails->send( trim( $address ), $email['subject'] ) ) {
|
|
wp_send_json_error();
|
|
}
|
|
}
|
|
}
|
|
wp_send_json_success();
|
|
}
|
|
|
|
/**
|
|
* Set up email template arguments.
|
|
*
|
|
* Prepares all the data and configuration needed for the email template,
|
|
* including header images, summaries data, and report features.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @param array $args Existing template arguments.
|
|
* @return array Modified template arguments with summary data.
|
|
*/
|
|
public function set_template_args( $args ) {
|
|
$summaries_data = $this->get_summaries(); // Get full summaries data
|
|
|
|
$args['header']['start_date'] = date( "F j, Y", strtotime( $this->summary_start_date ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- We want this to depend on the runtime timezone.
|
|
$args['header']['end_date'] = date( "F j, Y", strtotime( $this->summary_end_date ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- We want this to depend on the runtime timezone.
|
|
$args['header']['assets_url'] = self::SOURCE_URL;
|
|
$args['header']['reports_url'] = admin_url( 'admin.php?page=monsterinsights_reports' );
|
|
$args['header']['logo_link'] = monsterinsights_get_url( 'email-summary', 'email-header-logo', 'https://www.monsterinsights.com/' );
|
|
$args['body']['startDate'] = $this->get_summaries_start_date();
|
|
$args['body']['endDate'] = $this->get_summaries_end_date();
|
|
$args['body']['reports_url'] = admin_url( 'admin.php?page=monsterinsights_reports' );
|
|
|
|
if ( ! isset( $summaries_data['success'] ) || true !== $summaries_data['success'] || ! isset( $summaries_data['data'] ) ) {
|
|
return $args; // Return early if summaries data is not successful
|
|
}
|
|
|
|
$summaries_data = $summaries_data['data']; // Use only the 'data' part for simplified access
|
|
$summaries_data = $this->maybe_map_new_api_format( $summaries_data );
|
|
$args['body']['top_pages'] = $this->get_top_pages_from_summary_data( $summaries_data );
|
|
$args['body']['more_pages_url'] = $this->get_more_pages_report_link( $summaries_data );
|
|
$args['body']['update_available'] = $this->plugin_has_update_notice();
|
|
|
|
$args['footer']['settings_tab_url'] = esc_url( admin_url( 'admin.php?page=monsterinsights_settings#/advanced' ) );
|
|
|
|
$info_blocks = new MonsterInsights_Summaries_InfoBlocks();
|
|
$next_block = $info_blocks->get_next();
|
|
$default_block = $info_blocks->get_default_block_data();
|
|
// A default url for the blog posts source.
|
|
$args['body']['blog_posts_url'] = 'https://www.monsterinsights.com/blog/';
|
|
|
|
if ( ! empty( $default_block['blog_posts_source'] ) ) {
|
|
$args['body']['blog_posts_url'] = $default_block['blog_posts_source'];
|
|
}
|
|
|
|
$args['body']['blog_posts'] = $this->get_latest_blog_posts_from_feed( $default_block );
|
|
$args['body']['report_title'] = $next_block['title'];
|
|
$args['body']['report_description'] = $next_block['html'];
|
|
$args['body']['report_link'] = $next_block['link_url'];
|
|
$args['body']['report_button_text'] = $next_block['link_text'];
|
|
$args['body']['report_features'] = $this->get_report_features( $next_block );
|
|
$args['body']['report_stats'] = $this->get_report_stats( $next_block, $summaries_data );
|
|
|
|
if ( $this->is_enabled_html_template() ) {
|
|
$args['header']['header_image'] = $this->get_header_logo();
|
|
$args['header']['header_background'] = self::SOURCE_URL . '/assets/img/header-background-monsterinsights.png';
|
|
$args['body']['report_image_src'] = $next_block['featured_image'];
|
|
$args['footer']['left_image'] = self::SOURCE_URL . '/assets/img/logo-monsterinsights-long.png';
|
|
$args['footer']['logo_image'] = self::SOURCE_URL . '/assets/img/logo-monsterinsights-small.png';
|
|
}
|
|
|
|
$args['footer']['facebook_url'] = 'https://www.facebook.com/monsterinsights';
|
|
$args['footer']['linkedin_url'] = 'https://www.linkedin.com/company/awesome-motive-inc./';
|
|
$args['footer']['logo_link'] = monsterinsights_get_url( 'email-summary', 'email-footer-logo', 'https://www.monsterinsights.com/' );
|
|
|
|
return apply_filters( 'monsterinsights_email_summaries_template_args', $args );
|
|
}
|
|
|
|
/**
|
|
* Get the start date for the summary period.
|
|
*
|
|
* Returns the first day of the previous month as the start date
|
|
* for gathering analytics data.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @return string Date in Y-m-d format.
|
|
*/
|
|
public function get_summaries_start_date() {
|
|
if ( ! isset( $this->summary_start_date ) ) {
|
|
$this->summary_start_date = date( 'Y-m-d', strtotime( 'first day of last month' ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- We want this to depend on the runtime timezone.
|
|
}
|
|
|
|
return $this->summary_start_date;
|
|
}
|
|
|
|
/**
|
|
* Get the end date for the summary period.
|
|
*
|
|
* Returns the last day of the previous month as the end date
|
|
* for gathering analytics data.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @return string Date in Y-m-d format.
|
|
*/
|
|
public function get_summaries_end_date() {
|
|
if ( ! isset( $this->summary_end_date ) ) {
|
|
$this->summary_end_date = date( 'Y-m-d', strtotime( 'last day of last month' ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- We want this to depend on the runtime timezone.
|
|
}
|
|
|
|
return $this->summary_end_date;
|
|
}
|
|
|
|
/**
|
|
* Retrieve analytics data for email summaries.
|
|
*
|
|
* Fetches and processes analytics report data for the summary period.
|
|
* Handles permissions and data formatting.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @return array {
|
|
* Analytics data array.
|
|
*
|
|
* @type boolean $success Whether data was retrieved successfully.
|
|
* @type array $data The analytics data for the summary.
|
|
* }
|
|
*/
|
|
public function get_summaries() {
|
|
$data = array();
|
|
|
|
// get overview report data for email summaries template
|
|
$report_name = 'summaries';
|
|
$report = MonsterInsights()->reporting->get_report( $report_name );
|
|
|
|
if ( is_wp_error( $report ) ) {
|
|
return array(
|
|
'success' => false,
|
|
'data' => array(),
|
|
'error' => $report->get_error_message(),
|
|
);
|
|
}
|
|
|
|
$isnetwork = ! empty( $_REQUEST['isnetwork'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['isnetwork'] ) ) : '';
|
|
|
|
// get the data of last month
|
|
$args = array(
|
|
'start' => $this->summary_start_date,
|
|
'end' => $this->summary_end_date,
|
|
);
|
|
|
|
if ( $isnetwork ) {
|
|
$args['network'] = true;
|
|
}
|
|
|
|
if ( monsterinsights_is_pro_version() && ! MonsterInsights()->license->license_can( $report->level ) ) {
|
|
$data = array(
|
|
'success' => false,
|
|
'message' => __( "You don't have permission to view MonsterInsights reports.", 'google-analytics-for-wordpress' ),
|
|
);
|
|
} else {
|
|
$data = apply_filters( 'monsterinsights_vue_reports_data', $report->get_data( $args ), $report_name, $report );
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Get the list of report features.
|
|
*
|
|
* Returns either default features or remote data features if available.
|
|
*
|
|
* @since 9.4.0
|
|
* @access private
|
|
*
|
|
* @param array $remote_data Remote feature data if available.
|
|
* @return array List of report features.
|
|
*/
|
|
private function get_report_features( $remote_data ) {
|
|
$default_features = array(
|
|
__( 'Overall Score', 'google-analytics-for-wordpress' ),
|
|
__( 'Total Blocking Time', 'google-analytics-for-wordpress' ),
|
|
__( 'Time to Interactive', 'google-analytics-for-wordpress' ),
|
|
__( 'First Contentful Paint', 'google-analytics-for-wordpress' ),
|
|
__( 'Benchmarks', 'google-analytics-for-wordpress' ),
|
|
__( 'Server Response Time', 'google-analytics-for-wordpress' )
|
|
);
|
|
|
|
if ( ! empty( $remote_data ) && ! empty( $remote_data['features'] ) ) {
|
|
return $remote_data['features'];
|
|
}
|
|
|
|
return $default_features;
|
|
}
|
|
|
|
/**
|
|
* Check if a plugin update is available.
|
|
*
|
|
* Verifies if there's a new version of the plugin available for update.
|
|
*
|
|
* @since 8.19.0
|
|
* @access private
|
|
*
|
|
* @param boolean $force_update Whether to force a check with WordPress.org.
|
|
* @return boolean True if an update is available, false otherwise.
|
|
*/
|
|
private function plugin_has_update_notice( $force_update = false ) {
|
|
$plugin_slug = plugin_basename( MonsterInsights_Lite::get_instance()->file );
|
|
|
|
if ( $force_update === true ) {
|
|
wp_update_plugins();
|
|
}
|
|
|
|
// Get the update_plugins transient
|
|
$update_plugins = get_site_transient('update_plugins');
|
|
|
|
if (isset($update_plugins->response[$plugin_slug])) {
|
|
// There is a new version available
|
|
return true;
|
|
}
|
|
|
|
// No new version available
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the report stats.
|
|
*
|
|
* Returns an array of report stats.
|
|
*
|
|
* @since 9.4.0
|
|
* @access private
|
|
*
|
|
* @param array $data Analytics data array.
|
|
* @param array $summaries_data Summaries data array.
|
|
* @return array Report stats array.
|
|
*/
|
|
private function get_report_stats( $data, $summaries_data ) {
|
|
// Icons are stored as unicode characters in the summaries assets. Check the plugin.monsterinsights.com documentation for more info.
|
|
$stats = array(
|
|
'sessions' => array(
|
|
'label' => __( 'Number of Sessions', 'google-analytics-for-wordpress' ),
|
|
'icon' => '',
|
|
),
|
|
'views' => array(
|
|
'label' => __( 'Number of Page Views', 'google-analytics-for-wordpress' ),
|
|
'icon' => '',
|
|
),
|
|
'avg_duration' => array(
|
|
'label' => __( 'Avg Session Duration', 'google-analytics-for-wordpress' ),
|
|
'icon' => '',
|
|
),
|
|
'bounce_rate' => array(
|
|
'label' => __( 'Bounce Rate', 'google-analytics-for-wordpress' ),
|
|
'icon' => '',
|
|
),
|
|
'nr_of_posts' => array(
|
|
'label' => __( 'Number of Blog Posts', 'google-analytics-for-wordpress' ),
|
|
'icon' => '',
|
|
),
|
|
'nr_of_pages' => array(
|
|
'label' => __( 'Number of Pages', 'google-analytics-for-wordpress' ),
|
|
'icon' => '',
|
|
)
|
|
);
|
|
|
|
$infobox = array();
|
|
|
|
if ( ! empty( $summaries_data ) && isset( $summaries_data['infobox'] ) ) {
|
|
$infobox = $summaries_data['infobox'];
|
|
}
|
|
|
|
foreach ( $stats as $key => $stat ) {
|
|
// Add default values for stats
|
|
$new_value = array(
|
|
'value' => 0,
|
|
'difference' => 0,
|
|
'trend_icon' => '↑',
|
|
'trend_class' => 'mset-text-increase',
|
|
);
|
|
|
|
switch ( $key ) {
|
|
case 'sessions':
|
|
if ( ! empty( $infobox ) && isset( $infobox['sessions'] ) ) {
|
|
$value = $this->roundThousandsNumber( $infobox['sessions']['value'] );
|
|
$new_value = $this->calculate_trend_data( $stat, $value, $infobox['sessions']['prev'] );
|
|
}
|
|
break;
|
|
case 'views':
|
|
if ( ! empty( $infobox ) && isset( $infobox['pageviews'] ) ) {
|
|
$value = $this->roundThousandsNumber( $infobox['pageviews']['value'] );
|
|
$new_value = $this->calculate_trend_data( $stat, $value, $infobox['pageviews']['prev'] );
|
|
}
|
|
|
|
break;
|
|
case 'nr_of_posts':
|
|
$current_count = $this->get_posts_count_by_date_range();
|
|
$prev_count = $this->get_posts_count_by_date_range( 'post', 'first day of -2 months', 'last day of -2 months' );
|
|
$difference = $this->calculate_percentage_difference( $current_count, $prev_count );
|
|
$new_value = $this->calculate_trend_data( $stat, $current_count, $difference );
|
|
break;
|
|
case 'nr_of_pages':
|
|
$current_count = $this->get_posts_count_by_date_range( 'page' );
|
|
$prev_count = $this->get_posts_count_by_date_range( 'page', 'first day of -2 months', 'last day of -2 months' );
|
|
$difference = $this->calculate_percentage_difference( $current_count, $prev_count );
|
|
$new_value = $this->calculate_trend_data( $stat, $current_count, $difference );
|
|
break;
|
|
case 'avg_duration':
|
|
if ( ! empty( $infobox ) && isset( $infobox['duration'] ) ) {
|
|
$value = $this->format_duration( $infobox['duration']['value'] );
|
|
$new_value = $this->calculate_trend_data( $stat, $value, $infobox['duration']['prev'] );
|
|
}
|
|
break;
|
|
case 'bounce_rate':
|
|
if ( ! empty( $infobox ) && isset( $infobox['bounceRate'] ) ) {
|
|
$value = number_format( (float)$infobox['bounceRate']['value'], 2 ) . '%';
|
|
$new_value = $this->calculate_trend_data( $stat, $value, $infobox['bounceRate']['prev'], true );
|
|
}
|
|
break;
|
|
}
|
|
|
|
$stats[$key] = array_merge($new_value, $stat);
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Calculate trend data for a metric
|
|
*
|
|
* @since 9.4.0
|
|
* @access private
|
|
*
|
|
* @param array $stat The stat array to modify.
|
|
* @param int $current_count Current period value.
|
|
* @param int $difference Percentage difference.
|
|
* @param bool $inverse Whether to invert the trend.
|
|
* @return array Trend data including difference, class and icon
|
|
*/
|
|
private function calculate_trend_data( $stat, $current_count, $difference, $inverse = false ) {
|
|
$is_increase = $difference >= 0;
|
|
|
|
$stat['difference'] = round( $difference );
|
|
$stat['trend_icon'] = $is_increase ? '↑' : '↓';
|
|
|
|
// Invert the trend color only if the inverse parameter is true. For example, a higher bounce rate is not a great thing, so we invert the color.
|
|
if ( $inverse ) {
|
|
$is_increase = ! $is_increase;
|
|
}
|
|
|
|
$stat['trend_class'] = $is_increase ? 'mset-text-increase' : 'mset-text-decrease';
|
|
$stat['value'] = $current_count;
|
|
|
|
return $stat;
|
|
}
|
|
|
|
/**
|
|
* Extract top 5 pages from summaries data.
|
|
*
|
|
* @since 9.4.0
|
|
* @access private
|
|
*
|
|
* @param array $summaries_data Summaries data array.
|
|
* @return array Top 5 pages data.
|
|
*/
|
|
private function get_top_pages_from_summary_data( $summaries_data ) {
|
|
if ( ! is_array( $summaries_data ) || ! isset( $summaries_data['toppages'] ) ) {
|
|
return array(); // Return empty array if data is invalid or missing
|
|
}
|
|
|
|
$top_pages_data = $summaries_data['toppages'];
|
|
$top_pages = array();
|
|
$count = 0;
|
|
|
|
foreach ( $top_pages_data as $page ) {
|
|
if ( $count >= 5 ) {
|
|
break; // Limit to top 5 pages
|
|
}
|
|
$top_pages[] = $page;
|
|
$count++;
|
|
}
|
|
|
|
return $top_pages;
|
|
}
|
|
|
|
/**
|
|
* Get the "View All Report" link for top pages.
|
|
*
|
|
* @since 9.4.0
|
|
* @access private
|
|
*
|
|
* @return string URL for the full Top Pages report.
|
|
*/
|
|
private function get_more_pages_report_link( $summaries_data ) {
|
|
// Use the direct link to the Top Posts report from the API data if available.
|
|
return isset( $summaries_data['galinks']['topposts'] ) ? esc_url_raw( $summaries_data['galinks']['topposts'] ) : admin_url( 'admin.php?page=monsterinsights_reports' );
|
|
}
|
|
|
|
/**
|
|
* Reset email summaries options to defaults.
|
|
*
|
|
* Resets email addresses and header image when summaries are disabled.
|
|
*
|
|
* @since 8.19.0
|
|
* @access public
|
|
*
|
|
* @param string $key The option key being updated.
|
|
* @param mixed $value The new value for the option.
|
|
*/
|
|
public function reset_email_summaries_options( $key, $value ) {
|
|
if ( isset( $key ) && $key === 'email_summaries' && isset( $value ) && $value === 'off' ) {
|
|
$default_email = array(
|
|
'email' => get_option( 'admin_email' ),
|
|
);
|
|
monsterinsights_update_option( 'summaries_email_addresses', array( $default_email ) );
|
|
monsterinsights_update_option( 'summaries_header_image', '' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the count of posts or pages within a specified date range.
|
|
*
|
|
* @access private
|
|
* @since 9.4.0
|
|
*
|
|
* @param string $post_type Post type to count ('post' or 'page'). Default is 'post'.
|
|
* @param string $start_date Start date for the range. Default is 'first day of this month'.
|
|
* @param string $end_date End date for the range. Default is 'last day of this month'.
|
|
*
|
|
* @return int Number of posts or pages in the given date range.
|
|
*/
|
|
private function get_posts_count_by_date_range( $post_type = 'post', $start_date = 'first day of this month', $end_date = 'last day of this month' ) {
|
|
$query_args = array(
|
|
'post_type' => $post_type,
|
|
'post_status' => 'publish',
|
|
'date_query' => array(
|
|
array(
|
|
'after' => date( 'Y-m-d H:i:s', strtotime( $start_date ) ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- We want this to depend on the runtime timezone.
|
|
'before' => date( 'Y-m-d H:i:s', strtotime( $end_date ) ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- We want this to depend on the runtime timezone.
|
|
'inclusive' => true,
|
|
),
|
|
),
|
|
'posts_per_page' => - 1, // Get all posts in the date range
|
|
'no_found_rows' => true, // Optimize query performance
|
|
);
|
|
|
|
$query = new WP_Query( $query_args );
|
|
return $query->post_count;
|
|
}
|
|
|
|
/**
|
|
* Calculate the percentage difference between two numbers.
|
|
*
|
|
* @since 9.4.0
|
|
* @access private
|
|
*
|
|
* @param int $current_count Current count.
|
|
* @param int $prev_count Previous count.
|
|
*
|
|
* @return float Percentage difference.
|
|
*/
|
|
private function calculate_percentage_difference( $current_count, $prev_count ) {
|
|
if ( ! $prev_count ) {
|
|
return 0; // Avoid division by zero
|
|
}
|
|
return ( ( $current_count - $prev_count ) / $prev_count ) * 100;
|
|
}
|
|
|
|
/**
|
|
* Get the latest blog posts from the MonsterInsights RSS feed.
|
|
*
|
|
* Fetches the RSS feed from monsterinsights.com and extracts the titles and links
|
|
* of the last 3 blog posts.
|
|
*
|
|
* @since 9.4.0
|
|
* @access private
|
|
*
|
|
* @return array Array of the latest 3 blog posts with title and link, or empty array on failure.
|
|
*/
|
|
private function get_latest_blog_posts_from_feed( $info_block ) {
|
|
$source = 'https://monsterinsights.com';
|
|
|
|
if ( ! empty( $info_block['blog_posts_source'] ) ) {
|
|
$source = $info_block['blog_posts_source'];
|
|
}
|
|
|
|
$rest_url = $source . '/wp-json/wp/v2/posts?per_page=3&_embed'; // Fetch last 3 posts and embed media
|
|
|
|
$response = wp_remote_get( $rest_url );
|
|
|
|
if ( is_wp_error( $response ) ) {
|
|
return array(); // Return empty array on error
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body( $response );
|
|
$posts_data = json_decode( $body, true );
|
|
$posts = array();
|
|
|
|
if ( empty( $posts_data ) || ! is_array( $posts_data ) ) {
|
|
return array();
|
|
}
|
|
|
|
foreach ( $posts_data as $post_item ) {
|
|
$featured_image_url = '';
|
|
|
|
if ( isset( $post_item['_embedded']['wp:featuredmedia'] ) && ! empty( $post_item['_embedded']['wp:featuredmedia'] ) ) {
|
|
$featured_media = $post_item['_embedded']['wp:featuredmedia'][0];
|
|
|
|
if ( isset( $featured_media['media_details']['sizes']['medium']['source_url'] ) ) {
|
|
$featured_image_url = $featured_media['media_details']['sizes']['medium']['source_url'];
|
|
}
|
|
}
|
|
|
|
if ( empty( $post_item['title']['rendered'] ) || empty( $post_item['link'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
$new = array(
|
|
'title' => esc_html( $post_item['title']['rendered'] ),
|
|
'link' => esc_url( $post_item['link'] ),
|
|
'excerpt' => ''
|
|
);
|
|
|
|
if ( ! empty( $post_item['excerpt']['rendered'] ) ) {
|
|
$new['excerpt'] = esc_html( wp_trim_words( strip_tags( $post_item['excerpt']['rendered'] ), 15, '...' ) );
|
|
}
|
|
|
|
if ( ! empty( $featured_image_url ) ) {
|
|
$new['featured_image'] = esc_url_raw( $featured_image_url );
|
|
}
|
|
|
|
$posts[] = $new;
|
|
}
|
|
|
|
return $posts;
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert 1000 to 1K, 1000000 to 1M.
|
|
*
|
|
* @param integer $num
|
|
* @since 9.4.0
|
|
*
|
|
* @return string
|
|
*/
|
|
public function roundThousandsNumber( $num ) {
|
|
$units = ['', 'K', 'M', 'B', 'T'];
|
|
|
|
for ($i = 0; $num >= 1000; $i++) {
|
|
$num /= 1000;
|
|
}
|
|
|
|
return round($num, 1) . $units[$i];
|
|
}
|
|
|
|
/**
|
|
* Map new API response format to the legacy format expected by get_report_stats()
|
|
* and get_top_pages_from_summary_data().
|
|
*
|
|
* The API now returns 'analytics_stats' and 'top_pages' instead of the legacy
|
|
* 'infobox' and 'toppages' keys. This method bridges the gap so existing
|
|
* template logic continues to work.
|
|
*
|
|
* @since 9.4.1
|
|
* @access private
|
|
*
|
|
* @param array $data The summaries data array from the API.
|
|
* @return array Data array with legacy keys populated when missing.
|
|
*/
|
|
private function maybe_map_new_api_format( $data ) {
|
|
if ( ! is_array( $data ) ) {
|
|
return $data;
|
|
}
|
|
|
|
// Map analytics_stats → infobox.
|
|
if ( empty( $data['infobox'] ) && ! empty( $data['analytics_stats'] ) ) {
|
|
$stats = $data['analytics_stats'];
|
|
|
|
$data['infobox'] = array(
|
|
'sessions' => array(
|
|
'value' => isset( $stats['sessions']['value'] ) ? $this->parse_formatted_number( $stats['sessions']['value'] ) : 0,
|
|
'prev' => isset( $stats['sessions']['percentage'] ) ? (float) $stats['sessions']['percentage'] : 0,
|
|
),
|
|
'pageviews' => array(
|
|
'value' => isset( $stats['pageviews']['value'] ) ? $this->parse_formatted_number( $stats['pageviews']['value'] ) : 0,
|
|
'prev' => isset( $stats['pageviews']['percentage'] ) ? (float) $stats['pageviews']['percentage'] : 0,
|
|
),
|
|
'duration' => array(
|
|
'value' => isset( $stats['avg_session_length']['value'] ) ? $stats['avg_session_length']['value'] : 0,
|
|
'prev' => isset( $stats['avg_session_length']['percentage'] ) ? (float) $stats['avg_session_length']['percentage'] : 0,
|
|
),
|
|
'bounceRate' => array(
|
|
'value' => isset( $stats['bounce_rate']['value'] ) ? $this->parse_formatted_number( $stats['bounce_rate']['value'] ) : 0,
|
|
'prev' => isset( $stats['bounce_rate']['percentage'] ) ? (float) $stats['bounce_rate']['percentage'] : 0,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Map top_pages → toppages.
|
|
if ( empty( $data['toppages'] ) && ! empty( $data['top_pages'] ) ) {
|
|
$base_url = get_site_url();
|
|
$data['toppages'] = array();
|
|
|
|
foreach ( $data['top_pages'] as $page ) {
|
|
$path = isset( $page['page_path'] ) ? '/' . ltrim( $page['page_path'], '/' ) : '';
|
|
|
|
$data['toppages'][] = array(
|
|
'title' => isset( $page['page_title'] ) ? (string) $page['page_title'] : $path,
|
|
'hostname' => $base_url,
|
|
'url' => $path,
|
|
'sessions' => isset( $page['page_views'] ) ? (float) $page['page_views'] : 0,
|
|
);
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Format a duration in seconds to a human-readable string (e.g. "1m 30s", "45s").
|
|
*
|
|
* @since 9.4.1
|
|
* @access private
|
|
*
|
|
* @param mixed $seconds Duration in seconds.
|
|
* @return string Formatted duration string.
|
|
*/
|
|
private function format_duration( $seconds ) {
|
|
$seconds = (int) round( (float) $seconds );
|
|
$minutes = (int) floor( $seconds / 60 );
|
|
$secs = $seconds % 60;
|
|
|
|
if ( $minutes > 0 ) {
|
|
return $minutes . 'm ' . $secs . 's';
|
|
}
|
|
|
|
return $secs . 's';
|
|
}
|
|
|
|
/**
|
|
* Parse a formatted number string (e.g. "4.1K", "1.2M") back to a numeric value.
|
|
*
|
|
* @since 9.4.1
|
|
* @access private
|
|
*
|
|
* @param mixed $value The value to parse.
|
|
* @return float Numeric value.
|
|
*/
|
|
private function parse_formatted_number( $value ) {
|
|
if ( is_numeric( $value ) ) {
|
|
return (float) $value;
|
|
}
|
|
|
|
if ( ! is_string( $value ) ) {
|
|
return 0;
|
|
}
|
|
|
|
$value = trim( $value );
|
|
|
|
if ( preg_match( '/^([\d.]+)\s*K$/i', $value, $m ) ) {
|
|
return (float) $m[1] * 1000;
|
|
}
|
|
|
|
if ( preg_match( '/^([\d.]+)\s*M$/i', $value, $m ) ) {
|
|
return (float) $m[1] * 1000000;
|
|
}
|
|
|
|
if ( preg_match( '/^([\d.]+)\s*B$/i', $value, $m ) ) {
|
|
return (float) $m[1] * 1000000000;
|
|
}
|
|
|
|
// Strip non-numeric chars (e.g. "%" suffix).
|
|
$cleaned = preg_replace( '/[^0-9.]/', '', $value );
|
|
|
|
return is_numeric( $cleaned ) ? (float) $cleaned : 0;
|
|
}
|
|
}
|