PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.5.6
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.5.6
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Admin / ReportEmail.php

ReportEmail.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.5.6, at includes/Admin/ReportEmail.php

503 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- weekly/monthly analytics aggregation; runs once per email send.
3 namespace WPDeveloper\BetterDocs\Admin;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9
10 use WP_Error;
11 use WP_Query;
12 use stdClass;
13 use WPDeveloper\BetterDocs\Utils\Base;
14 use WPDeveloper\BetterDocs\Utils\Views;
15 use WPDeveloper\BetterDocs\Core\Settings;
16
17 /**
18 * This class is responsible for sending weekly email with reports
19 *
20 * @since 1.4.4
21 */
22 class ReportEmail extends Base {
23 /**
24 * Get a single Instance of Analytics
25 * @var Settings
26 */
27 public $settings;
28
29 private static $current_timestamp = null;
30 private static $current_date = null;
31
32 /**
33 * Initially Invoked by Default.
34 */
35 public function __construct( Settings $settings ) {
36 $this->settings = $settings;
37
38 add_action( 'betterdocs::settings::saved', [ $this, 'after_save_settings' ] );
39
40 if ( $this->settings->get( 'enable_reporting', false ) ) {
41 $this->init();
42 }
43 }
44
45 public function init() {
46 add_filter( 'cron_schedules', [ $this, 'schedules_cron' ] );
47 add_action( 'admin_init', [ $this, 'activate' ] );
48 add_action( 'betterdocs_weekly_email_reporting', [ $this, 'send_email' ] );
49 }
50
51 public function after_save_settings() {
52 if ( $this->settings->get( 'enable_reporting', false ) ) {
53 $this->activate();
54 } else {
55 $this->deactivate( true );
56 }
57 }
58
59 public function reporting( $request ) {
60 if ( ! boolval( $request->get_param( 'disable_reporting' ) ) ) {
61 if ( $request->has_param( 'reporting_email' ) ) {
62 $email = $request->get_param( 'reporting_email' );
63 }
64 $email = $this->receiver_email_address( $email );
65
66 if ( ! empty( $email ) ) {
67 $is_send = $this->send_email( $request->get_param( 'reporting_frequency' ), true, $email );
68 if ( $is_send && ! is_wp_error( $is_send ) ) {
69 return [ 'message' => __( 'Successfully Sent an Email', 'betterdocs' ) ];
70 } elseif ( is_wp_error( $is_send ) ) {
71 return $is_send;
72 } else {
73 return $this->error( 'betterdocs_unknown_reason', __( 'Email cannot be sent for some reason.', 'betterdocs' ) );
74 }
75 }
76 return $this->error( 'betterdocs_unknown_reason', __( 'Invalid email address.', 'betterdocs' ) );
77 } else {
78 return $this->error( 'betterdocs_disabled_reporting', __( 'You have to enable Reporting first.', 'betterdocs' ) );
79 }
80 }
81
82 public function error( $code, $message ) {
83 return new WP_Error( $code, $message );
84 }
85
86 private static function timestamps( $date = false ) {
87 if ( is_null( self::$current_timestamp ) ) {
88 self::$current_timestamp = current_time( 'timestamp' );
89 }
90 if ( $date ) {
91 if ( is_null( self::$current_date ) ) {
92 self::$current_date = current_time( 'Y-m-d' );
93 }
94 return self::$current_date;
95 }
96 return self::$current_timestamp;
97 }
98
99 public function create_date( $count = '-7days' ) {
100 return gmdate( 'Y-m-d', strtotime( $count, self::timestamps() ) );
101 }
102
103 public function get_views( $start_date, $end_date = null ) {
104 global $wpdb;
105 $query = $wpdb->get_results(
106 $wpdb->prepare(
107 "SELECT sum(impressions) as views, sum(unique_visit) as unique_visit, sum(happy + sad + normal) as reactions
108 FROM {$wpdb->prefix}betterdocs_analytics
109 WHERE created_at BETWEEN %s AND %s",
110 (string) $start_date,
111 (string) $end_date
112 )
113 );
114 return $query;
115 }
116
117 public function get_search( $start_date, $end_date = null ) {
118 global $wpdb;
119 $query = $wpdb->get_results(
120 $wpdb->prepare(
121 "SELECT SUM(count + not_found_count) as search_count, SUM(count) as search_found, SUM(not_found_count) as search_not_found_count
122 FROM {$wpdb->prefix}betterdocs_search_log as search_log
123 WHERE search_log.created_at BETWEEN %s AND %s",
124 (string) $start_date,
125 (string) $end_date
126 )
127 );
128
129 return $query;
130 }
131
132 public function count_new_docs( $start_date, $end_date = null ) {
133 $args = [
134 'post_type' => 'docs',
135 'post_status' => 'publish',
136 'posts_per_page' => -1,
137 'date_query' => [
138 [
139 'after' => $start_date,
140 'before' => $end_date,
141 'inclusive' => true
142 ]
143 ]
144 ];
145 $query = new WP_Query( $args );
146
147 return $query->found_posts;
148 }
149
150 public function get_search_keywords( $start_date, $end_date = null ) {
151 global $wpdb;
152 $query = $wpdb->get_results(
153 $wpdb->prepare(
154 "SELECT search_keyword.keyword, search_log.keyword_id,
155 SUM(search_log.count + search_log.not_found_count) as total_search,
156 SUM(search_log.count) as count,
157 SUM(search_log.not_found_count) as not_found
158 FROM {$wpdb->prefix}betterdocs_search_keyword as search_keyword
159 JOIN {$wpdb->prefix}betterdocs_search_log as search_log ON search_keyword.id = search_log.keyword_id
160 WHERE search_log.created_at BETWEEN %s AND %s
161 GROUP BY search_log.keyword_id
162 ORDER BY count DESC LIMIT 5",
163 (string) $start_date,
164 (string) $end_date
165 )
166 );
167
168 return $query;
169 }
170
171 public function get_docs_items( $request ) {
172 $prepared_post = new stdClass();
173
174 if ( isset( $request->ID ) ) {
175 $prepared_post->ID = $request->ID;
176 }
177
178 if ( isset( $request->post_title ) ) {
179 $prepared_post->title = betterdocs()->template_helper->kses( $request->post_title );
180 }
181
182 if ( isset( $request->total_views ) ) {
183 $prepared_post->total_views = $request->total_views;
184 }
185
186 if ( isset( $request->total_unique_visit ) ) {
187 $prepared_post->total_unique_visit = $request->total_unique_visit;
188 }
189
190 if ( isset( $request->total_reactions ) ) {
191 $prepared_post->total_reactions = $request->total_reactions;
192 }
193
194 if ( isset( $request->ID ) ) {
195 $prepared_post->link = get_permalink( $request->ID );
196 }
197
198 return $prepared_post;
199 }
200
201 public function get_leading_docs( $start_date, $end_date = null ) {
202 global $wpdb;
203
204 $query = $wpdb->get_results(
205 $wpdb->prepare(
206 "SELECT docs.ID, docs.post_author, docs.post_title,
207 SUM(analytics.impressions) as total_views,
208 SUM(analytics.unique_visit) as total_unique_visit,
209 SUM(analytics.happy + analytics.sad + analytics.normal) as total_reactions
210 FROM {$wpdb->posts} as docs
211 JOIN {$wpdb->prefix}betterdocs_analytics as analytics ON docs.ID = analytics.post_id
212 WHERE post_type = %s AND post_status = %s
213 AND analytics.created_at BETWEEN %s AND %s
214 GROUP BY analytics.post_id
215 ORDER BY total_views DESC LIMIT 5",
216 'docs',
217 'publish',
218 (string) $start_date,
219 (string) $end_date
220 )
221 );
222
223 $docs = [];
224 foreach ( $query as $key => $value ) {
225 $docs[ $key ] = $this->get_docs_items( $value );
226 }
227
228 return $docs;
229 }
230
231 /**
232 * Calculate Total BetterDocs Views
233 *
234 * @return array
235 */
236 public function get_data( $frequency = 'betterdocs_weekly' ) {
237 if ( $frequency == 'betterdocs_daily' ) {
238 $start_date = $this->create_date( 'last day' );
239 $end_date = $this->create_date( 'last day' );
240 $previous_start_date = $this->create_date( 'last day last day' );
241 $previous_end_date = $this->create_date( 'last day last day' );
242 }
243
244 if ( $frequency == 'betterdocs_weekly' ) {
245 $start_date = $this->create_date( '-7days' );
246 $end_date = $this->create_date( 'last day' );
247 $previous_start_date = $this->create_date( '-14days' );
248 $previous_end_date = $this->create_date( '-7days' );
249 }
250 if ( $frequency == 'betterdocs_monthly' ) {
251 $previous_start_date = $this->create_date( 'first day of last month last month' );
252 $previous_end_date = $this->create_date( 'last day of last month last month' );
253 $start_date = $this->create_date( 'first day of last month' );
254 $end_date = $this->create_date( 'last day of last month' );
255 }
256
257 $views_current_data = $this->get_views( $start_date, $end_date );
258 $views_previous_data = $this->get_views( $previous_start_date, $previous_end_date );
259 $search_current_data = $this->get_search( $start_date, $end_date );
260 $search_previous_data = $this->get_search( $previous_start_date, $previous_end_date );
261 $docs_current_data = $this->get_leading_docs( $start_date, $end_date );
262 $docs_total_current_reactions = array_sum( array_column( $docs_current_data, 'total_reactions' ) );
263 $search_keyword_data = $this->get_search_keywords( $start_date, $end_date );
264 $count_new_docs_current = $this->count_new_docs( $start_date, $end_date );
265 $count_new_docs_previous = $this->count_new_docs( $previous_start_date, $previous_end_date );
266
267 $from_date = $start_date;
268 $to_date = $frequency == 'betterdocs_daily' ? $start_date : $end_date;
269
270 $data = [
271 'views' => [
272 'from_date' => $from_date,
273 'to_date' => $to_date,
274 'current_data' => $views_current_data,
275 'previous_data' => $views_previous_data
276 ],
277 'search' => [
278 'from_date' => $from_date,
279 'to_date' => $to_date,
280 'current_data' => $search_current_data,
281 'previous_data' => $search_previous_data,
282 'keywords' => $search_keyword_data
283 ],
284 'new_docs' => [
285 'from_date' => $from_date,
286 'to_date' => $to_date,
287 'current_data' => $count_new_docs_current,
288 'previous_data' => $count_new_docs_previous
289 ],
290 'docs' => [
291 'from_date' => $from_date,
292 'to_date' => $to_date,
293 'current_data' => $docs_current_data,
294 'total_current_reactions' => $docs_total_current_reactions
295 ]
296 ];
297 return $data;
298 }
299
300 /**
301 * Adds a custom cron schedule for Weekly.
302 *
303 * @param array $schedules An array of non-default cron schedules.
304 * @return array Filtered array of non-default cron schedules.
305 */
306 function schedules_cron( $schedules = [] ) {
307 $schedules['betterdocs_weekly'] = [
308 'interval' => 604800,
309 'display' => __( 'Once Weekly', 'betterdocs' )
310 ];
311 $schedules['betterdocs_daily'] = [
312 'interval' => 86400,
313 'display' => __( 'Once Daily', 'betterdocs' )
314 ];
315 $schedules['betterdocs_monthly'] = [
316 'interval' => 2635200,
317 'display' => __( 'Once Monthly', 'betterdocs' )
318 ];
319 return $schedules;
320 }
321
322 /**
323 * Set Email Receiver mail address
324 * By Default, Admin Email Address
325 * Admin can set Custom email from NotificationX Advanced Settings Panel
326 *
327 * @return string|array<string>
328 */
329 public function receiver_email_address( $email = '' ) {
330 if ( empty( $email ) ) {
331 $email = $this->settings->get( 'reporting_email', '' );
332 if ( empty( $email ) ) {
333 $email = get_option( 'admin_email' );
334 }
335 }
336 if ( strpos( $email, ',' ) !== false ) {
337 $email = str_replace( ' ', '', $email );
338 $email = explode( ',', $email );
339 } else {
340 $email = trim( $email );
341 }
342 return $email;
343 }
344
345 /**
346 * Set Email Subject
347 * By Default, subject will be "Weekly Reporting for NotificationX"
348 * Admin can set Custom Subject from NotificationX Advanced Settings Panel
349 *
350 * @return string
351 */
352 public function email_subject() {
353 return wp_sprintf(
354 // Translators: %s is replaced with the website name.
355 __( 'Your Documentation Performance of %s Website', 'betterdocs' ),
356 get_bloginfo( 'name' )
357 );
358 }
359
360 /**
361 * Enable Cron Function
362 * Hook: admin_init
363 */
364 public function activate() {
365 $day = $this->settings->get( 'reporting_day', 'monday' );
366 $frequency = $this->settings->get( 'reporting_frequency', 'betterdocs_weekly' );
367
368 if ( $frequency === 'betterdocs_weekly' ) {
369 $datetime = strtotime( "next $day 9AM", current_time( 'timestamp' ) );
370 $this->schedule_event( $datetime, $frequency, 'betterdocs_weekly_email_reporting' );
371 }
372 }
373
374 /**
375 * Execute Cron Function
376 * Hook: admin_init
377 */
378 public function send_email( $frequency = 'betterdocs_weekly', $test = false, $email = null ) {
379 // if ( ! $this->settings->get( 'enable_analytics', false ) && ! $test ) {
380 // return $this->error( 'betterdocs_disabled_analytics', __( 'Analytics disabled. No data found.', 'betterdocs' ) );
381 // }
382
383 $data = $this->get_data( $frequency );
384 if ( empty( $data ) && ! $test ) {
385 return $this->error( 'betterdocs_no_reporting_data', __( 'No data found.', 'betterdocs' ) );
386 }
387
388 $to = null == $email ? $this->receiver_email_address() : $email;
389 if ( empty( $to ) ) {
390 return $this->error( 'betterdocs_reporting_email', __( 'No email found.', 'betterdocs' ) );
391 }
392
393 $subject = $this->email_subject();
394
395 ob_start();
396 betterdocs()->views->get( 'admin/email/header' );
397
398 betterdocs()->views->get(
399 'admin/email/body',
400 [
401 'days' => $this->frequency( $frequency ),
402 'bloginfo' => get_bloginfo( 'name' ),
403 'frequency' => $frequency,
404 'args' => $data,
405 'report_email' => $this
406 ]
407 );
408
409 betterdocs()->views->get( 'admin/email/footer' );
410 $message = ob_get_clean();
411 $admin_email = get_option( 'admin_email' );
412 $headers = [ 'Content-Type: text/html; charset=UTF-8', "From: BetterDocs <{$admin_email}>" ];
413
414 return wp_mail( $to, $subject, $message, $headers );
415 }
416
417 /**
418 * Disable Cron Function
419 * Hook: plugin_deactivation
420 */
421 public function deactivate( $clear_hook = 'betterdocs_weekly_email_reporting', $filter = null ) {
422 if ( is_string( $clear_hook ) ) {
423 $this->clear_scheduled_hook( $clear_hook );
424 } elseif ( $clear_hook === true ) {
425 $deactivate_hooks = [
426 'betterdocs_daily_email_reporting',
427 'betterdocs_weekly_email_reporting',
428 'betterdocs_monthly_email_reporting'
429 ];
430
431 array_walk(
432 $deactivate_hooks,
433 function ( $item ) use ( $filter ) {
434 if ( ! ( $filter !== null && $filter === $item ) ) {
435 $this->clear_scheduled_hook( $item );
436 }
437 }
438 );
439 }
440 }
441
442 public function schedule_event( $timestamp, $frequency, $hook ) {
443 if ( ! wp_next_scheduled( $hook ) ) {
444 wp_schedule_event( $timestamp, $frequency, $hook );
445
446 $this->deactivate( true, $hook );
447 }
448 }
449
450 public function clear_scheduled_hook( $hook ) {
451 wp_clear_scheduled_hook( $hook );
452 }
453
454 public function percentage( $previous, $current ) {
455 if ( $previous == 0 ) {
456 $factor = $current * 100;
457 } elseif ( $current == 0 ) {
458 $factor = $previous * 100;
459 } else {
460 $factor = ( ( (int) $current - (int) $previous ) / (int) $previous ) * 100;
461 }
462 return number_format( $factor, 2, '.', '' );
463 }
464
465 public function frequency( $frequency = 'betterdocs_weekly' ) {
466 switch ( $frequency ) {
467 case 'betterdocs_weekly':
468 $days_ago = '7 days';
469 break;
470 case 'betterdocs_daily':
471 $days_ago = '1 day';
472 break;
473 case 'betterdocs_monthly':
474 $initial_timestamp = strtotime( 'first day of last month', current_time( 'timestamp' ) );
475 $days_in_last_month = cal_days_in_month( CAL_GREGORIAN, (int) gmdate( 'm', $initial_timestamp ), (int) gmdate( 'Y', $initial_timestamp ) );
476 $days_ago = $days_in_last_month . ' days';
477 break;
478 }
479
480 return $days_ago;
481 }
482
483 public function test_email_report( $request ) {
484 $email = $this->send_email( $request->get_param( 'reporting_frequency' ), true, $request->get_param( 'reporting_email' ) );
485
486 $response = [
487 'status' => 'success',
488 'data' => [
489 'context' => [
490 'test_report' => $email
491 ]
492 ]
493 ];
494
495 if ( $email ) {
496 return $response;
497 } else {
498 $response['status'] = 'error';
499 return $response;
500 }
501 }
502 }
503