Passed
Push — 283-fix-log-scheduling ( 796237 )
by Jonathan
05:31
created

Object_Sync_Sf_Logging::init()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 2
eloc 11
c 2
b 1
f 0
nc 2
nop 0
dl 0
loc 15
rs 9.9
1
<?php
2
/**
3
 * Class file for the Object_Sync_Sf_Logging class. Extend the WP_Logging class for the purposes of Object Sync for Salesforce.
4
 *
5
 * @file
6
 */
7
8
if ( ! class_exists( 'Object_Sync_Salesforce' ) ) {
9
	die();
10
}
11
12
/**
13
 * Log events based on plugin settings
14
 */
15
class Object_Sync_Sf_Logging extends WP_Logging {
16
17
	protected $wpdb;
18
	protected $version;
19
	protected $slug;
20
	protected $option_prefix;
21
22
	public $enabled;
23
	public $statuses_to_log;
24
25
	private $schedule_name;
26
27
28
	/**
29
	 * Constructor which sets content type and pruning for logs
30
	 *
31
	 * @param object $wpdb An instance of the wpdb class.
32
	 * @param string $version The version of this plugin.
33
	 * @param string $slug The plugin slug
34
	 * @param string $option_prefix The plugin's option prefix
35
	 * @throws \Exception
36
	 */
37
	public function __construct( $wpdb, $version, $slug = '', $option_prefix = '' ) {
38
		$this->wpdb          = $wpdb;
39
		$this->version       = $version;
40
		$this->slug          = $slug;
41
		$this->option_prefix = isset( $option_prefix ) ? $option_prefix : 'object_sync_for_salesforce_';
42
43
		$this->enabled         = get_option( $this->option_prefix . 'enable_logging', false );
44
		$this->statuses_to_log = get_option( $this->option_prefix . 'statuses_to_log', array() );
45
46
		$this->schedule_name = 'wp_logging_prune_routine';
47
48
		$this->capability = 'configure_salesforce';
0 ignored issues
show
Bug Best Practice introduced by
The property capability does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
49
50
		$this->init();
51
52
	}
53
54
	/**
55
	 * Start. This creates a schedule for pruning logs, and also the custom content type
56
	 *
57
	 * @throws \Exception
58
	 */
59
	private function init() {
60
		if ( true === filter_var( $this->enabled, FILTER_VALIDATE_BOOLEAN ) ) {
61
			add_filter( 'cron_schedules', array( $this, 'add_prune_interval' ) );
62
			add_filter( 'wp_log_types', array( $this, 'set_log_types' ), 10, 1 );
63
			add_filter( 'wp_logging_should_we_prune', array( $this, 'set_prune_option' ), 10, 1 );
64
			add_filter( 'wp_logging_prune_when', array( $this, 'set_prune_age' ), 10, 1 );
65
			add_filter( 'wp_logging_prune_query_args', array( $this, 'set_prune_args' ), 10, 1 );
66
			add_filter( 'wp_logging_post_type_args', array( $this, 'set_log_visibility' ), 10, 1 );
67
			add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_log_slug' ), 10, 5 );
68
69
			// when the schedule might change
70
			add_action( 'update_option_' . $this->option_prefix . 'logs_how_often_unit', array( $this, 'check_log_schedule' ), 10, 3 );
71
			add_action( 'update_option_' . $this->option_prefix . 'logs_how_often_number', array( $this, 'check_log_schedule' ), 10, 3 );
72
73
			$this->save_log_schedule();
74
		}
75
	}
76
77
	/**
78
	 * Set visibility for the post type
79
	 *
80
	 * @param array $log_args The post arguments
81
	 * @return array $log_args
82
	 */
83
	public function set_log_visibility( $log_args ) {
84
		// set public to true overrides the WP_DEBUG setting that is the default on the class
85
		// capabilities makes it so (currently) only admin users can see the log posts in their admin view
86
		// note: a public value of true is required to show Logs as a nav menu item on the admin.
87
		// however, if we don't set exclude_from_search to true and publicly_queryable to false, logs *can* appear in search results
88
		$log_args['public']              = true;
89
		$log_args['publicly_queryable']  = false;
90
		$log_args['exclude_from_search'] = true;
91
		$log_args['capabilities']        = array(
92
			'edit_post'          => $this->capability,
93
			'read_post'          => $this->capability,
94
			'delete_post'        => $this->capability,
95
			'edit_posts'         => $this->capability,
96
			'edit_others_posts'  => $this->capability,
97
			'delete_posts'       => $this->capability,
98
			'publish_posts'      => $this->capability,
99
			'read_private_posts' => $this->capability,
100
		);
101
102
		$log_args = apply_filters( $this->option_prefix . 'logging_post_type_args', $log_args );
103
104
		return $log_args;
105
	}
106
107
	/**
108
	 * Create a (probably unique) post name for logs in a more performant manner than wp_unique_post_slug().
109
	 *
110
	 * @param string $override_slug Short-circuit return value.
111
	 * @param string $slug The desired slug (post_name).
112
	 * @param int $post_ID The post ID
113
	 * @param string $post_status The post status
114
	 * @param string $post_type The post type
115
	 * @return string
116
	 */
117
	public function set_log_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
118
		if ( 'wp_log' === $post_type ) {
119
			$override_slug = uniqid( $post_type . '-', true ) . '-' . wp_generate_password( 32, false );
120
		}
121
		return $override_slug;
122
	}
123
124
	/**
125
	 * When the cron settings change, clear the relevant schedule
126
	 *
127
	 * @param mixed $old_value Previous option value
128
	 * @param mixed $new_value New option value
129
	 * @param string $option Name of option
130
	 */
131
	public function check_log_schedule( $old_value, $new_value, $option ) {
132
		$clear_schedule  = false;
133
		$schedule_unit   = get_option( $this->option_prefix . 'logs_how_often_unit', '' );
134
		$schedule_number = get_option( $this->option_prefix . 'logs_how_often_number', '' );
135
		if ( $this->option_prefix . 'logs_how_often_unit' === $option ) {
136
			$old_frequency = $this->get_schedule_frequency( $old_value, $schedule_number );
0 ignored issues
show
Bug introduced by
It seems like $schedule_number can also be of type false and string; however, parameter $number of Object_Sync_Sf_Logging::get_schedule_frequency() does only seem to accept double|integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

136
			$old_frequency = $this->get_schedule_frequency( $old_value, /** @scrutinizer ignore-type */ $schedule_number );
Loading history...
137
			$new_frequency = $this->get_schedule_frequency( $new_value, $schedule_number );
138
			$old_key       = $old_frequency['key'];
139
			$new_key       = $new_frequency['key'];
140
			if ( $old_key !== $new_key ) {
141
				$clear_schedule = true;
142
			}
143
		}
144
		if ( $this->option_prefix . 'logs_how_often_number' === $option ) {
145
			$old_frequency = $this->get_schedule_frequency( $schedule_unit, $old_value );
0 ignored issues
show
Bug introduced by
It seems like $schedule_unit can also be of type false; however, parameter $unit of Object_Sync_Sf_Logging::get_schedule_frequency() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

145
			$old_frequency = $this->get_schedule_frequency( /** @scrutinizer ignore-type */ $schedule_unit, $old_value );
Loading history...
146
			$new_frequency = $this->get_schedule_frequency( $schedule_unit, $new_value );
147
			$old_key       = $old_frequency['key'];
148
			$new_key       = $new_frequency['key'];
149
			if ( $old_key !== $new_key ) {
150
				$clear_schedule = true;
151
			}
152
		}
153
		if ( true === $clear_schedule ) {
154
			wp_clear_scheduled_hook( $this->schedule_name );
155
			$this->save_log_schedule();
156
		}
157
	}
158
159
	/**
160
	 * Save a cron schedule
161
	 *
162
	 */
163
	public function save_log_schedule() {
164
		global $pagenow;
165
		if ( ( 'options.php' !== $pagenow ) && ( ! isset( $_GET['page'] ) || $this->slug . '-admin' !== $_GET['page'] ) ) {
166
			return;
167
		}
168
		$schedule_unit   = get_option( $this->option_prefix . 'logs_how_often_unit', '' );
169
		$schedule_number = get_option( $this->option_prefix . 'logs_how_often_number', '' );
170
		$frequency       = $this->get_schedule_frequency( $schedule_unit, $schedule_number );
0 ignored issues
show
Bug introduced by
It seems like $schedule_unit can also be of type false; however, parameter $unit of Object_Sync_Sf_Logging::get_schedule_frequency() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

170
		$frequency       = $this->get_schedule_frequency( /** @scrutinizer ignore-type */ $schedule_unit, $schedule_number );
Loading history...
Bug introduced by
It seems like $schedule_number can also be of type false and string; however, parameter $number of Object_Sync_Sf_Logging::get_schedule_frequency() does only seem to accept double|integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

170
		$frequency       = $this->get_schedule_frequency( $schedule_unit, /** @scrutinizer ignore-type */ $schedule_number );
Loading history...
171
		$key             = $frequency['key'];
172
		if ( ! wp_next_scheduled( $this->schedule_name ) ) {
173
			wp_schedule_event( time(), $key, $this->schedule_name );
174
		}
175
	}
176
177
	/**
178
	 * Add interval to wp schedules based on admin settings
179
	 *
180
	 * @param array $schedules An array of scheduled cron items.
181
	 * @return array $frequency
182
	 */
183
	public function add_prune_interval( $schedules ) {
184
185
		$schedule_unit   = get_option( $this->option_prefix . 'logs_how_often_unit', '' );
186
		$schedule_number = get_option( $this->option_prefix . 'logs_how_often_number', '' );
187
		$frequency       = $this->get_schedule_frequency( $schedule_unit, $schedule_number );
0 ignored issues
show
Bug introduced by
It seems like $schedule_number can also be of type false and string; however, parameter $number of Object_Sync_Sf_Logging::get_schedule_frequency() does only seem to accept double|integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

187
		$frequency       = $this->get_schedule_frequency( $schedule_unit, /** @scrutinizer ignore-type */ $schedule_number );
Loading history...
Bug introduced by
It seems like $schedule_unit can also be of type false; however, parameter $unit of Object_Sync_Sf_Logging::get_schedule_frequency() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

187
		$frequency       = $this->get_schedule_frequency( /** @scrutinizer ignore-type */ $schedule_unit, $schedule_number );
Loading history...
188
		$key             = $frequency['key'];
189
		$seconds         = $frequency['seconds'];
190
191
		$schedules[ $key ] = array(
192
			'interval' => $seconds * $schedule_number,
193
			'display'  => 'Every ' . $schedule_number . ' ' . $schedule_unit,
0 ignored issues
show
Bug introduced by
Are you sure $schedule_unit of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
			'display'  => 'Every ' . $schedule_number . ' ' . /** @scrutinizer ignore-type */ $schedule_unit,
Loading history...
Bug introduced by
Are you sure $schedule_number of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
			'display'  => 'Every ' . /** @scrutinizer ignore-type */ $schedule_number . ' ' . $schedule_unit,
Loading history...
194
		);
195
196
		return $schedules;
197
198
	}
199
200
	/**
201
	 * Convert the schedule frequency from the admin settings into an array
202
	 * interval must be in seconds for the class to use it
203
	 *
204
	 * @param string $unit A unit of time.
205
	 * @param number $number The number of those units.
206
	 * @return array
207
	 */
208
	public function get_schedule_frequency( $unit, $number ) {
209
210
		switch ( $unit ) {
211
			case 'minutes':
212
				$seconds = 60;
213
				break;
214
			case 'hours':
215
				$seconds = 3600;
216
				break;
217
			case 'days':
218
				$seconds = 86400;
219
				break;
220
			default:
221
				$seconds = 0;
222
		}
223
224
		$key = $unit . '_' . $number;
225
226
		return array(
227
			'key'     => $key,
228
			'seconds' => $seconds,
229
		);
230
231
	}
232
233
	/**
234
	 * Set terms for Salesforce logs
235
	 *
236
	 * @param array $terms An array of string log types in the WP_Logging class.
237
	 * @return array $terms
238
	 */
239
	public function set_log_types( $terms ) {
240
		$terms[] = 'salesforce';
241
		return $terms;
242
	}
243
244
	/**
245
	 * Should logs be pruned at all?
246
	 *
247
	 * @param string $should_we_prune Whether to prune old log items.
248
	 * @return string $should_we_prune Whether to prune old log items.
249
	 */
250
	public function set_prune_option( $should_we_prune ) {
251
		$should_we_prune = get_option( $this->option_prefix . 'prune_logs', $should_we_prune );
252
		$should_we_prune = filter_var( $should_we_prune, FILTER_VALIDATE_BOOLEAN );
253
		return $should_we_prune;
254
	}
255
256
	/**
257
	 * Set how often to prune the Salesforce logs
258
	 *
259
	 * @param string $how_old How old the oldest non-pruned log items should be allowed to be.
260
	 * @return string $how_old
261
	 */
262
	public function set_prune_age( $how_old ) {
263
		$value = get_option( $this->option_prefix . 'logs_how_old', '' ) . ' ago';
0 ignored issues
show
Bug introduced by
Are you sure get_option($this->option...x . 'logs_how_old', '') of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

263
		$value = /** @scrutinizer ignore-type */ get_option( $this->option_prefix . 'logs_how_old', '' ) . ' ago';
Loading history...
264
		if ( '' !== $value ) {
265
			return $value;
266
		} else {
267
			return $how_old;
268
		}
269
	}
270
271
	/**
272
	 * Set arguments for only getting the Salesforce logs
273
	 *
274
	 * @param array $args Argument array for get_posts determining what posts are eligible for pruning.
275
	 * @return array $args
276
	 */
277
	public function set_prune_args( $args ) {
278
		$args['wp_log_type'] = 'salesforce';
279
		return $args;
280
	}
281
282
	/**
283
	 * Setup new log entry
284
	 *
285
	 * Check and see if we should log anything, and if so, send it to add()
286
	 *
287
	 * @access      public
288
	 * @since       1.0
289
	 *
290
	 * @param       string|array $title_or_params A log post title, or the full array of parameters
291
	 * @param       string $message The log message.
292
	 * @param       string|0 $trigger The type of log triggered. Usually one of: debug, notice, warning, error.
0 ignored issues
show
Documentation Bug introduced by
The doc comment string|0 at position 2 could not be parsed: Unknown type name '0' at position 2 in string|0.
Loading history...
293
	 * @param       int $parent The parent WordPress object.
294
	 * @param       string $status The log status.
295
	 *
296
	 * @uses        self::add()
297
	 * @see         Object_Sync_Sf_Mapping::__construct()    the location of the bitmasks that define the logging triggers.
298
	 *
299
	 * @return      void
300
	 */
301
	public function setup( $title_or_params, $message = '', $trigger = 0, $parent = 0, $status = '' ) {
302
303
		if ( is_array( $title_or_params ) ) {
304
			$title   = $title_or_params['title'];
305
			$message = $title_or_params['message'];
306
			$trigger = $title_or_params['trigger'];
307
			$parent  = $title_or_params['parent'];
308
			$status  = $title_or_params['status'];
309
		} else {
310
			$title = $title_or_params;
311
		}
312
313
		if ( ! is_array( maybe_unserialize( $this->statuses_to_log ) ) ) {
0 ignored issues
show
Bug introduced by
It seems like $this->statuses_to_log can also be of type array; however, parameter $original of maybe_unserialize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

313
		if ( ! is_array( maybe_unserialize( /** @scrutinizer ignore-type */ $this->statuses_to_log ) ) ) {
Loading history...
314
			if ( $status === $this->statuses_to_log ) {
315
				$this->add( $title, $message, $parent );
316
			} else {
317
				return;
318
			}
319
		}
320
321
		if ( true === filter_var( $this->enabled, FILTER_VALIDATE_BOOLEAN ) && in_array( $status, maybe_unserialize( $this->statuses_to_log ), true ) ) {
0 ignored issues
show
Bug introduced by
It seems like maybe_unserialize($this->statuses_to_log) can also be of type boolean; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

321
		if ( true === filter_var( $this->enabled, FILTER_VALIDATE_BOOLEAN ) && in_array( $status, /** @scrutinizer ignore-type */ maybe_unserialize( $this->statuses_to_log ), true ) ) {
Loading history...
322
			$triggers_to_log = get_option( $this->option_prefix . 'triggers_to_log', array() );
323
			// if we force strict on this in_array, it fails because the mapping triggers are bit operators, as indicated in Object_Sync_Sf_Mapping class's method __construct()
324
			if ( in_array( $trigger, maybe_unserialize( $triggers_to_log ) ) || 0 === $trigger ) {
0 ignored issues
show
Bug introduced by
It seems like maybe_unserialize($triggers_to_log) can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

324
			if ( in_array( $trigger, /** @scrutinizer ignore-type */ maybe_unserialize( $triggers_to_log ) ) || 0 === $trigger ) {
Loading history...
325
				$this->add( $title, $message, $parent );
326
			} elseif ( is_array( $trigger ) && array_intersect( $trigger, maybe_unserialize( $triggers_to_log ) ) ) {
0 ignored issues
show
Bug introduced by
It seems like maybe_unserialize($triggers_to_log) can also be of type false; however, parameter $array2 of array_intersect() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

326
			} elseif ( is_array( $trigger ) && array_intersect( $trigger, /** @scrutinizer ignore-type */ maybe_unserialize( $triggers_to_log ) ) ) {
Loading history...
327
				$this->add( $title, $message, $parent );
328
			}
329
		}
330
	}
331
332
	/**
333
	 * Create new log entry
334
	 *
335
	 * This is just a simple and fast way to log something. Use self::insert_log()
336
	 * if you need to store custom meta data
337
	 *
338
	 * @access      public
339
	 * @since       1.0
340
	 *
341
	 * @param       string $title A log post title.
342
	 *
343
	 * @uses        self::insert_log()
344
	 * @param       string $message The log message.
345
	 * @param       int $parent The parent WordPress object.
346
	 * @param       string $type The type of log message; defaults to 'salesforce'.
347
	 *
348
	 * @return      int The ID of the new log entry
349
	 */
350
	public static function add( $title = '', $message = '', $parent = 0, $type = 'salesforce' ) {
351
352
		$log_data = array(
353
			'post_title'   => esc_html( $title ),
354
			'post_content' => wp_kses_post( $message ),
355
			'post_parent'  => absint( $parent ),
356
			'log_type'     => esc_attr( $type ),
357
		);
358
359
		return self::insert_log( $log_data );
360
361
	}
362
363
364
	/**
365
	 * Easily retrieves log items for a particular object ID
366
	 *
367
	 * @access      private
368
	 * @since       1.0
369
	 *
370
	 * @param       int $object_id A WordPress object ID.
371
	 * @param       string $type The type of log item; defaults to 'salesforce' because that's the type of logs we create.
372
	 * @param       int $paged Which page of results do we want?
373
	 *
374
	 * @uses        self::get_connected_logs()
375
	 *
376
	 * @return      array
377
	 */
378
	public static function get_logs( $object_id = 0, $type = 'salesforce', $paged = null ) {
379
		return self::get_connected_logs(
380
			array(
381
				'post_parent' => (int) $object_id,
382
				'paged'       => (int) $paged,
383
				'log_type'    => (string) $type,
384
			)
385
		);
386
	}
387
388
389
	/**
390
	 * Retrieve all connected logs
391
	 *
392
	 * Used for retrieving logs related to particular items, such as a specific purchase.
393
	 *
394
	 * @access  private
395
	 * @since   1.0
396
	 *
397
	 * @param   Array $args An array of arguments for get_posts().
398
	 *
399
	 * @uses    wp_parse_args()
400
	 * @uses    get_posts()
401
	 * @uses    get_query_var()
402
	 * @uses    self::valid_type()
403
	 *
404
	 * @return  array / false
405
	 */
406
	public static function get_connected_logs( $args = array() ) {
407
408
		$defaults = array(
409
			'post_parent'    => 0,
410
			'post_type'      => 'wp_log',
411
			'posts_per_page' => 10,
412
			'post_status'    => 'publish',
413
			'paged'          => get_query_var( 'paged' ),
414
			'log_type'       => 'salesforce',
415
		);
416
417
		$query_args = wp_parse_args( $args, $defaults );
418
419
		if ( $query_args['log_type'] && self::valid_type( $query_args['log_type'] ) ) {
420
421
			$query_args['tax_query'] = array(
422
				array(
423
					'taxonomy' => 'wp_log_type',
424
					'field'    => 'slug',
425
					'terms'    => $query_args['log_type'],
426
				),
427
			);
428
429
		}
430
431
		$logs = get_posts( $query_args );
432
433
		if ( $logs ) {
434
			return $logs;
435
		}
436
437
		// no logs found.
438
		return false;
439
440
	}
441
442
443
	/**
444
	 * Retrieves number of log entries connected to particular object ID
445
	 *
446
	 * @access  private
447
	 * @since   1.0
448
	 *
449
	 * @param       int $object_id A WordPress object ID.
450
	 * @param       string $type The type of log item; defaults to 'salesforce' because that's the type of logs we create.
451
	 * @param       Array $meta_query A WordPress meta query, parseable by WP_Meta_Query.
452
	 *
453
	 * @uses    WP_Query()
454
	 * @uses    self::valid_type()
455
	 *
456
	 * @return  int
457
	 */
458
	public static function get_log_count( $object_id = 0, $type = 'salesforce', $meta_query = null ) {
459
460
		$query_args = array(
461
			'post_parent'    => (int) $object_id,
462
			'post_type'      => 'wp_log',
463
			'posts_per_page' => 100,
464
			'post_status'    => 'publish',
465
		);
466
467
		if ( ! empty( $type ) && self::valid_type( $type ) ) {
468
469
			$query_args['tax_query'] = array(
470
				array(
471
					'taxonomy' => 'wp_log_type',
472
					'field'    => 'slug',
473
					'terms'    => sanitize_key( $type ),
474
				),
475
			);
476
477
		}
478
479
		if ( ! empty( $meta_query ) ) {
480
			$query_args['meta_query'] = $meta_query;
481
		}
482
483
		$logs = new WP_Query( $query_args );
484
485
		return (int) $logs->post_count;
486
487
	}
488
489
}
490