Helper   F
last analyzed

Complexity

Total Complexity 80

Size/Duplication

Total Lines 522
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 80
eloc 128
c 4
b 0
f 0
dl 0
loc 522
rs 2

30 Methods

Rating   Name   Duplication   Size   Complexity  
A _is_transient_locked() 0 2 1
A transient() 0 10 6
A _unlock_transient() 0 2 1
A start_timer() 0 5 1
A _lock_transient() 0 2 1
A handle_transient_locking() 0 22 6
A ob_function() 0 6 1
A stop_timer() 0 7 1
B pluck() 0 12 8
A iseven() 0 2 1
A array_truncate() 0 5 2
A error_log() 0 9 5
A is_array_assoc() 0 5 2
A close_tags() 0 2 1
A function_wrapper() 0 4 1
A get_object_index_by_property() 0 17 6
A warn() 0 2 1
A osort() 0 3 2
A isodd() 0 2 1
A get_object_by_property() 0 11 4
B is_true() 0 10 9
A array_to_object() 0 10 3
A get_wp_title() 0 3 1
A trim_words() 0 2 1
A get_comment_form() 0 4 1
A get_current_url() 0 3 1
A paginate_links() 0 3 1
A wp_list_filter() 0 11 4
A filter_array() 0 12 3
A convert_wp_object() 0 11 4

How to fix   Complexity   

Complex Class

Complex classes like Helper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Helper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Timber;
4
5
use Timber\FunctionWrapper;
6
use Timber\URLHelper;
7
8
/**
9
 * As the name suggests these are helpers for Timber (and you!) when developing. You can find additional (mainly internally-focused helpers) in TimberURLHelper
10
 */
11
class Helper {
12
13
	/**
14
	 * A utility for a one-stop shop for Transients
15
	 * @api
16
	 * @example
17
	 * ```php
18
	 * $context = Timber::context();
19
	 * $context['favorites'] = Timber\Helper::transient('user-' .$uid. '-favorites', function() use ($uid) {
20
	 *  	//some expensive query here that's doing something you want to store to a transient
21
	 *  	return $favorites;
22
	 * }, 600);
23
	 * Timber::render('single.twig', $context);
24
	 * ```
25
	 *
26
	 * @param string  	$slug           Unique identifier for transient
27
	 * @param callable 	$callback      Callback that generates the data that's to be cached
28
	 * @param integer  	$transient_time (optional) Expiration of transients in seconds
29
	 * @param integer 	$lock_timeout   (optional) How long (in seconds) to lock the transient to prevent race conditions
30
	 * @param boolean 	$force          (optional) Force callback to be executed when transient is locked
31
	 * @return mixed
32
	 */
33
	public static function transient( $slug, $callback, $transient_time = 0, $lock_timeout = 5, $force = false ) {
34
		$slug = apply_filters('timber/transient/slug', $slug);
35
36
		$enable_transients = ($transient_time === false || (defined('WP_DISABLE_TRANSIENTS') && WP_DISABLE_TRANSIENTS)) ? false : true;
0 ignored issues
show
Bug introduced by
The constant Timber\WP_DISABLE_TRANSIENTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
37
		$data = $enable_transients ? get_transient($slug) : false;
38
39
		if ( false === $data ) {
40
			$data = self::handle_transient_locking($slug, $callback, $transient_time, $lock_timeout, $force, $enable_transients);
41
		}
42
		return $data;
43
	}
44
45
	/**
46
	 * Does the dirty work of locking the transient, running the callback and unlocking
47
	 * @param string 	$slug
48
	 * @param callable 	$callback
49
	 * @param integer  	$transient_time Expiration of transients in seconds
50
	 * @param integer 	$lock_timeout   How long (in seconds) to lock the transient to prevent race conditions
51
	 * @param boolean 	$force          Force callback to be executed when transient is locked
52
	 * @param boolean 	$enable_transients Force callback to be executed when transient is locked
53
	 */
54
	protected static function handle_transient_locking( $slug, $callback, $transient_time, $lock_timeout, $force, $enable_transients ) {
55
		if ( $enable_transients && self::_is_transient_locked($slug) ) {
56
			$force = apply_filters('timber_force_transients', $force);
57
			$force = apply_filters('timber_force_transient_'.$slug, $force);
58
			if ( !$force ) {
59
				//the server is currently executing the process.
60
				//We're just gonna dump these users. Sorry!
61
				return false;
62
			}
63
			$enable_transients = false;
64
		}
65
		// lock timeout shouldn't be higher than 5 seconds, unless
66
		// remote calls with high timeouts are made here
67
		if ( $enable_transients ) {
68
			self::_lock_transient($slug, $lock_timeout);
69
		}
70
		$data = $callback();
71
		if ( $enable_transients ) {
72
			set_transient($slug, $data, $transient_time);
73
			self::_unlock_transient($slug);
74
		}
75
		return $data;
76
	}
77
78
	/**
79
	 * @internal
80
	 * @param string $slug
81
	 * @param integer $lock_timeout
82
	 */
83
	public static function _lock_transient( $slug, $lock_timeout ) {
84
		set_transient($slug.'_lock', true, $lock_timeout);
85
	}
86
87
	/**
88
	 * @internal
89
	 * @param string $slug
90
	 */
91
	public static function _unlock_transient( $slug ) {
92
		delete_transient($slug.'_lock', true);
0 ignored issues
show
Unused Code introduced by
The call to delete_transient() has too many arguments starting with true. ( Ignorable by Annotation )

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

92
		/** @scrutinizer ignore-call */ 
93
  delete_transient($slug.'_lock', true);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
93
	}
94
95
	/**
96
	 * @internal
97
	 * @param string $slug
98
	 */
99
	public static function _is_transient_locked( $slug ) {
100
		return (bool) get_transient($slug.'_lock');
101
	}
102
103
	/* These are for measuring page render time */
104
105
	/**
106
	 * For measuring time, this will start a timer
107
	 * @api
108
	 * @return float
109
	 */
110
	public static function start_timer() {
111
		$time = microtime();
112
		$time = explode(' ', $time);
113
		$time = $time[1] + $time[0];
114
		return $time;
115
	}
116
117
	/**
118
	 * For stopping time and getting the data
119
	 * @example
120
	 * ```php
121
	 * $start = TimberHelper::start_timer();
122
	 * // do some stuff that takes awhile
123
	 * echo TimberHelper::stop_timer( $start );
124
	 * ```
125
	 * @param int     $start
126
	 * @return string
127
	 */
128
	public static function stop_timer( $start ) {
129
		$time = microtime();
130
		$time = explode(' ', $time);
131
		$time = $time[1] + $time[0];
132
		$finish = $time;
133
		$total_time = round(($finish - $start), 4);
134
		return $total_time.' seconds.';
135
	}
136
137
	/* Function Utilities
138
	======================== */
139
140
	/**
141
	 * Calls a function with an output buffer. This is useful if you have a function that outputs text that you want to capture and use within a twig template.
142
	 * @example
143
	 * ```php
144
	 * function the_form() {
145
	 *     echo '<form action="form.php"><input type="text" /><input type="submit /></form>';
146
	 * }
147
	 *
148
	 * $context = Timber::context();
149
	 * $context['post'] = new Timber\Post();
150
	 * $context['my_form'] = TimberHelper::ob_function('the_form');
151
	 * Timber::render('single-form.twig', $context);
152
	 * ```
153
	 * ```twig
154
	 * <h1>{{ post.title }}</h1>
155
	 * {{ my_form }}
156
	 * ```
157
	 * ```html
158
	 * <h1>Apply to my contest!</h1>
159
	 * <form action="form.php"><input type="text" /><input type="submit /></form>
160
	 * ```
161
	 * @api
162
	 * @param callback $function
163
	 * @param array   $args
164
	 * @return string
165
	 */
166
	public static function ob_function( $function, $args = array(null) ) {
167
		ob_start();
168
		call_user_func_array($function, $args);
169
		$data = ob_get_contents();
170
		ob_end_clean();
171
		return $data;
172
	}
173
174
	/**
175
	 * @codeCoverageIgnore
176
	 * @deprecated since 1.3.0
177
	 *
178
	 * @param mixed $function_name        String or array( $class( string|object ), $function_name ).
179
	 * @param array $defaults             Optional.
180
	 * @param bool  $return_output_buffer Optional. Return function output instead of return value. Default false.
181
	 * @return FunctionWrapper|mixed
182
	 */
183
	public static function function_wrapper( $function_name, $defaults = array(), $return_output_buffer = false ) {
184
		Helper::warn( 'function_wrapper is deprecated and will be removed in 1.4. Use {{ function( \'function_to_call\' ) }} instead or use FunctionWrapper directly. For more information refer to https://timber.github.io/docs/guides/functions/' );
185
186
		return new FunctionWrapper( $function_name, $defaults, $return_output_buffer );
187
	}
188
189
	/**
190
	 *
191
	 *
192
	 * @param mixed $arg that you want to error_log
193
	 * @return void
194
	 */
195
	public static function error_log( $error ) {
196
		global $timber_disable_error_log;
197
		if ( !WP_DEBUG || $timber_disable_error_log ) {
198
			return;
199
		}
200
		if ( is_object($error) || is_array($error) ) {
201
			$error = print_r($error, true);
202
		}
203
		return error_log('[ Timber ] '.$error);
204
	}
205
206
	/**
207
	 * @param string $message that you want to output
208
	 * @return boolean
209
	 */
210
	public static function warn( $message ) {
211
		return trigger_error($message, E_USER_WARNING);
212
	}
213
214
	/**
215
	 *
216
	 *
217
	 * @param string  $separator
218
	 * @param string  $seplocation
219
	 * @return string
220
	 */
221
	public static function get_wp_title( $separator = ' ', $seplocation = 'left' ) {
222
		$separator = apply_filters('timber_wp_title_seperator', $separator);
223
		return trim(wp_title($separator, false, $seplocation));
224
	}
225
226
	/* Text Utitilites */
227
228
229
230
	/* Object Utilities
231
	======================== */
232
233
	/**
234
	 * @codeCoverageIgnore
235
     * @deprecated since 1.2.0
236
     * @see TextHelper::trim_words
237
     * @param string  $text
238
     * @param int     $num_words
239
     * @param string|null|false  $more text to appear in "Read more...". Null to use default, false to hide
240
     * @param string  $allowed_tags
241
     * @return string
242
     */
243
    public static function trim_words( $text, $num_words = 55, $more = null, $allowed_tags = 'p a span b i br blockquote' ) {
244
        return TextHelper::trim_words($text, $num_words, $more, $allowed_tags);
245
    }
246
247
     /**
248
     * @deprecated since 1.2.0
249
     * @see TextHelper::close_tags
250
     * @param string  $html
251
     * @return string
252
     */
253
254
    public static function close_tags( $html ) {
255
    	return TextHelper::close_tags($html);
256
    }
257
258
	/**
259
	 *
260
	 *
261
	 * @param array   $array
262
	 * @param string  $prop
263
	 * @return void
264
	 */
265
	public static function osort( &$array, $prop ) {
266
		usort($array, function( $a, $b ) use ($prop) {
267
				return $a->$prop > $b->$prop ? 1 : -1;
268
			} );
269
	}
270
271
	/**
272
	 *
273
	 *
274
	 * @param array   $arr
275
	 * @return bool
276
	 */
277
	public static function is_array_assoc( $arr ) {
278
		if ( !is_array($arr) ) {
0 ignored issues
show
introduced by
The condition is_array($arr) is always true.
Loading history...
279
			return false;
280
		}
281
		return (bool) count(array_filter(array_keys($arr), 'is_string'));
282
	}
283
284
	/**
285
	 *
286
	 *
287
	 * @param array   $array
288
	 * @return \stdClass
289
	 */
290
	public static function array_to_object( $array ) {
291
		$obj = new \stdClass;
292
		foreach ( $array as $k => $v ) {
293
			if ( is_array($v) ) {
294
				$obj->{$k} = self::array_to_object($v); //RECURSION
295
			} else {
296
				$obj->{$k} = $v;
297
			}
298
		}
299
		return $obj;
300
	}
301
302
	/**
303
	 *
304
	 *
305
	 * @param array   $array
306
	 * @param string  $key
307
	 * @param mixed   $value
308
	 * @return bool|int
309
	 */
310
	public static function get_object_index_by_property( $array, $key, $value ) {
311
		if ( is_array($array) ) {
0 ignored issues
show
introduced by
The condition is_array($array) is always true.
Loading history...
312
			$i = 0;
313
			foreach ( $array as $arr ) {
314
				if ( is_array($arr) ) {
315
					if ( $arr[$key] == $value ) {
316
						return $i;
317
					}
318
				} else {
319
					if ( $arr->$key == $value ) {
320
						return $i;
321
					}
322
				}
323
				$i++;
324
			}
325
		}
326
		return false;
327
	}
328
329
	/**
330
	 *
331
	 *
332
	 * @param array   $array
333
	 * @param string  $key
334
	 * @param mixed   $value
335
	 * @return array|null
336
	 * @throws Exception
337
	 */
338
	public static function get_object_by_property( $array, $key, $value ) {
339
		if ( is_array($array) ) {
0 ignored issues
show
introduced by
The condition is_array($array) is always true.
Loading history...
340
			foreach ( $array as $arr ) {
341
				if ( $arr->$key == $value ) {
342
					return $arr;
343
				}
344
			}
345
			return false;
346
		}
347
		throw new \InvalidArgumentException('$array is not an array, got:');
348
		Helper::error_log($array);
0 ignored issues
show
Unused Code introduced by
Timber\Helper::error_log($array) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
349
	}
350
351
	/**
352
	 *
353
	 *
354
	 * @param array   $array
355
	 * @param int     $len
356
	 * @return array
357
	 */
358
	public static function array_truncate( $array, $len ) {
359
		if ( sizeof($array) > $len ) {
360
			$array = array_splice($array, 0, $len);
361
		}
362
		return $array;
363
	}
364
365
	/* Bool Utilities
366
	======================== */
367
368
	/**
369
	 *
370
	 *
371
	 * @param mixed   $value
372
	 * @return bool
373
	 */
374
	public static function is_true( $value ) {
375
		if ( isset($value) ) {
376
			if ( is_string($value) ) {
377
				$value = strtolower($value);
378
			}
379
			if ( ($value == 'true' || $value === 1 || $value === '1' || $value == true) && $value !== false && $value !== 'false' ) {
380
				return true;
381
			}
382
		}
383
		return false;
384
	}
385
386
	/**
387
	 *
388
	 *
389
	 * @param int     $i
390
	 * @return bool
391
	 */
392
	public static function iseven( $i ) {
393
		return ($i % 2) == 0;
394
	}
395
396
	/**
397
	 *
398
	 *
399
	 * @param int     $i
400
	 * @return bool
401
	 */
402
	public static function isodd( $i ) {
403
		return ($i % 2) != 0;
404
	}
405
406
	/**
407
	 * Plucks the values of a certain key from an array of objects
408
	 * @param array $array
409
	 * @param string $key
410
	 */
411
	public static function pluck( $array, $key ) {
412
		$return = array();
413
		foreach ( $array as $obj ) {
414
			if ( is_object($obj) && method_exists($obj, $key) ) {
415
				$return[] = $obj->$key();
416
			} elseif ( is_object($obj) && property_exists($obj, $key) ) {
417
				$return[] = $obj->$key;
418
			} elseif ( is_array($obj) && isset($obj[$key]) ) {
419
				$return[] = $obj[$key];
420
			}
421
		}
422
		return $return;
423
	}
424
425
	/**
426
	 * Filters a list of objects, based on a set of key => value arguments.
427
	 * Uses native Twig Filter.
428
	 *
429
	 * @since 1.14.0
430
	 * @deprecated since 1.17 (to be removed in 2.0). Use array_filter or Helper::wp_list_filter instead
431
	 * @todo remove this in 2.x
432
	 * @param array                 $list to filter.
433
	 * @param callback|string|array $arrow function used for filtering,
434
	 *                              string or array for backward compatibility.
435
	 * @param string                $operator to use (AND, NOT, OR). For backward compatibility.
436
	 * @return array
437
	 */
438
	public static function filter_array( $list, $arrow, $operator = 'AND' ) {
439
		if ( ! is_callable( $arrow ) ) {
440
			self::warn( 'This filter is using Twig\'s filter by default. If you want to use wp_list_filter use {{ my_array|wp_list_filter }}.' );
441
			return self::wp_list_filter( $list, $arrow, $operator );
0 ignored issues
show
Bug introduced by
It seems like $arrow can also be of type callable; however, parameter $args of Timber\Helper::wp_list_filter() does only seem to accept array|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

441
			return self::wp_list_filter( $list, /** @scrutinizer ignore-type */ $arrow, $operator );
Loading history...
442
		}
443
444
		if ( is_array( $list ) ) {
0 ignored issues
show
introduced by
The condition is_array($list) is always true.
Loading history...
445
			return array_filter( $list, $arrow, \ARRAY_FILTER_USE_BOTH );
446
		}
447
448
		// the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator
449
		return new \CallbackFilterIterator( new \IteratorIterator( $list ), $arrow );
450
	}
451
452
	/**
453
	 * Filters a list of objects, based on a set of key => value arguments.
454
	 * Uses WordPress WP_List_Util's filter.
455
	 *
456
	 * @since 1.5.3
457
	 * @ticket #1594
458
	 * @param array        $list to filter.
459
	 * @param string|array $args to search for.
460
	 * @param string       $operator to use (AND, NOT, OR).
461
	 * @return array
462
	 */
463
	public static function wp_list_filter( $list, $args, $operator = 'AND' ) {
464
		if ( ! is_array( $args ) ) {
465
			$args = array( 'slug' => $args );
466
		}
467
468
		if ( ! is_array( $list ) && ! is_a( $list, 'Traversable' ) ) {
0 ignored issues
show
introduced by
The condition is_array($list) is always true.
Loading history...
469
			return array();
470
		}
471
472
		$util = new \WP_List_Util( $list );
473
		return $util->filter( $args, $operator );
474
	}
475
476
	/* Links, Forms, Etc. Utilities
477
	======================== */
478
479
	/**
480
	 *
481
	 * Gets the comment form for use on a single article page
482
	 * @deprecated 0.21.8 use `{{ function('comment_form') }}` instead
483
	 * @param int $post_id which post_id should the form be tied to?
484
	 * @param array The $args thing is a mess, [fix at some point](http://codex.wordpress.org/Function_Reference/comment_form)
485
	 * @return string
486
	 */
487
	public static function get_comment_form( $post_id = null, $args = array() ) {
488
		global $post;
489
		$post = get_post($post_id);
490
		return self::ob_function('comment_form', array($args, $post_id));
491
	}
492
493
	/**
494
	 * @codeCoverageIgnore
495
	 * @deprecated since 1.1.2
496
	 * @param array  $args
497
	 * @return array
498
	 */
499
	public static function paginate_links( $args = array() ) {
500
		Helper::warn('Helper/paginate_links has been moved to Pagination/paginate_links');
501
		return Pagination::paginate_links($args);
502
	}
503
504
	/**
505
	 * @codeCoverageIgnore
506
	 * @return string
507
	 */
508
	public function get_current_url() {
509
		Helper::warn('TimberHelper::get_current_url() is deprecated and will be removed in future versions, use Timber\URLHelper::get_current_url()');
510
		return URLHelper::get_current_url();
511
	}
512
513
	/**
514
	 * Converts a WP object (WP_Post, WP_Term) into his
515
	 * equivalent Timber class (Timber\Post, Timber\Term).
516
	 *
517
	 * If no match is found the function will return the inital argument.
518
	 *
519
	 * @param mixed $obj WP Object
520
	 * @return mixed Instance of equivalent Timber object, or the argument if no match is found
521
	 */
522
	public static function convert_wp_object( $obj ) {
523
		if ( $obj instanceof \WP_Post ) {
524
			$class = \Timber\PostGetter::get_post_class($obj->post_type);
525
			return new $class($obj->ID);
526
		} elseif ( $obj instanceof \WP_Term ) {
527
			return new \Timber\Term($obj->term_id);
528
		} elseif ( $obj instanceof \WP_User ) {
529
			return new \Timber\User($obj->ID);
530
		}
531
532
		return $obj;
533
	}
534
}
535