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
|
|
|
|