Loader   F
last analyzed

Complexity

Total Complexity 69

Size/Duplication

Total Lines 359
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 170
c 7
b 0
f 0
dl 0
loc 359
rs 2.88
wmc 69

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A delete_cache() 0 2 1
A get_loader() 0 12 3
A template_exists() 0 2 1
C render() 0 42 12
B get_twig() 0 40 7
B set_cache() 0 23 8
A clear_cache_timber_database() 0 7 1
B clear_cache_timber() 0 14 7
A clear_cache_twig() 0 11 3
A _get_cache_mode() 0 11 4
B get_cache() 0 21 7
A clear_cache_timber_object() 0 11 4
A rrmdir() 0 16 5
A _get_cache_extension() 0 9 1
A choose_template() 0 23 4

How to fix   Complexity   

Complex Class

Complex classes like Loader 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 Loader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Timber;
4
5
use Timber\Cache\Cleaner;
6
7
class Loader {
8
9
	const CACHEGROUP = 'timberloader';
10
11
	const TRANS_KEY_LEN = 50;
12
13
	const CACHE_NONE = 'none';
14
	const CACHE_OBJECT = 'cache';
15
	const CACHE_TRANSIENT = 'transient';
16
	const CACHE_SITE_TRANSIENT = 'site-transient';
17
	const CACHE_USE_DEFAULT = 'default';
18
19
	public static $cache_modes = array(
20
		self::CACHE_NONE,
21
		self::CACHE_OBJECT,
22
		self::CACHE_TRANSIENT,
23
		self::CACHE_SITE_TRANSIENT
24
	);
25
26
	protected $cache_mode = self::CACHE_TRANSIENT;
27
28
	protected $locations;
29
30
	/**
31
	 * @param bool|string   $caller the calling directory or false
32
	 */
33
	public function __construct( $caller = false ) {
34
		$this->locations = LocationManager::get_locations($caller);
35
		$this->cache_mode = apply_filters('timber_cache_mode', $this->cache_mode);
36
		$this->cache_mode = apply_filters('timber/cache/mode', $this->cache_mode);
37
	}
38
39
	/**
40
	 * @param string        	$file
41
	 * @param array         	$data
42
	 * @param array|boolean    	$expires (array for options, false for none, integer for # of seconds)
43
	 * @param string        	$cache_mode
44
	 * @return bool|string
45
	 */
46
	public function render( $file, $data = null, $expires = false, $cache_mode = self::CACHE_USE_DEFAULT ) {
47
		// Different $expires if user is anonymous or logged in
48
		if ( is_array($expires) ) {
49
			/** @var array $expires */
50
			if ( is_user_logged_in() && isset($expires[1]) ) {
51
				$expires = $expires[1];
52
			} else {
53
				$expires = $expires[0];
54
			}
55
		}
56
57
		if ( $expires === 0 ) {
58
			$expires = false;
59
		}
60
61
		$key = null;
62
		$output = false;
63
		if ( false !== $expires ) {
64
			ksort($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type null; however, parameter $array of ksort() 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

64
			ksort(/** @scrutinizer ignore-type */ $data);
Loading history...
65
			$key = md5($file.json_encode($data));
66
			$output = $this->get_cache($key, self::CACHEGROUP, $cache_mode);
67
		}
68
69
		if ( false === $output || null === $output ) {
70
			$twig = $this->get_twig();
71
			if ( strlen($file) ) {
72
				$loader = $this->get_loader();
73
				$result = $loader->getCacheKey($file);
74
				do_action('timber_loader_render_file', $result);
75
			}
76
			$data = apply_filters('timber_loader_render_data', $data);
77
			$data = apply_filters('timber/loader/render_data', $data, $file);
78
			$template = $twig->load($file);
79
			$output = $template->render($data);
80
		}
81
82
		if ( false !== $output && false !== $expires && null !== $key ) {
83
			$this->delete_cache();
84
			$this->set_cache($key, $output, self::CACHEGROUP, $expires, $cache_mode);
85
		}
86
		$output = apply_filters('timber_output', $output);
87
		return apply_filters('timber/output', $output, $data, $file);
88
	}
89
90
	protected function delete_cache() {
91
		Cleaner::delete_transients();
92
	}
93
94
	/**
95
	 * Get first existing template.
96
	 *
97
	 * @param array|string $templates  Name(s) of the Twig template(s) to choose from.
98
	 * @return string|bool             Name of chosen template, otherwise false.
99
	 */
100
	public function choose_template( $templates ) {
101
		// Change $templates into array, if needed
102
		if ( !is_array($templates) ) {
103
			$templates = (array) $templates;
104
		}
105
106
		// Get Twig loader
107
		$loader = $this->get_loader();
108
109
		// Run through template array
110
		foreach ( $templates as $template ) {
111
112
			// Remove any whitespace around the template name
113
			$template = trim( $template );
114
			// Use the Twig loader to test for existance
115
			if ( $loader->exists($template) ) {
116
				// Return name of existing template
117
				return $template;
118
			}
119
		}
120
121
		// No existing template was found
122
		return false;
123
	}
124
125
	/**
126
	 * @param string $name
127
	 * @return bool
128
	 * @deprecated 1.3.5 No longer used internally
129
	 * @todo remove in 2.x
130
	 * @codeCoverageIgnore
131
	 */
132
	protected function template_exists( $name ) {
133
		return $this->get_loader()->exists($name);
134
	}
135
136
137
	/**
138
	 * @return \Twig\Loader\FilesystemLoader
139
	 */
140
	public function get_loader() {
141
		$open_basedir = ini_get('open_basedir');
142
		$paths = array_merge($this->locations, array($open_basedir ? ABSPATH : '/'));
143
		$paths = apply_filters('timber/loader/paths', $paths);
144
145
		$rootPath = '/';
146
		if ( $open_basedir ) {
147
			$rootPath = null;
148
		}
149
		$fs = new \Twig\Loader\FilesystemLoader($paths, $rootPath);
150
		$fs = apply_filters('timber/loader/loader', $fs, $paths, $rootPath);
151
		return $fs;
152
	}
153
154
155
	/**
156
	 * @return \Twig\Environment
157
	 */
158
	public function get_twig() {
159
		$loader = $this->get_loader();
160
		$params = array('debug' => WP_DEBUG,'autoescape' => false);
161
		if ( isset(Timber::$autoescape) ) {
162
			$params['autoescape'] = Timber::$autoescape === true ? 'html' : Timber::$autoescape;
163
		}
164
		if ( Timber::$cache === true ) {
165
			Timber::$twig_cache = true;
166
		}
167
		if ( Timber::$twig_cache ) {
168
			$twig_cache_loc = apply_filters('timber/cache/location', TIMBER_LOC.'/cache/twig');
169
			if ( !file_exists($twig_cache_loc) ) {
170
				mkdir($twig_cache_loc, 0777, true);
171
			}
172
			$params['cache'] = $twig_cache_loc;
173
		}
174
		$twig = new \Twig\Environment($loader, $params);
175
		if ( WP_DEBUG ) {
176
			$twig->addExtension(new \Twig\Extension\DebugExtension());
177
		} else {
178
			$twig->addFunction(new Twig_Function('dump', function() {
179
				return null;
180
			}));
181
		}
182
		$twig->addExtension($this->_get_cache_extension());
183
184
		$twig = apply_filters('twig_apply_filters', $twig);
185
		$twig = apply_filters('timber/twig/filters', $twig);
186
		$twig = apply_filters('timber/twig/functions', $twig);
187
		$twig = apply_filters('timber/twig/escapers', $twig);
188
		$twig = apply_filters('timber/loader/twig', $twig);
189
190
		$twig = apply_filters('timber/twig', $twig);
191
192
		/**
193
		 * get_twig is deprecated, use timber/twig
194
		 */
195
		$twig = apply_filters('get_twig', $twig);
196
197
		return $twig;
198
	}
199
200
	public function clear_cache_timber( $cache_mode = self::CACHE_USE_DEFAULT ) {
201
		//_transient_timberloader
202
		$object_cache = false;
203
		if ( isset($GLOBALS['wp_object_cache']) && is_object($GLOBALS['wp_object_cache']) ) {
204
			$object_cache = true;
205
		}
206
		$cache_mode = $this->_get_cache_mode($cache_mode);
207
		if ( self::CACHE_TRANSIENT === $cache_mode || self::CACHE_SITE_TRANSIENT === $cache_mode ) {
208
			// $wpdb->query() might return 0 affected rows, but that means it’s still successful.
209
			return false !== self::clear_cache_timber_database();
210
		} else if ( self::CACHE_OBJECT === $cache_mode && $object_cache ) {
211
			return false !== self::clear_cache_timber_object();
212
		}
213
		return false;
214
	}
215
216
	protected static function clear_cache_timber_database() {
217
		global $wpdb;
218
		$query = $wpdb->prepare(
219
			"DELETE FROM $wpdb->options WHERE option_name LIKE '%s'",
220
			'_transient%timberloader_%'
221
		);
222
		return $wpdb->query($query);
223
	}
224
225
	protected static function clear_cache_timber_object() {
226
		global $wp_object_cache;
227
		if ( isset($wp_object_cache->cache[self::CACHEGROUP]) ) {
228
			$items = $wp_object_cache->cache[self::CACHEGROUP];
229
			foreach ( $items as $key => $value ) {
230
				if ( is_multisite() ) {
231
					$key = preg_replace('/^(.*?):/', '', $key);
232
				}
233
				wp_cache_delete($key, self::CACHEGROUP);
234
			}
235
			return true;
236
		}
237
	}
238
239
	public function clear_cache_twig() {
240
		$twig = $this->get_twig();
241
		if ( method_exists($twig, 'clearCacheFiles') ) {
242
			$twig->clearCacheFiles();
243
		}
244
		$cache = $twig->getCache();
245
		if ( $cache ) {
246
			self::rrmdir($twig->getCache());
0 ignored issues
show
Bug introduced by
It seems like $twig->getCache() can also be of type Twig\Cache\CacheInterface; however, parameter $dirPath of Timber\Loader::rrmdir() does only seem to accept false|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

246
			self::rrmdir(/** @scrutinizer ignore-type */ $twig->getCache());
Loading history...
247
			return true;
248
		}
249
		return false;
250
	}
251
252
	/**
253
	 * Remove a directory and everything inside
254
	 *
255
	 * @param string|false $dirPath
256
	 */
257
	public static function rrmdir( $dirPath ) {
258
		if ( !is_dir($dirPath) ) {
0 ignored issues
show
Bug introduced by
It seems like $dirPath can also be of type false; however, parameter $filename of is_dir() 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

258
		if ( !is_dir(/** @scrutinizer ignore-type */ $dirPath) ) {
Loading history...
259
			throw new \InvalidArgumentException("$dirPath must be a directory");
260
		}
261
		if ( substr($dirPath, strlen($dirPath) - 1, 1) != '/' ) {
0 ignored issues
show
Bug introduced by
It seems like $dirPath can also be of type false; however, parameter $string of substr() 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

261
		if ( substr(/** @scrutinizer ignore-type */ $dirPath, strlen($dirPath) - 1, 1) != '/' ) {
Loading history...
Bug introduced by
It seems like $dirPath can also be of type false; however, parameter $string of strlen() 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

261
		if ( substr($dirPath, strlen(/** @scrutinizer ignore-type */ $dirPath) - 1, 1) != '/' ) {
Loading history...
262
			$dirPath .= '/';
263
		}
264
		$files = glob($dirPath.'*', GLOB_MARK);
0 ignored issues
show
Bug introduced by
Are you sure $dirPath of type false|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

264
		$files = glob(/** @scrutinizer ignore-type */ $dirPath.'*', GLOB_MARK);
Loading history...
265
		foreach ( $files as $file ) {
266
			if ( is_dir($file) ) {
267
				self::rrmdir($file);
268
			} else {
269
				unlink($file);
270
			}
271
		}
272
		rmdir($dirPath);
0 ignored issues
show
Bug introduced by
It seems like $dirPath can also be of type false; however, parameter $directory of rmdir() 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

272
		rmdir(/** @scrutinizer ignore-type */ $dirPath);
Loading history...
273
	}
274
275
	/**
276
	 * @return \Twig\CacheExtension\Extension
277
	 */
278
	private function _get_cache_extension() {
279
280
		$key_generator   = new \Timber\Cache\KeyGenerator();
281
		$cache_provider  = new \Timber\Cache\WPObjectCacheAdapter($this);
282
		$cache_lifetime  = apply_filters('timber/cache/extension/lifetime', 0);
283
		$cache_strategy  = new \Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy($cache_provider, $key_generator, $cache_lifetime);
284
		$cache_extension = new \Twig\CacheExtension\Extension($cache_strategy);
285
286
		return $cache_extension;
287
	}
288
289
	/**
290
	 * @param string $key
291
	 * @param string $group
292
	 * @param string $cache_mode
293
	 * @return bool
294
	 */
295
	public function get_cache( $key, $group = self::CACHEGROUP, $cache_mode = self::CACHE_USE_DEFAULT ) {
296
		$object_cache = false;
297
298
		if ( isset($GLOBALS['wp_object_cache']) && is_object($GLOBALS['wp_object_cache']) ) {
299
			$object_cache = true;
300
		}
301
302
		$cache_mode = $this->_get_cache_mode($cache_mode);
303
304
		$value = false;
305
306
		$trans_key = substr($group.'_'.$key, 0, self::TRANS_KEY_LEN);
307
		if ( self::CACHE_TRANSIENT === $cache_mode ) {
308
			$value = get_transient($trans_key);
309
		} elseif ( self::CACHE_SITE_TRANSIENT === $cache_mode ) {
310
			$value = get_site_transient($trans_key);
311
		} elseif ( self::CACHE_OBJECT === $cache_mode && $object_cache ) {
312
			$value = wp_cache_get($key, $group);
313
		}
314
315
		return $value;
316
	}
317
318
	/**
319
	 * @param string $key
320
	 * @param string|boolean $value
321
	 * @param string $group
322
	 * @param integer $expires
323
	 * @param string $cache_mode
324
	 * @return string|boolean
325
	 */
326
	public function set_cache( $key, $value, $group = self::CACHEGROUP, $expires = 0, $cache_mode = self::CACHE_USE_DEFAULT ) {
327
		$object_cache = false;
328
329
		if ( isset($GLOBALS['wp_object_cache']) && is_object($GLOBALS['wp_object_cache']) ) {
330
			$object_cache = true;
331
		}
332
333
		if ( (int) $expires < 1 ) {
334
			$expires = 0;
335
		}
336
337
		$cache_mode = self::_get_cache_mode($cache_mode);
0 ignored issues
show
Bug Best Practice introduced by
The method Timber\Loader::_get_cache_mode() is not static, but was called statically. ( Ignorable by Annotation )

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

337
		/** @scrutinizer ignore-call */ 
338
  $cache_mode = self::_get_cache_mode($cache_mode);
Loading history...
338
		$trans_key = substr($group.'_'.$key, 0, self::TRANS_KEY_LEN);
339
340
		if ( self::CACHE_TRANSIENT === $cache_mode ) {
341
			set_transient($trans_key, $value, $expires);
342
		} elseif ( self::CACHE_SITE_TRANSIENT === $cache_mode ) {
343
			set_site_transient($trans_key, $value, $expires);
344
		} elseif ( self::CACHE_OBJECT === $cache_mode && $object_cache ) {
345
			wp_cache_set($key, $value, $group, $expires);
346
		}
347
348
		return $value;
349
	}
350
351
	/**
352
	 * @param string $cache_mode
353
	 * @return string
354
	 */
355
	private function _get_cache_mode( $cache_mode ) {
356
		if ( empty($cache_mode) || self::CACHE_USE_DEFAULT === $cache_mode ) {
357
			$cache_mode = $this->cache_mode;
358
		}
359
360
		// Fallback if self::$cache_mode did not get a valid value
361
		if ( !in_array($cache_mode, self::$cache_modes) ) {
362
			$cache_mode = self::CACHE_OBJECT;
363
		}
364
365
		return $cache_mode;
366
	}
367
368
}
369