Stencil_Handler   B
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 381
Duplicated Lines 3.67 %

Coupling/Cohesion

Components 2
Dependencies 4
Metric Value
wmc 36
lcom 2
cbo 4
dl 14
loc 381
rs 8.8

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
A load_wp_header_and_footer() 0 13 3
A get_implementation() 0 3 1
A get_recorder() 0 3 1
A set_recorder() 0 3 1
A get_engine() 0 3 1
B get_usable_view() 0 26 6
B set() 0 38 3
A get() 0 3 1
A display() 0 3 1
A fetch() 0 3 1
A set_wp_head_footer() 0 15 1
B start_recording() 0 22 5
B finish_recording() 0 25 3
B internal_fetch() 0 41 4
A hook_wp_header_and_footer() 7 7 1
A unhook_wp_header_and_footer() 7 7 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * Handler class
4
 *
5
 * The handler holds an Implementation and controls recording for it.
6
 * Filtering for variables also happens here.
7
 *
8
 * @package Stencil
9
 */
10
11
/**
12
 * Class Stencil_Handler
13
 */
14
class Stencil_Handler implements Stencil_Handler_Interface, Stencil_Implementation_Interface {
15
16
	/**
17
	 * Use ob_start / ob_end_clean to record a variable.
18
	 *
19
	 * @var string
20
	 */
21
	protected $recording_for = null;
22
23
	/**
24
	 * Revert to recorder after finishing recording.
25
	 *
26
	 * @var null|Stencil_Recorder_Interface
27
	 */
28
	protected $revert_recorder_to;
29
30
	/**
31
	 * The instance of the proxy
32
	 *
33
	 * The proxy communicates with the engine
34
	 *
35
	 * @var Stencil_Implementation $proxy
36
	 */
37
	protected $proxy;
38
39
	/**
40
	 * Recorder instance
41
	 *
42
	 * @var Stencil_Recorder_Interface
43
	 */
44
	protected $recorder;
45
46
	/**
47
	 * Control if the wp_head and wp_footer are being loaded
48
	 *
49
	 * Default disabled, Stencil will enable this for the core handler
50
	 *
51
	 * @var bool
52
	 */
53
	protected $load_wp_header_and_footer = false;
54
55
	/**
56
	 * StencilHandler constructor.
57
	 *
58
	 * @param Stencil_Implementation          $implementation Implementation to use.
59
	 * @param Stencil_Recorder_Interface|null $recorder Recorder to use.
60
	 */
61
	public function __construct( Stencil_Implementation $implementation, Stencil_Recorder_Interface $recorder = null ) {
62
		$this->proxy    = $implementation;
63
		$this->recorder = is_null( $recorder ) ? new Stencil_Recorder() : $recorder;
64
	}
65
66
	/**
67
	 * Control loading the wp_head and wp_footer into variable
68
	 *
69
	 * @param bool|true $yes_or_no Wheter to load them or not.
70
	 */
71
	public function load_wp_header_and_footer( $yes_or_no = true ) {
72
		if ( $this->load_wp_header_and_footer === $yes_or_no ) {
73
			return;
74
		}
75
76
		if ( $yes_or_no ) {
77
			$this->hook_wp_header_and_footer();
78
		} else {
79
			$this->unhook_wp_header_and_footer();
80
		}
81
82
		$this->load_wp_header_and_footer = (bool) $yes_or_no;
83
	}
84
85
	/**
86
	 * Get the used implementation
87
	 *
88
	 * @return Stencil_Implementation
89
	 */
90
	public function get_implementation() {
91
		return $this->proxy;
92
	}
93
94
	/**
95
	 * Get the default recorder
96
	 *
97
	 * @return Stencil_Recorder_Interface
98
	 */
99
	public function get_recorder() {
100
		return $this->recorder;
101
	}
102
103
	/**
104
	 * Set the new default Recorder
105
	 *
106
	 * @param Stencil_Recorder_Interface $recorder The new Recorder to use as default.
107
	 */
108
	public function set_recorder( Stencil_Recorder_Interface $recorder ) {
109
		$this->recorder = $recorder;
110
	}
111
112
	/**
113
	 * Get the engine from the implementation
114
	 */
115
	public function get_engine() {
116
		return $this->get_implementation()->get_engine();
117
	}
118
119
	/**
120
	 * Find the view that is implemented
121
	 *
122
	 * @param array|null $options Options that were collected from the hierarchy.
123
	 *
124
	 * @return string
125
	 */
126
	public function get_usable_view( array $options = null ) {
127
		/**
128
		 * Clean up
129
		 */
130
		$options = array_unique( $options );
131
		$options = array_filter( $options );
132
133
		if ( empty( $options ) ) {
134
			return 'index';
135
		}
136
137
		$implementation = $this->get_implementation();
138
139
		$base = $implementation->get_template_path();
140
		$base = ! is_array( $base ) ? array( $base ) : $base;
141
142
		foreach ( $options as $option ) {
143
			foreach ( $base as $root ) {
144
				if ( is_file( $root . $option . '.' . $implementation->get_template_extension() ) ) {
145
					return $option;
146
				}
147
			}
148
		}
149
150
		return 'index';
151
	}
152
153
	/**
154
	 * Sets a variable to the template engine
155
	 *
156
	 * @param string $variable Name of the variable.
157
	 * @param mixed  $value Value to assign to the variable.
158
	 * @param bool   $override Optional. Allowed override the variable if already set.
159
	 *
160
	 * @return mixed Template known value of the provided variable name
161
	 */
162
	public function set( $variable, $value, $override = true ) {
163
		if ( ! $override ) {
164
			$current = $this->get( $variable );
165
166
			// Exit if variable has a value.
167
			if ( ! is_null( $current ) ) {
168
				return $current;
169
			}
170
		}
171
172
		/**
173
		 * Filter: stencil:set
174
		 *
175
		 * Allows for global filtering of template data
176
		 * Questionable if this is the place to do this but
177
		 * you never know what people would use this for..
178
		 */
179
		$value = Stencil_Environment::filter( 'set', $value, $variable );
180
181
		/**
182
		 * Filter: stencil:set-{variable_name}
183
		 *
184
		 * Allows for global filtering of template data
185
		 * Questionable if this is the place to do this but
186
		 * you never know what people would use this for..
187
		 */
188
		$value = Stencil_Environment::filter( 'set-' . $variable, $value );
189
190
		/**
191
		 * Set the variable
192
		 */
193
		$this->get_implementation()->set( $variable, $value );
194
195
		/**
196
		 * Return the value of the variable in the engine
197
		 */
198
		return $this->get( $variable );
199
	}
200
201
	/**
202
	 * Gets a variable from the template engine
203
	 *
204
	 * @param string $variable Name of the variable to get.
205
	 *
206
	 * @return mixed
207
	 */
208
	public function get( $variable ) {
209
		return $this->get_implementation()->get( $variable );
210
	}
211
212
	/**
213
	 * Displays a chosen template
214
	 *
215
	 * Uses fetch to fetch the output
216
	 *
217
	 * @param string $template Template file to use.
218
	 */
219
	public function display( $template ) {
220
		$this->internal_fetch( $template, 'display' );
221
	}
222
223
	/**
224
	 * Build and display the template
225
	 *
226
	 * @param string $template Template file to fetch.
227
	 *
228
	 * @return bool|string|void
229
	 */
230
	public function fetch( $template ) {
231
		return $this->internal_fetch( $template, 'fetch' );
232
	}
233
234
	/**
235
	 * Set the wp_head and wp_footer variables
236
	 *
237
	 * This function is being triggered by
238
	 * stencil.pre_display and stencil.pre_fetch
239
	 * so it can be disabled if preferred
240
	 */
241
	public function set_wp_head_footer() {
242
		/**
243
		 * Could split this up in a head/footer
244
		 * but when the header is required the footer
245
		 * finishes the complete scope.
246
		 */
247
248
		$this->start_recording( 'wp_head' );
249
		wp_head();
250
		$this->finish_recording();
251
252
		$this->start_recording( 'wp_footer' );
253
		wp_footer();
254
		$this->finish_recording();
255
	}
256
257
	/**
258
	 * Recorder for inline HTML cathing
259
	 *
260
	 * @param string                          $variable Variable to record into.
261
	 * @param Stencil_Recorder_Interface|null $temporary_recorder Optional. Recorder to use for this recording.
262
	 *
263
	 * @throws Exception When already recording for other variable.
264
	 * @throws InvalidArgumentException When the variable name is not a string.
265
	 */
266
	public function start_recording( $variable, Stencil_Recorder_Interface $temporary_recorder = null ) {
267
		/**
268
		 * Throw exception or error?
269
		 */
270
		if ( ! empty( $this->recording_for ) ) {
271
			throw new Exception( sprintf( 'Already recording variable "%s".', $this->recording_for ) );
272
		}
273
274
		if ( ! is_string( $variable ) || empty( $variable ) ) {
275
			throw new InvalidArgumentException( 'Expected variable name to record for.' );
276
		}
277
278
		// Set temp recorder as active.
279
		if ( ! is_null( $temporary_recorder ) ) {
280
			$swap                     = $this->recorder;
281
			$this->recorder           = $temporary_recorder;
282
			$this->revert_recorder_to = $swap;
283
		}
284
285
		$this->recording_for = $variable;
286
		$this->recorder->start_recording();
287
	}
288
289
	/**
290
	 * Finish recording raw input
291
	 *
292
	 * @return mixed
293
	 * @throws Exception When we are not recording.
294
	 */
295
	public function finish_recording() {
296
		if ( is_null( $this->recording_for ) ) {
297
			throw new Exception( 'Not recording.' );
298
		}
299
300
		$this->recorder->finish_recording();
301
		$recording = $this->recorder->get_recording();
302
303
		$this->set( $this->recording_for, $recording );
304
305
		/**
306
		 * Re-set original recorder
307
		 */
308
		if ( isset( $this->revert_recorder_to ) ) {
309
			$this->recorder = $this->revert_recorder_to;
310
			unset( $this->revert_recorder_to );
311
		}
312
313
		/**
314
		 * Clear variable holder
315
		 */
316
		$this->recording_for = null;
317
318
		return $recording;
319
	}
320
321
	/**
322
	 * Unified function for fetching and displaying a template
323
	 *
324
	 * @param string $template Template file to load.
325
	 * @param string $from Source of this request.
326
	 *
327
	 * @return mixed|WP_Error
328
	 *
329
	 * @throws LogicException When we are still recording a variable.
330
	 */
331
	protected function internal_fetch( $template, $from ) {
332
		if ( ! is_null( $this->recording_for ) ) {
333
			throw new LogicException( sprintf( 'Stencil: trying to fetch view %s but still recording for "%s".', $template, $this->recording_for ) );
334
		}
335
336
		$implementation = $this->get_implementation();
337
338
		// Hook pre_fetch / pre_display.
339
		Stencil_Environment::trigger( 'pre_' . $from, $template );
340
341
		// Make sure undefined index errors are not caught; template engines don't check for these.
342
		$error_reporting = error_reporting();
343
		error_reporting( error_reporting() & ~E_NOTICE );
344
345
		// Fetch.
346
		$fetched = $implementation->fetch( $template . '.' . $implementation->get_template_extension() );
347
348
		// Restore error_reporting.
349
		error_reporting( $error_reporting );
350
351
		/**
352
		 * Apply filtering
353
		 */
354
		$fetched = Stencil_Environment::filter( 'content', $fetched );
355
356
		/**
357
		 * Echo if we are displaying
358
		 */
359
		if ( 'display' === $from ) {
360
			echo $fetched;
361
		}
362
363
		// Hook post_fetch / post_display.
364
		Stencil_Environment::trigger( 'post_' . $from, $template );
365
366
		if ( 'fetch' === $from ) {
367
			return $fetched;
368
		}
369
370
		return '';
371
	}
372
373
	/**
374
	 * Attach hooks to load the wp_head and wp_footer variables
375
	 */
376 View Code Duplication
	private function hook_wp_header_and_footer() {
377
		/**
378
		 * Add wp_head and wp_footer variable recording
379
		 */
380
		add_action( Stencil_Environment::format_hook( 'pre_display' ), array( $this, 'set_wp_head_footer' ) );
381
		add_action( Stencil_Environment::format_hook( 'pre_fetch' ), array( $this, 'set_wp_head_footer' ) );
382
	}
383
384
	/**
385
	 * Detach hooks to load the wp_head and wp_footer variables
386
	 */
387 View Code Duplication
	private function unhook_wp_header_and_footer() {
388
		/**
389
		 * Add wp_head and wp_footer variable recording
390
		 */
391
		remove_action( Stencil_Environment::format_hook( 'pre_display' ), array( $this, 'set_wp_head_footer' ) );
392
		remove_action( Stencil_Environment::format_hook( 'pre_fetch' ), array( $this, 'set_wp_head_footer' ) );
393
	}
394
}
395