| 1 |
<?php |
| 2 |
namespace NotificationX\Admin\Reports; |
| 3 |
|
| 4 |
use NotificationX\Admin\Settings; |
| 5 |
use NotificationX\Core\Helper as NotificationX_Helper; |
| 6 |
use NotificationX\GetInstance; |
| 7 |
|
| 8 |
/** |
| 9 |
* This class is responsible for sending weekly email with reports |
| 10 |
* @method static ReportEmail get_instance($args = null) |
| 11 |
* |
| 12 |
* @since 1.4.4 |
| 13 |
*/ |
| 14 |
class ReportEmail { |
| 15 |
/** |
| 16 |
* Instance of Admin |
| 17 |
* |
| 18 |
* @var Admin |
| 19 |
*/ |
| 20 |
use GetInstance; |
| 21 |
|
| 22 |
/** |
| 23 |
* Get a single Instance of Analytics |
| 24 |
* @var Report_Email |
| 25 |
*/ |
| 26 |
public $settings = null; |
| 27 |
|
| 28 |
private static $current_timestamp = null; |
| 29 |
private static $current_date = null; |
| 30 |
|
| 31 |
/** |
| 32 |
* Initially Invoked by Default. |
| 33 |
*/ |
| 34 |
public function __construct() { |
| 35 |
$this->settings = Settings::get_instance()->get('settings'); |
| 36 |
if( !Settings::get_instance()->get('settings.enable_analytics', true) || Settings::get_instance()->get('settings.disable_reporting')) { |
| 37 |
$this->mail_report_deactivation( 'daily_email_reporting' ); |
| 38 |
$this->mail_report_deactivation( 'weekly_email_reporting' ); |
| 39 |
$this->mail_report_deactivation( 'monthly_email_reporting' ); |
| 40 |
return; |
| 41 |
} |
| 42 |
|
| 43 |
add_filter( 'cron_schedules', array( $this, 'schedules_cron' ) ); |
| 44 |
add_action('admin_init', array( $this, 'mail_report_activation' )); |
| 45 |
add_action('weekly_email_reporting', array( $this, 'send_email_weekly' )); |
| 46 |
} |
| 47 |
|
| 48 |
public function reporting( $request ){ |
| 49 |
if( ! boolval($request->get_param('disable_reporting')) ) { |
| 50 |
if( $request->has_param('reporting_email') ) { |
| 51 |
$email = $request->get_param( 'reporting_email' ); |
| 52 |
} |
| 53 |
$email = $this->receiver_email_address( $email ); |
| 54 |
|
| 55 |
if( ! empty( $email ) ) { |
| 56 |
$is_send = $this->send_email_weekly( $request->get_param('reporting_frequency'), true, $email ); |
| 57 |
if( $is_send && ! is_wp_error( $is_send ) ) { |
| 58 |
return [ 'message' => __( 'Successfully Sent an Email', 'notificationx' ) ]; |
| 59 |
} else if ( is_wp_error( $is_send ) ) { |
| 60 |
return $is_send; |
| 61 |
} else { |
| 62 |
new \WP_Error('nx_unknown_reason', __( 'Email cannot be sent for some reason.', 'notificationx' ) ); |
| 63 |
} |
| 64 |
} |
| 65 |
new \WP_Error('nx_unknown_reason', __( 'Invalid email address.', 'notificationx' ) ); |
| 66 |
} else { |
| 67 |
return new \WP_Error('nx_disabled_reporting', __( 'You have to enable Reporting first.', 'notificationx' ) ); |
| 68 |
} |
| 69 |
} |
| 70 |
|
| 71 |
private static function timestamps( $date = false ){ |
| 72 |
if( is_null( self::$current_timestamp ) ) { |
| 73 |
self::$current_timestamp = current_time('timestamp'); |
| 74 |
} |
| 75 |
if( $date ) { |
| 76 |
if( is_null( self::$current_date ) ) { |
| 77 |
self::$current_date = current_time('Y-m-d'); |
| 78 |
} |
| 79 |
return self::$current_date; |
| 80 |
} |
| 81 |
return self::$current_timestamp; |
| 82 |
} |
| 83 |
|
| 84 |
public function create_date($count = '-7days'){ |
| 85 |
return gmdate('Y-m-d', strtotime($count, self::timestamps())); |
| 86 |
} |
| 87 |
|
| 88 |
public function get_stats( $start_date, $end_date = null ){ |
| 89 |
global $wpdb; |
| 90 |
|
| 91 |
$extra_query = $wpdb->prepare( 'BETWEEN %s AND %s', $start_date, $end_date ); |
| 92 |
|
| 93 |
if ( is_null( $end_date ) ) { |
| 94 |
$extra_query = $wpdb->prepare( ' = %s', $start_date ); |
| 95 |
} |
| 96 |
|
| 97 |
$query = "SELECT MAIN.`nx_id`, MAIN.`title`, MAIN.`type`, STATS.`views`, STATS.`clicks`, STATS.`CTR` FROM ( SELECT P.`nx_id`, title, type FROM {$wpdb->prefix}nx_posts as P LEFT JOIN {$wpdb->prefix}nx_stats as S ON P.`nx_id` = S.`nx_id` GROUP BY P.nx_id ) AS MAIN INNER JOIN ( SELECT *, (clicks/views)*100 as ctr FROM ( SELECT SUM(views) as views, SUM(clicks) as clicks, nx_id FROM {$wpdb->prefix}nx_stats WHERE created_at $extra_query GROUP BY nx_id ) as VCID ) as STATS |
| 98 |
ON MAIN.`nx_id` = STATS.`nx_id`"; |
| 99 |
|
| 100 |
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- False positive: the query is prepared via $this->wpdb->prepare(), which this sniff does not recognise, and only $wpdb->prefix table names are interpolated. Audited 2026-07-16. |
| 101 |
return $wpdb->get_results( $query ); |
| 102 |
} |
| 103 |
/** |
| 104 |
* Calculate Total NotificationX Views |
| 105 |
* @return int |
| 106 |
*/ |
| 107 |
public function get_data( $frequency = 'nx_weekly'){ |
| 108 |
if( $frequency == 'nx_daily' ) { |
| 109 |
$start_date = $this->create_date('last day'); |
| 110 |
$end_date = null; |
| 111 |
$previous_start_date = $this->create_date('last day last day'); |
| 112 |
$previous_end_date = null; |
| 113 |
} |
| 114 |
|
| 115 |
if( $frequency == 'nx_weekly' ) { |
| 116 |
$start_date = $this->create_date('-7days'); |
| 117 |
$end_date = $this->create_date('last day'); |
| 118 |
$previous_start_date = $this->create_date('-14days'); |
| 119 |
$previous_end_date = $this->create_date('-7days'); |
| 120 |
} |
| 121 |
if( $frequency == 'nx_monthly' ) { |
| 122 |
$previous_start_date = $this->create_date('first day of last month last month'); |
| 123 |
$previous_end_date = $this->create_date('last day of last month last month'); |
| 124 |
$start_date = $this->create_date('first day of last month'); |
| 125 |
$end_date = $this->create_date('last day of last month'); |
| 126 |
} |
| 127 |
|
| 128 |
$current_data = $this->get_stats( $start_date, $end_date ); |
| 129 |
$previous_data = $this->get_stats( $previous_start_date, $previous_end_date ); |
| 130 |
|
| 131 |
$from_date = $previous_start_date; |
| 132 |
$to_date = $frequency == 'nx_daily' ? $start_date : $end_date; |
| 133 |
|
| 134 |
$data = [ |
| 135 |
'from_date' => $from_date, |
| 136 |
'to_date' => $to_date, |
| 137 |
'current_data' => $current_data, |
| 138 |
'previous_data' => $previous_data, |
| 139 |
]; |
| 140 |
|
| 141 |
return $this->generate_data( $data ); |
| 142 |
} |
| 143 |
|
| 144 |
private function percentage( $_last, $_current ){ |
| 145 |
return $_last > 0 ? number_format( ( ( $_current - $_last ) / $_last ) * 100, 2 ) : 0; |
| 146 |
} |
| 147 |
|
| 148 |
private function previous_data( &$_single_data, &$_data = null ){ |
| 149 |
if( empty( $_single_data ) ) { |
| 150 |
return [ |
| 151 |
'last_views' => 0, |
| 152 |
'percentage_views' => 0, |
| 153 |
'last_clicks' => 0, |
| 154 |
'percentage_clicks' => 0, |
| 155 |
'last_ctr' => 0, |
| 156 |
'percentage_ctr' => 0, |
| 157 |
]; |
| 158 |
} |
| 159 |
$_new_data = []; |
| 160 |
array_walk( $_single_data, function( $item , $key ) use( &$_data, &$_new_data ) { |
| 161 |
switch( $key ) { |
| 162 |
case 'views': |
| 163 |
$_new_data['last_views'] = $item; |
| 164 |
$_new_data['percentage_views'] = $this->percentage( $item, $_data['views'] ); |
| 165 |
break; |
| 166 |
case 'clicks': |
| 167 |
$_new_data['last_clicks'] = $item; |
| 168 |
$_new_data['percentage_clicks'] = $this->percentage( $item, $_data['clicks'] ); |
| 169 |
break; |
| 170 |
case 'ctr': |
| 171 |
$_new_data['last_ctr'] = $item; |
| 172 |
$_new_data['percentage_ctr'] = $this->percentage( $item, $_data['ctr'] ); |
| 173 |
break; |
| 174 |
} |
| 175 |
}); |
| 176 |
|
| 177 |
return $_new_data; |
| 178 |
} |
| 179 |
|
| 180 |
public function generate_data( &$data ){ |
| 181 |
if( empty( $data ) ) { |
| 182 |
return []; |
| 183 |
} |
| 184 |
|
| 185 |
// previous -> || (isset( $data['previous_data'] ) && empty( $data['previous_data'] ) |
| 186 |
if( (isset( $data['current_data'] ) && empty( $data['current_data'] ) ) ) { |
| 187 |
return []; |
| 188 |
} |
| 189 |
|
| 190 |
$current_data = []; |
| 191 |
$previous_data = []; |
| 192 |
array_walk( $data['previous_data'], function( $single ) use ( &$previous_data ){ |
| 193 |
$previous_data[ $single->nx_id ] = ( array ) $single; |
| 194 |
}); |
| 195 |
array_walk( $data['current_data'], function( $single ) use ( &$current_data, &$previous_data, $data ){ |
| 196 |
$_single_data = ( array ) $single; |
| 197 |
$_single_data['source'] = NotificationX_Helper::get_type_title( $single->type ); |
| 198 |
$_single_data['type'] = NotificationX_Helper::get_type_title( $single->type ); |
| 199 |
$_single_data['from_date'] = $data['from_date']; |
| 200 |
$_single_data['to_date'] = $data['to_date']; |
| 201 |
$_previous_data = []; |
| 202 |
if( isset( $previous_data[ $single->nx_id ] ) ) { |
| 203 |
$_previous_data = $this->previous_data( $previous_data[ $single->nx_id ], $_single_data ); |
| 204 |
} else { |
| 205 |
$_previous_data = $this->previous_data( $_previous_data, $_single_data ); |
| 206 |
} |
| 207 |
$current_data[ $single->nx_id ] = array_merge( $_single_data, $_previous_data ); |
| 208 |
}); |
| 209 |
|
| 210 |
return $current_data; |
| 211 |
} |
| 212 |
/** |
| 213 |
* Adds a custom cron schedule for Weekly. |
| 214 |
* |
| 215 |
* @param array $schedules An array of non-default cron schedules. |
| 216 |
* @return array Filtered array of non-default cron schedules. |
| 217 |
*/ |
| 218 |
function schedules_cron( $schedules = array() ) { |
| 219 |
$schedules['nx_weekly'] = array( |
| 220 |
'interval' => 604800, |
| 221 |
'display' => __( 'Once Weekly', 'notificationx' ) |
| 222 |
); |
| 223 |
$schedules['nx_daily'] = array( |
| 224 |
'interval' => 86400, |
| 225 |
'display' => __( 'Once Daily', 'notificationx' ) |
| 226 |
); |
| 227 |
$schedules['nx_monthly'] = array( |
| 228 |
'interval' => strtotime( 'first day of next month 9AM' ), |
| 229 |
'display' => __( 'Once Monthly', 'notificationx' ) |
| 230 |
); |
| 231 |
return $schedules; |
| 232 |
} |
| 233 |
|
| 234 |
/** |
| 235 |
* Set Email Receiver mail address |
| 236 |
* By Default, Admin Email Address |
| 237 |
* Admin can set Custom email from NotificationX Advanced Settings Panel |
| 238 |
* @return email||String |
| 239 |
*/ |
| 240 |
public function receiver_email_address( $email = '' ) { |
| 241 |
if( empty( $email ) ) { |
| 242 |
$email = Settings::get_instance()->get('settings.reporting_email' ); |
| 243 |
if( empty( $email ) ) { |
| 244 |
$email = get_option( 'admin_email' ); |
| 245 |
} |
| 246 |
} |
| 247 |
if( strpos( $email, ',' ) !== false ) { |
| 248 |
$email = str_replace( ' ', '', $email ); |
| 249 |
$email = explode(',', $email ); |
| 250 |
} else { |
| 251 |
$email = trim( $email ); |
| 252 |
} |
| 253 |
return $email; |
| 254 |
} |
| 255 |
|
| 256 |
/** |
| 257 |
* Set Email Subject |
| 258 |
* By Default, subject will be "Weekly Reporting for NotificationX" |
| 259 |
* Admin can set Custom Subject from NotificationX Advanced Settings Panel |
| 260 |
* @return subject||String |
| 261 |
*/ |
| 262 |
public function email_subject() { |
| 263 |
$site_name = get_bloginfo( 'name' ); |
| 264 |
/* translators: %s: site name */ |
| 265 |
$subject = sprintf( __( 'Weekly Engagement Summary of “%s”', 'notificationx' ), $site_name ); |
| 266 |
if( isset( $this->settings['reporting_subject'] ) && ! empty( $this->settings['reporting_subject'] ) ) { |
| 267 |
$subject = stripcslashes( $this->settings['reporting_subject'] ); |
| 268 |
} |
| 269 |
return $subject; |
| 270 |
} |
| 271 |
|
| 272 |
public function reporting_frequency(){ |
| 273 |
$frequency = Settings::get_instance()->get('settings.reporting_frequency', 'nx_weekly'); |
| 274 |
return $frequency; |
| 275 |
} |
| 276 |
|
| 277 |
/** |
| 278 |
* Enable Cron Function |
| 279 |
* Hook: admin_init |
| 280 |
*/ |
| 281 |
function mail_report_activation() { |
| 282 |
$day = "monday"; |
| 283 |
if( isset( $this->settings['reporting_day'] ) ) { |
| 284 |
$day = $this->settings['reporting_day']; |
| 285 |
} |
| 286 |
|
| 287 |
$frequency = $this->reporting_frequency(); |
| 288 |
if( $frequency === 'nx_weekly' ) { |
| 289 |
$datetime = strtotime( "next $day 9AM", current_time('timestamp') ); |
| 290 |
$triggered = Settings::get_instance()->get("reporting.mail_sent.$frequency", false); |
| 291 |
if ( $triggered == 1 ) { |
| 292 |
$this->mail_report_deactivation( 'weekly_email_reporting' ); |
| 293 |
} |
| 294 |
if( ! $triggered ) { |
| 295 |
$datetime = strtotime( "+1hour", current_time('timestamp') ); |
| 296 |
} |
| 297 |
$this->mail_report_deactivation( 'daily_email_reporting' ); |
| 298 |
$this->mail_report_deactivation( 'monthly_email_reporting' ); |
| 299 |
if ( ! wp_next_scheduled ( 'weekly_email_reporting' ) ) { |
| 300 |
wp_schedule_event( $datetime, $frequency, 'weekly_email_reporting' ); |
| 301 |
} |
| 302 |
} |
| 303 |
|
| 304 |
} |
| 305 |
|
| 306 |
/** |
| 307 |
* Execute Cron Function |
| 308 |
* Hook: admin_init |
| 309 |
*/ |
| 310 |
public function send_email_weekly( $frequency = 'nx_weekly', $test = false, $email = null ) { |
| 311 |
$data = $this->get_data( $frequency ); |
| 312 |
if( empty( $data ) ) { |
| 313 |
return new \WP_Error('nx_no_reporting_data', __('No data found.', 'notificationx')); |
| 314 |
} |
| 315 |
if( isset( $this->settings['enable_analytics'] ) && ! $this->settings['enable_analytics'] ) { |
| 316 |
return new \WP_Error('nx_disabled_analytics', __('Analytics disabled. No data found.', 'notificationx')); |
| 317 |
} |
| 318 |
$to = is_null( $email ) ? $this->receiver_email_address() : $email; |
| 319 |
if( empty( $to ) ) { |
| 320 |
return new \WP_Error('nx_reporting_email', __('No email found.', 'notificationx')); |
| 321 |
} |
| 322 |
|
| 323 |
$subject = $this->email_subject(); |
| 324 |
$template = new EmailTemplate(); |
| 325 |
$message = $template->template_body( $data, $frequency ); |
| 326 |
$headers = array( 'Content-Type: text/html; charset=UTF-8', "From: NotificationX <support@wpdeveloper.com>" ); |
| 327 |
if( ! $test ) { |
| 328 |
$triggered = Settings::get_instance()->get("reporting.mail_sent.$frequency"); |
| 329 |
$triggered = ! $triggered ? 0 : $triggered++; |
| 330 |
Settings::get_instance()->set("reporting.mail_sent.$frequency", $triggered); |
| 331 |
} |
| 332 |
return wp_mail( $to, $subject, $message, $headers ); |
| 333 |
} |
| 334 |
|
| 335 |
/** |
| 336 |
* Disable Cron Function |
| 337 |
* Hook: plugin_deactivation |
| 338 |
*/ |
| 339 |
public function mail_report_deactivation( $clear_hook = 'weekly_email_reporting' ) { |
| 340 |
wp_clear_scheduled_hook( $clear_hook ); |
| 341 |
} |
| 342 |
} |