| @@ -1,0 +1,342 @@ | ||
| 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 <[email protected]>" ); | |
| 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 | +} | |