Completed
Push — 3.7 ( 81b2d8...ef0909 )
by
unknown
09:42
created

SSViewer::flush_cacheblock_cache()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 3
nop 1
dl 0
loc 19
rs 9.0111
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This tracks the current scope for an SSViewer instance. It has three goals:
5
 *   - Handle entering & leaving sub-scopes in loops and withs
6
 *   - Track Up and Top
7
 *   - (As a side effect) Inject data that needs to be available globally (used to live in ViewableData)
8
 *
9
 * In order to handle up, rather than tracking it using a tree, which would involve constructing new objects
10
 * for each step, we use indexes into the itemStack (which already has to exist).
11
 *
12
 * Each item has three indexes associated with it
13
 *
14
 *   - Pop. Which item should become the scope once the current scope is popped out of
15
 *   - Up. Which item is up from this item
16
 *   - Current. Which item is the first time this object has appeared in the stack
17
 *
18
 * We also keep the index of the current starting point for lookups. A lookup is a sequence of obj calls -
19
 * when in a loop or with tag the end result becomes the new scope, but for injections, we throw away the lookup
20
 * and revert back to the original scope once we've got the value we're after
21
 *
22
 * @package framework
23
 * @subpackage view
24
 */
25
class SSViewer_Scope {
26
27
	const ITEM = 0;
28
	const ITEM_ITERATOR = 1;
29
	const ITEM_ITERATOR_TOTAL = 2;
30
	const POP_INDEX = 3;
31
	const UP_INDEX = 4;
32
	const CURRENT_INDEX = 5;
33
	const ITEM_OVERLAY = 6;
34
35
	// The stack of previous "global" items
36
	// An indexed array of item, item iterator, item iterator total, pop index, up index, current index & parent overlay
37
	private $itemStack = array();
38
39
	// The current "global" item (the one any lookup starts from)
40
	protected $item;
41
42
	// If we're looping over the current "global" item, here's the iterator that tracks with item we're up to
43
	protected $itemIterator;
44
45
	//Total number of items in the iterator
46
	protected $itemIteratorTotal;
47
48
	// A pointer into the item stack for which item should be scope on the next pop call
49
	private $popIndex;
50
51
	// A pointer into the item stack for which item is "up" from this one
52
	private $upIndex = null;
53
54
	// A pointer into the item stack for which item is this one (or null if not in stack yet)
55
	private $currentIndex = null;
56
57
	private $localIndex;
58
59
	public function __construct($item, $inheritedScope = null) {
60
		$this->item = $item;
61
		$this->localIndex = 0;
62
		$this->localStack = array();
0 ignored issues
show
Bug introduced by
The property localStack does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
63
		if ($inheritedScope instanceof SSViewer_Scope) {
64
			$this->itemIterator = $inheritedScope->itemIterator;
65
			$this->itemIteratorTotal = $inheritedScope->itemIteratorTotal;
66
			$this->itemStack[] = array($this->item, $this->itemIterator, $this->itemIteratorTotal, null, null, 0);
67
		} else {
68
			$this->itemStack[] = array($this->item, null, 0, null, null, 0);
69
		}
70
	}
71
72
	public function getItem(){
73
		return $this->itemIterator ? $this->itemIterator->current() : $this->item;
74
	}
75
76
	/** Called at the start of every lookup chain by SSTemplateParser to indicate a new lookup from local scope */
77
	public function locally() {
78
		list($this->item, $this->itemIterator, $this->itemIteratorTotal, $this->popIndex, $this->upIndex,
79
			$this->currentIndex) = $this->itemStack[$this->localIndex];
80
81
		// Remember any  un-completed (resetLocalScope hasn't been called) lookup chain. Even if there isn't an
82
		// un-completed chain we need to store an empty item, as resetLocalScope doesn't know the difference later
83
		$this->localStack[] = array_splice($this->itemStack, $this->localIndex+1);
84
85
		return $this;
86
	}
87
88
	public function resetLocalScope(){
89
		$previousLocalState = $this->localStack ? array_pop($this->localStack) : null;
90
91
		array_splice($this->itemStack, $this->localIndex+1, count($this->itemStack), $previousLocalState);
92
93
		list($this->item, $this->itemIterator, $this->itemIteratorTotal, $this->popIndex, $this->upIndex,
94
			$this->currentIndex) = end($this->itemStack);
95
	}
96
97
	public function getObj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {
98
		$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
99
		return $on->obj($name, $arguments, $forceReturnedObject, $cache, $cacheName);
100
	}
101
102
	public function obj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {
103
		switch ($name) {
104
			case 'Up':
105
				if ($this->upIndex === null) {
106
					user_error('Up called when we\'re already at the top of the scope', E_USER_ERROR);
107
				}
108
109
				list($this->item, $this->itemIterator, $this->itemIteratorTotal, $unused2, $this->upIndex,
0 ignored issues
show
Unused Code introduced by
The assignment to $unused2 is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
110
					$this->currentIndex) = $this->itemStack[$this->upIndex];
111
				break;
112
113
			case 'Top':
114
				list($this->item, $this->itemIterator, $this->itemIteratorTotal, $unused2, $this->upIndex,
0 ignored issues
show
Unused Code introduced by
The assignment to $unused2 is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
115
					$this->currentIndex) = $this->itemStack[0];
116
				break;
117
118
			default:
119
				$this->item = $this->getObj($name, $arguments, $forceReturnedObject, $cache, $cacheName);
120
				$this->itemIterator = null;
121
				$this->upIndex = $this->currentIndex ? $this->currentIndex : count($this->itemStack)-1;
122
				$this->currentIndex = count($this->itemStack);
123
				break;
124
		}
125
126
		$this->itemStack[] = array($this->item, $this->itemIterator, $this->itemIteratorTotal, null,
127
			$this->upIndex, $this->currentIndex);
128
		return $this;
129
	}
130
131
	/**
132
	 * Gets the current object and resets the scope.
133
	 *
134
	 * @return object
135
	 */
136
	public function self() {
137
		$result = $this->itemIterator ? $this->itemIterator->current() : $this->item;
138
		$this->resetLocalScope();
139
140
		return $result;
141
	}
142
143
	public function pushScope(){
144
		$newLocalIndex = count($this->itemStack)-1;
145
146
		$this->popIndex = $this->itemStack[$newLocalIndex][SSViewer_Scope::POP_INDEX] = $this->localIndex;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
147
		$this->localIndex = $newLocalIndex;
148
149
		// We normally keep any previous itemIterator around, so local $Up calls reference the right element. But
150
		// once we enter a new global scope, we need to make sure we use a new one
151
		$this->itemIterator = $this->itemStack[$newLocalIndex][SSViewer_Scope::ITEM_ITERATOR] = null;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
152
153
		return $this;
154
	}
155
156
	public function popScope(){
157
		$this->localIndex = $this->popIndex;
158
		$this->resetLocalScope();
159
160
		return $this;
161
	}
162
163
	public function next(){
164
		if (!$this->item) return false;
165
166
		if (!$this->itemIterator) {
167
			if (is_array($this->item)) $this->itemIterator = new ArrayIterator($this->item);
168
			else $this->itemIterator = $this->item->getIterator();
169
170
			$this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR] = $this->itemIterator;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
171
			$this->itemIteratorTotal = iterator_count($this->itemIterator); //count the total number of items
172
			$this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR_TOTAL] = $this->itemIteratorTotal;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
173
			$this->itemIterator->rewind();
174
		}
175
		else {
176
			$this->itemIterator->next();
177
		}
178
179
		$this->resetLocalScope();
180
181
		if (!$this->itemIterator->valid()) return false;
182
		return $this->itemIterator->key();
183
	}
184
185
	public function __call($name, $arguments) {
186
		$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
187
		$retval = $on ? call_user_func_array(array($on, $name), $arguments) : null;
188
189
		$this->resetLocalScope();
190
		return $retval;
191
	}
192
193
	/**
194
	 * @return array
195
	 */
196
	protected function getItemStack() {
197
		return $this->itemStack;
198
	}
199
200
	/**
201
	 * @param array
202
	 */
203
	protected function setItemStack(array $stack) {
204
		$this->itemStack = $stack;
205
	}
206
207
	/**
208
	 * @return int|null
209
	 */
210
	protected function getUpIndex() {
211
		return $this->upIndex;
212
	}
213
}
214
215
/**
216
 * Defines an extra set of basic methods that can be used in templates
217
 * that are not defined on sub-classes of {@link ViewableData}.
218
 *
219
 * @package framework
220
 * @subpackage view
221
 */
222
class SSViewer_BasicIteratorSupport implements TemplateIteratorProvider {
223
224
	protected $iteratorPos;
225
	protected $iteratorTotalItems;
226
227
	public static function get_template_iterator_variables() {
228
		return array(
229
			'First',
230
			'Last',
231
			'FirstLast',
232
			'Middle',
233
			'MiddleString',
234
			'Even',
235
			'Odd',
236
			'EvenOdd',
237
			'Pos',
238
			'FromEnd',
239
			'TotalItems',
240
			'Modulus',
241
			'MultipleOf',
242
		);
243
	}
244
245
	/**
246
	 * Set the current iterator properties - where we are on the iterator.
247
	 *
248
	 * @param int $pos position in iterator
249
	 * @param int $totalItems total number of items
250
	 */
251
	public function iteratorProperties($pos, $totalItems) {
252
		$this->iteratorPos        = $pos;
253
		$this->iteratorTotalItems = $totalItems;
254
	}
255
256
	/**
257
	 * Returns true if this object is the first in a set.
258
	 *
259
	 * @return bool
260
	 */
261
	public function First() {
262
		return $this->iteratorPos == 0;
263
	}
264
265
	/**
266
	 * Returns true if this object is the last in a set.
267
	 *
268
	 * @return bool
269
	 */
270
	public function Last() {
271
		return $this->iteratorPos == $this->iteratorTotalItems - 1;
272
	}
273
274
	/**
275
	 * Returns 'first' or 'last' if this is the first or last object in the set.
276
	 *
277
	 * @return string|null
278
	 */
279
	public function FirstLast() {
280
		if($this->First() && $this->Last()) return 'first last';
281
		if($this->First()) return 'first';
282
		if($this->Last())  return 'last';
283
	}
284
285
	/**
286
	 * Return true if this object is between the first & last objects.
287
	 *
288
	 * @return bool
289
	 */
290
	public function Middle() {
291
		return !$this->First() && !$this->Last();
292
	}
293
294
	/**
295
	 * Return 'middle' if this object is between the first & last objects.
296
	 *
297
	 * @return string|null
298
	 */
299
	public function MiddleString() {
300
		if($this->Middle()) return 'middle';
301
	}
302
303
	/**
304
	 * Return true if this object is an even item in the set.
305
	 * The count starts from $startIndex, which defaults to 1.
306
	 *
307
	 * @param int $startIndex Number to start count from.
308
	 * @return bool
309
	 */
310
	public function Even($startIndex = 1) {
311
		return !$this->Odd($startIndex);
312
	}
313
314
	/**
315
	 * Return true if this is an odd item in the set.
316
	 *
317
	 * @param int $startIndex Number to start count from.
318
	 * @return bool
319
	 */
320
	public function Odd($startIndex = 1) {
321
		return (bool) (($this->iteratorPos+$startIndex) % 2);
322
	}
323
324
	/**
325
	 * Return 'even' or 'odd' if this object is in an even or odd position in the set respectively.
326
	 *
327
	 * @param int $startIndex Number to start count from.
328
	 * @return string
329
	 */
330
	public function EvenOdd($startIndex = 1) {
331
		return ($this->Even($startIndex)) ? 'even' : 'odd';
332
	}
333
334
	/**
335
	 * Return the numerical position of this object in the container set. The count starts at $startIndex.
336
	 * The default is the give the position using a 1-based index.
337
	 *
338
	 * @param int $startIndex Number to start count from.
339
	 * @return int
340
	 */
341
	public function Pos($startIndex = 1) {
342
		return $this->iteratorPos + $startIndex;
343
	}
344
345
	/**
346
	 * Return the position of this item from the last item in the list. The position of the final
347
	 * item is $endIndex, which defaults to 1.
348
	 *
349
	 * @param integer $endIndex Value of the last item
350
	 * @return int
351
	 */
352
	public function FromEnd($endIndex = 1) {
353
		return $this->iteratorTotalItems - $this->iteratorPos + $endIndex - 1;
354
	}
355
356
	/**
357
	 * Return the total number of "sibling" items in the dataset.
358
	 *
359
	 * @return int
360
	 */
361
	public function TotalItems() {
362
		return $this->iteratorTotalItems;
363
	}
364
365
	/**
366
	 * Returns the modulus of the numerical position of the item in the data set.
367
	 * The count starts from $startIndex, which defaults to 1.
368
	 * @param int $Mod The number to perform Mod operation to.
0 ignored issues
show
Bug introduced by
There is no parameter named $Mod. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
369
	 * @param int $startIndex Number to start count from.
370
	 * @return int
371
	 */
372
	public function Modulus($mod, $startIndex = 1) {
373
		return ($this->iteratorPos + $startIndex) % $mod;
374
	}
375
376
	/**
377
	 * Returns true or false depending on if the pos of the iterator is a multiple of a specific number.
378
	 * So, <% if MultipleOf(3) %> would return true on indexes: 3,6,9,12,15, etc.
379
	 * The count starts from $offset, which defaults to 1.
380
	 * @param int $factor The multiple of which to return
381
	 * @param int $offset Number to start count from.
382
	 * @return bool
383
	 */
384
	public function MultipleOf($factor, $offset = 1) {
385
		return (bool) ($this->Modulus($factor, $offset) == 0);
386
	}
387
388
389
390
}
391
/**
392
 * This extends SSViewer_Scope to mix in data on top of what the item provides. This can be "global"
393
 * data that is scope-independant (like BaseURL), or type-specific data that is layered on top cross-cut like
394
 * (like $FirstLast etc).
395
 *
396
 * It's separate from SSViewer_Scope to keep that fairly complex code as clean as possible.
397
 *
398
 * @package framework
399
 * @subpackage view
400
 */
401
class SSViewer_DataPresenter extends SSViewer_Scope {
402
403
	private static $globalProperties = null;
404
	private static $iteratorProperties = null;
405
406
	/**
407
	 * Overlay variables. Take precedence over anything from the current scope
408
	 * @var array|null
409
	 */
410
	protected $overlay;
411
412
	/**
413
	 * Underlay variables. Concede precedence to overlay variables or anything from the current scope
414
	 * @var array|null
415
	 */
416
	protected $underlay;
417
418
	public function __construct($item, $overlay = null, $underlay = null, $inheritedScope = null) {
419
		parent::__construct($item, $inheritedScope);
420
421
		// Build up global property providers array only once per request
422
		if (self::$globalProperties === null) {
423
			self::$globalProperties = array();
424
			// Get all the exposed variables from all classes that implement the TemplateGlobalProvider interface
425
			$this->createCallableArray(self::$globalProperties, "TemplateGlobalProvider",
426
				"get_template_global_variables");
427
		}
428
429
		// Build up iterator property providers array only once per request
430
		if (self::$iteratorProperties === null) {
431
			self::$iteratorProperties = array();
432
			// Get all the exposed variables from all classes that implement the TemplateIteratorProvider interface
433
			// //call non-statically
434
			$this->createCallableArray(self::$iteratorProperties, "TemplateIteratorProvider",
435
				"get_template_iterator_variables", true);
436
		}
437
438
		$this->overlay = $overlay ? $overlay : array();
439
		$this->underlay = $underlay ? $underlay : array();
440
	}
441
442
	protected function createCallableArray(&$extraArray, $interfaceToQuery, $variableMethod, $createObject = false) {
443
		$implementers = ClassInfo::implementorsOf($interfaceToQuery);
444
		if($implementers) foreach($implementers as $implementer) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $implementers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
445
446
			// Create a new instance of the object for method calls
447
			if ($createObject) $implementer = new $implementer();
448
449
			// Get the exposed variables
450
			$exposedVariables = call_user_func(array($implementer, $variableMethod));
451
452
			foreach($exposedVariables as $varName => $details) {
453
				if (!is_array($details)) $details = array('method' => $details,
454
					'casting' => Config::inst()->get('ViewableData', 'default_cast', Config::FIRST_SET));
455
456
				// If just a value (and not a key => value pair), use it for both key and value
457
				if (is_numeric($varName)) $varName = $details['method'];
458
459
				// Add in a reference to the implementing class (might be a string class name or an instance)
460
				$details['implementer'] = $implementer;
461
462
				// And a callable array
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
463
				if (isset($details['method'])) $details['callable'] = array($implementer, $details['method']);
464
465
				// Save with both uppercase & lowercase first letter, so either works
466
				$lcFirst = strtolower($varName[0]) . substr($varName,1);
467
				$extraArray[$lcFirst] = $details;
468
				$extraArray[ucfirst($varName)] = $details;
469
			}
470
		}
471
	}
472
473
	/**
474
	 * Get the injected value
475
	 *
476
	 * @param string $property Name of property
477
	 * @param array $params
478
	 * @param bool $cast If true, an object is always returned even if not an object.
479
	 * @return array Result array with the keys 'value' for raw value, or 'obj' if contained in an object
480
	 * @throws InvalidArgumentException
481
	 */
482
	public function getInjectedValue($property, $params, $cast = true) {
483
		$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
484
485
		// Find the source of the value
486
		$source = null;
487
488
		// Check for a presenter-specific override
489
		if (array_key_exists($property, $this->overlay)) {
490
			$source = array('value' => $this->overlay[$property]);
491
		}
492
		// Check if the method to-be-called exists on the target object - if so, don't check any further
493
		// injection locations
494
		else if (isset($on->$property) || method_exists($on, $property)) {
495
			$source = null;
496
		}
497
		// Check for a presenter-specific override
498
		else if (array_key_exists($property, $this->underlay)) {
499
			$source = array('value' => $this->underlay[$property]);
500
		}
501
		// Then for iterator-specific overrides
502
		else if (array_key_exists($property, self::$iteratorProperties)) {
503
			$source = self::$iteratorProperties[$property];
504
			if ($this->itemIterator) {
505
				// Set the current iterator position and total (the object instance is the first item in
506
				// the callable array)
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
507
				$source['implementer']->iteratorProperties($this->itemIterator->key(), $this->itemIteratorTotal);
508
			} else {
509
				// If we don't actually have an iterator at the moment, act like a list of length 1
510
				$source['implementer']->iteratorProperties(0, 1);
511
			}
512
		}
513
		// And finally for global overrides
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
514
		else if (array_key_exists($property, self::$globalProperties)) {
515
			$source = self::$globalProperties[$property];  //get the method call
516
		}
517
518
		if ($source) {
519
			$res = array();
520
521
			// Look up the value - either from a callable, or from a directly provided value
522
			if (isset($source['callable'])) $res['value'] = call_user_func_array($source['callable'], $params);
523
			elseif (isset($source['value'])) $res['value'] = $source['value'];
524
			else throw new InvalidArgumentException("Injected property $property does't have a value or callable " .
525
				"value source provided");
526
527
			// If we want to provide a casted object, look up what type object to use
528
			if ($cast) {
529
				// If the handler returns an object, then we don't need to cast.
530
				if(is_object($res['value'])) {
531
					$res['obj'] = $res['value'];
532
				} else {
533
					// Get the object to cast as
534
					$casting = isset($source['casting']) ? $source['casting'] : null;
535
536
					// If not provided, use default
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
537
					if (!$casting) $casting = Config::inst()->get('ViewableData', 'default_cast', Config::FIRST_SET);
538
539
					$obj = Injector::inst()->create($casting, false, array($property));
540
					$obj->setValue($res['value']);
541
542
					$res['obj'] = $obj;
543
				}
544
			}
545
546
			return $res;
547
		}
548
549
	}
550
551
	/**
552
	 * Store the current overlay (as it doesn't directly apply to the new scope
553
	 * that's being pushed). We want to store the overlay against the next item
554
	 * "up" in the stack (hence upIndex), rather than the current item, because
555
	 * SSViewer_Scope::obj() has already been called and pushed the new item to
556
	 * the stack by this point
557
	 * @return SSViewer_Scope
558
	 */
559
	public function pushScope() {
560
		$scope = parent::pushScope();
561
		$upIndex = $this->getUpIndex();
562
563
		if ($upIndex !== null) {
564
			$itemStack = $this->getItemStack();
565
			$itemStack[$upIndex][SSViewer_Scope::ITEM_OVERLAY] = $this->overlay;
566
567
			$this->setItemStack($itemStack);
568
			$this->overlay = array();
569
		}
570
571
		return $scope;
572
	}
573
574
	/**
575
	 * Now that we're going to jump up an item in the item stack, we need to
576
	 * restore the overlay that was previously stored against the next item "up"
577
	 * in the stack from the current one
578
	 * @return SSViewer_Scope
579
	 */
580
	public function popScope() {
581
		$upIndex = $this->getUpIndex();
582
583
		if ($upIndex !== null) {
584
			$itemStack = $this->getItemStack();
585
			$this->overlay = $itemStack[$this->getUpIndex()][SSViewer_Scope::ITEM_OVERLAY];
586
		}
587
588
		return parent::popScope();
589
	}
590
591
	/**
592
	 * $Up and $Top need to restore the overlay from the parent and top-level
593
	 * scope respectively.
594
	 */
595
	public function obj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {
596
		$overlayIndex = false;
597
598
		switch($name) {
599
			case 'Up':
600
				$upIndex = $this->getUpIndex();
601
				if ($upIndex === null) {
602
					user_error('Up called when we\'re already at the top of the scope', E_USER_ERROR);
603
				}
604
605
				$overlayIndex = $upIndex; // Parent scope
606
				break;
607
			case 'Top':
608
				$overlayIndex = 0; // Top-level scope
609
				break;
610
		}
611
612
		if ($overlayIndex !== false) {
613
			$itemStack = $this->getItemStack();
614
			if (!$this->overlay && isset($itemStack[$overlayIndex][SSViewer_Scope::ITEM_OVERLAY])) {
615
				$this->overlay = $itemStack[$overlayIndex][SSViewer_Scope::ITEM_OVERLAY];
616
			}
617
		}
618
619
		return parent::obj($name, $arguments, $forceReturnedObject, $cache, $cacheName);
620
	}
621
622
	public function getObj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {
623
		$result = $this->getInjectedValue($name, (array)$arguments);
624
		if($result) return $result['obj'];
625
		else return parent::getObj($name, $arguments, $forceReturnedObject, $cache, $cacheName);
626
	}
627
628
	public function __call($name, $arguments) {
629
		//extract the method name and parameters
630
		$property = $arguments[0];  //the name of the public function being called
631
632
		//the public function parameters in an array
633
		if (isset($arguments[1]) && $arguments[1] != null) $params = $arguments[1];
634
		else $params = array();
635
636
		$val = $this->getInjectedValue($property, $params);
637
		if ($val) {
638
			$obj = $val['obj'];
639
			if ($name === 'hasValue') {
640
				$res = $obj instanceof SS_Object
641
					? $obj->exists()
642
					: (bool)$obj;
643
			} else {
644
				// XML_val
645
				$res = $obj->forTemplate();
646
			}
647
			$this->resetLocalScope();
648
			return $res;
649
		} else {
650
			return parent::__call($name, $arguments);
651
		}
652
	}
653
}
654
655
/**
656
 * Parses a template file with an *.ss file extension.
657
 *
658
 * In addition to a full template in the templates/ folder, a template in
659
 * templates/Content or templates/Layout will be rendered into $Content and
660
 * $Layout, respectively.
661
 *
662
 * A single template can be parsed by multiple nested {@link SSViewer} instances
663
 * through $Layout/$Content placeholders, as well as <% include MyTemplateFile %> template commands.
664
 *
665
 * <b>Themes</b>
666
 *
667
 * See http://doc.silverstripe.org/themes and http://doc.silverstripe.org/themes:developing
668
 *
669
 * <b>Caching</b>
670
 *
671
 * Compiled templates are cached via {@link SS_Cache}, usually on the filesystem.
672
 * If you put ?flush=1 on your URL, it will force the template to be recompiled.
673
 *
674
 * @see http://doc.silverstripe.org/themes
675
 * @see http://doc.silverstripe.org/themes:developing
676
 *
677
 * @package framework
678
 * @subpackage view
679
 */
680
class SSViewer implements Flushable {
681
682
	/**
683
	 * @config
684
	 * @var boolean $source_file_comments
685
	 */
686
	private static $source_file_comments = false;
687
688
	/**
689
	 * @ignore
690
	 */
691
	private static $template_cache_flushed = false;
692
693
	/**
694
	 * @ignore
695
	 */
696
	private static $cacheblock_cache_flushed = false;
697
698
	/**
699
	 * Set whether HTML comments indicating the source .SS file used to render this page should be
700
	 * included in the output.  This is enabled by default
701
	 *
702
	 * @deprecated 4.0 Use the "SSViewer.source_file_comments" config setting instead
703
	 * @param boolean $val
704
	 */
705
	public static function set_source_file_comments($val) {
706
		Deprecation::notice('4.0', 'Use the "SSViewer.source_file_comments" config setting instead');
707
		Config::inst()->update('SSViewer', 'source_file_comments', $val);
708
	}
709
710
	/**
711
	 * @deprecated 4.0 Use the "SSViewer.source_file_comments" config setting instead
712
	 * @return boolean
713
	 */
714
	public static function get_source_file_comments() {
715
		Deprecation::notice('4.0', 'Use the "SSViewer.source_file_comments" config setting instead');
716
		return Config::inst()->get('SSViewer', 'source_file_comments');
717
	}
718
719
	/**
720
	 * @var array $chosenTemplates Associative array for the different
721
	 * template containers: "main" and "Layout". Values are absolute file paths to *.ss files.
722
	 */
723
	private $chosenTemplates = array();
724
725
	/**
726
	 * @var boolean
727
	 */
728
	protected $rewriteHashlinks = true;
729
730
	/**
731
	 * @config
732
	 * @var string The used "theme", which usually consists of templates, images and stylesheets.
733
	 * Only used when {@link $theme_enabled} is set to TRUE.
734
	 */
735
	private static $theme = null;
736
737
	/**
738
	 * @config
739
	 * @var boolean Use the theme. Set to FALSE in order to disable themes,
740
	 * which can be useful for scenarios where theme overrides are temporarily undesired,
741
	 * such as an administrative interface separate from the website theme.
742
	 * It retains the theme settings to be re-enabled, for example when a website content
743
	 * needs to be rendered from within this administrative interface.
744
	 */
745
	private static $theme_enabled = true;
746
747
	/**
748
	 * @var boolean
749
	 */
750
	protected $includeRequirements = true;
751
752
	/**
753
	 * @var TemplateParser
754
	 */
755
	protected $parser;
756
757
	/*
758
	 * Default prepended cache key for partial caching
759
	 *
760
	 * @var string
761
	 * @config
762
	 */
763
	private static $global_key = '$CurrentReadingMode, $CurrentUser.ID';
764
765
	/**
766
	 * Triggered early in the request when someone requests a flush.
767
	 */
768
	public static function flush() {
769
		self::flush_template_cache(true);
770
		self::flush_cacheblock_cache(true);
771
	}
772
773
	/**
774
	 * Create a template from a string instead of a .ss file
775
	 *
776
	 * @param string $content The template content
777
	 * @param bool|void $cacheTemplate Whether or not to cache the template from string
778
	 * @return SSViewer
779
	 */
780
	public static function fromString($content, $cacheTemplate = null) {
781
		$viewer = new SSViewer_FromString($content);
782
		if ($cacheTemplate !== null) {
783
			$viewer->setCacheTemplate($cacheTemplate);
784
		}
785
		return $viewer;
786
	}
787
788
	/**
789
	 * @deprecated 4.0 Use the "SSViewer.theme" config setting instead
790
	 * @param string $theme The "base theme" name (without underscores).
791
	 */
792
	public static function set_theme($theme) {
793
		Deprecation::notice('4.0', 'Use the "SSViewer.theme" config setting instead');
794
		Config::inst()->update('SSViewer', 'theme', $theme);
795
	}
796
797
	/**
798
	 * @deprecated 4.0 Use the "SSViewer.theme" config setting instead
799
	 * @return string
800
	 */
801
	public static function current_theme() {
802
		Deprecation::notice('4.0', 'Use the "SSViewer.theme" config setting instead');
803
		return Config::inst()->get('SSViewer', 'theme');
804
	}
805
806
	/**
807
	 * Returns the path to the theme folder
808
	 *
809
	 * @return string
810
	 */
811
	public static function get_theme_folder() {
812
		$theme = Config::inst()->get('SSViewer', 'theme');
813
		return $theme ? THEMES_DIR . "/" . $theme : project();
814
	}
815
816
	/**
817
	 * Returns an array of theme names present in a directory.
818
	 *
819
	 * @param  string $path
820
	 * @param  bool   $subthemes Include subthemes (default false).
821
	 * @return array
822
	 */
823
	public static function get_themes($path = null, $subthemes = false) {
824
		$path   = rtrim($path ? $path : THEMES_PATH, '/');
825
		$themes = array();
826
827
		if (!is_dir($path)) return $themes;
828
829
		foreach (scandir($path) as $item) {
830
			if ($item[0] != '.' && is_dir("$path/$item")) {
831
				if ($subthemes || strpos($item, '_') === false) {
832
					$themes[$item] = $item;
833
				}
834
			}
835
		}
836
837
		return $themes;
838
	}
839
840
	/**
841
	 * @deprecated since version 4.0
842
	 * @return string
843
	 */
844
	public static function current_custom_theme(){
845
		Deprecation::notice('4.0', 'Use the "SSViewer.theme" and "SSViewer.theme_enabled" config settings instead');
846
		return Config::inst()->get('SSViewer', 'theme_enabled') ? Config::inst()->get('SSViewer', 'theme') : null;
847
	}
848
849
	/**
850
	 * Traverses the given the given class context looking for templates with the relevant name.
851
	 *
852
	 * @param $className string - valid class name
853
	 * @param $suffix string
854
	 * @param $baseClass string
855
	 *
856
	 * @return array
857
	 */
858
	public static function get_templates_by_class($className, $suffix = '', $baseClass = null) {
859
		// Figure out the class name from the supplied context.
860
		if(!is_string($className) || !class_exists($className)) {
861
			throw new InvalidArgumentException('SSViewer::get_templates_by_class() expects a valid class name as ' .
862
				'its first parameter.');
863
			return array();
0 ignored issues
show
Unused Code introduced by
return array(); does not seem to be 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...
864
		}
865
		$templates = array();
866
		$classes = array_reverse(ClassInfo::ancestry($className));
867
		foreach($classes as $class) {
868
			$template = $class . $suffix;
869
			if(SSViewer::hasTemplate($template)) $templates[] = $template;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
870
871
			// If the class is "Page_Controller", look for Page.ss
872
			if(stripos($class,'_controller') !== false) {
873
				$template = str_ireplace('_controller','',$class) . $suffix;
874
				if(SSViewer::hasTemplate($template)) $templates[] = $template;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
875
			}
876
877
			if($baseClass && $class == $baseClass) break;
878
		}
879
		return $templates;
880
	}
881
882
	/**
883
	 * @param string|array $templateList If passed as a string with .ss extension, used as the "main" template.
884
	 *  If passed as an array, it can be used for template inheritance (first found template "wins").
885
	 *  Usually the array values are PHP class names, which directly correlate to template names.
886
	 *  <code>
887
	 *  array('MySpecificPage', 'MyPage', 'Page')
888
	 *  </code>
889
	 */
890
	public function __construct($templateList, TemplateParser $parser = null) {
891
		if ($parser) {
892
			$this->setParser($parser);
893
		}
894
895
		if(!is_array($templateList) && substr((string) $templateList,-3) == '.ss') {
896
			$this->chosenTemplates['main'] = $templateList;
897
		} else {
898
			if(Config::inst()->get('SSViewer', 'theme_enabled')) {
899
				$theme = Config::inst()->get('SSViewer', 'theme');
900
			} else {
901
				$theme = null;
902
			}
903
			$this->chosenTemplates = SS_TemplateLoader::instance()->findTemplates(
904
				$templateList, $theme
905
			);
906
		}
907
908
		if(!$this->chosenTemplates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->chosenTemplates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
909
			$templateList = (is_array($templateList)) ? $templateList : array($templateList);
910
911
			$message = 'None of the following templates could be found';
912
			if(!$theme) {
913
				$message .= ' (no theme in use)';
914
			} else {
915
				$message .= ' in theme "' . $theme . '"';
0 ignored issues
show
Bug introduced by
The variable $theme does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
916
			}
917
918
			user_error($message . ': ' . implode(".ss, ", $templateList) . ".ss", E_USER_WARNING);
919
		}
920
	}
921
922
	/**
923
	 * Set the template parser that will be used in template generation
924
	 * @param \TemplateParser $parser
925
	 */
926
	public function setParser(TemplateParser $parser)
927
	{
928
		$this->parser = $parser;
929
	}
930
931
	/**
932
	 * Returns the parser that is set for template generation
933
	 * @return \TemplateParser
934
	 */
935
	public function getParser()
936
	{
937
		if (!$this->parser) {
938
			$this->setParser(Injector::inst()->get('SSTemplateParser'));
939
		}
940
		return $this->parser;
941
	}
942
943
	/**
944
	 * Returns true if at least one of the listed templates exists.
945
	 *
946
	 * @param array $templates
947
	 *
948
	 * @return boolean
949
	 */
950
	public static function hasTemplate($templates) {
951
		$manifest = SS_TemplateLoader::instance()->getManifest();
952
953
		if(Config::inst()->get('SSViewer', 'theme_enabled')) {
954
			$theme = Config::inst()->get('SSViewer', 'theme');
955
		} else {
956
			$theme = null;
957
		}
958
959
		foreach ((array) $templates as $template) {
960
			if ($manifest->getCandidateTemplate($template, $theme)) return true;
961
		}
962
963
		return false;
964
	}
965
966
	/**
967
	 * Set a global rendering option.
968
	 *
969
	 * The following options are available:
970
	 *  - rewriteHashlinks: If true (the default), <a href="#..."> will be rewritten to contain the
971
	 *    current URL.  This lets it play nicely with our <base> tag.
972
	 *  - If rewriteHashlinks = 'php' then, a piece of PHP script will be inserted before the hash
973
	 *    links: "<?php echo $_SERVER['REQUEST_URI']; ?>".  This is useful if you're generating a
974
	 *    page that will be saved to a .php file and may be accessed from different URLs.
975
	 *
976
	 * @deprecated 4.0 Use the "SSViewer.rewrite_hash_links" config setting instead
977
	 * @param string $optionName
978
	 * @param mixed $optionVal
979
	 */
980
	public static function setOption($optionName, $optionVal) {
981
		if($optionName == 'rewriteHashlinks') {
982
			Deprecation::notice('4.0', 'Use the "SSViewer.rewrite_hash_links" config setting instead');
983
			Config::inst()->update('SSViewer', 'rewrite_hash_links', $optionVal);
984
		} else {
985
			Deprecation::notice('4.0', 'Use the "SSViewer.' . $optionName . '" config setting instead');
986
			Config::inst()->update('SSViewer', $optionName, $optionVal);
987
		}
988
	}
989
990
	/**
991
 	 * @deprecated 4.0 Use the "SSViewer.rewrite_hash_links" config setting instead
992
 	 * @param string
993
 	 * @return mixed
994
	 */
995
	public static function getOption($optionName) {
996
		if($optionName == 'rewriteHashlinks') {
997
			Deprecation::notice('4.0', 'Use the "SSViewer.rewrite_hash_links" config setting instead');
998
			return Config::inst()->get('SSViewer', 'rewrite_hash_links');
999
		} else {
1000
			Deprecation::notice('4.0', 'Use the "SSViewer.' . $optionName . '" config setting instead');
1001
			return Config::inst()->get('SSViewer', $optionName);
1002
		}
1003
	}
1004
1005
	/**
1006
	 * @config
1007
	 * @var boolean
1008
	 */
1009
	private static $rewrite_hash_links = true;
1010
1011
	protected static $topLevel = array();
1012
1013
	public static function topLevel() {
1014
		if(SSViewer::$topLevel) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \SSViewer::$topLevel of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1015
			return SSViewer::$topLevel[sizeof(SSViewer::$topLevel)-1];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1016
		}
1017
	}
1018
1019
	/**
1020
	 * @return Zend_Cache_Core
1021
	 */
1022
	protected static function defaultPartialCacheStore()
1023
	{
1024
		return SS_Cache::factory('cacheblock', 'Output', array('disable-segmentation' => true));
1025
	}
1026
1027
	/**
1028
	 * Call this to disable rewriting of <a href="#xxx"> links.  This is useful in Ajax applications.
1029
	 * It returns the SSViewer objects, so that you can call new SSViewer("X")->dontRewriteHashlinks()->process();
1030
	 */
1031
	public function dontRewriteHashlinks() {
1032
		$this->rewriteHashlinks = false;
1033
		Config::inst()->update('SSViewer', 'rewrite_hash_links', false);
1034
		return $this;
1035
	}
1036
1037
	public function exists() {
1038
		return $this->chosenTemplates;
1039
	}
1040
1041
	/**
1042
	 * @param string $identifier A template name without '.ss' extension or path
1043
	 * @param string $type The template type, either "main", "Includes" or "Layout"
1044
	 *
1045
	 * @return string Full system path to a template file
1046
	 */
1047
	public static function getTemplateFileByType($identifier, $type) {
1048
		$loader = SS_TemplateLoader::instance();
1049
		if(Config::inst()->get('SSViewer', 'theme_enabled')) {
1050
			$theme = Config::inst()->get('SSViewer', 'theme');
1051
		} else {
1052
			$theme = null;
1053
		}
1054
		$found  = $loader->findTemplates("$type/$identifier", $theme);
1055
1056
		if (isset($found['main'])) {
1057
			return $found['main'];
1058
		}
1059
		else if (!empty($found)) {
1060
			$founds = array_values($found);
1061
			return $founds[0];
1062
		}
1063
	}
1064
1065
	/**
1066
	 * Clears all parsed template files in the cache folder.
1067
	 *
1068
	 * Can only be called once per request (there may be multiple SSViewer instances).
1069
	 *
1070
	 * @param bool $force Set this to true to force a re-flush. If left to false, flushing
1071
	 * may only be performed once a request.
1072
	 */
1073
	public static function flush_template_cache($force = false) {
1074
		if (!self::$template_cache_flushed || $force) {
1075
			$dir = dir(TEMP_FOLDER);
1076
			while (false !== ($file = $dir->read())) {
1077
				if (strstr($file, '.cache')) unlink(TEMP_FOLDER . '/' . $file);
1078
			}
1079
			self::$template_cache_flushed = true;
1080
		}
1081
	}
1082
1083
	/**
1084
	 * Clears all partial cache blocks.
1085
	 *
1086
	 * Can only be called once per request (there may be multiple SSViewer instances).
1087
	 *
1088
	 * @param bool $force Set this to true to force a re-flush. If left to false, flushing
1089
	 * may only be performed once a request.
1090
	 */
1091
	public static function flush_cacheblock_cache($force = false) {
1092
		if (!self::$cacheblock_cache_flushed || $force) {
1093
			$cache = self::defaultPartialCacheStore();
1094
			$backend = $cache->getBackend();
1095
1096
			if(
1097
				$backend instanceof Zend_Cache_Backend_ExtendedInterface
1098
				&& ($capabilities = $backend->getCapabilities())
1099
				&& $capabilities['tags']
1100
			) {
1101
				$cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, $cache->getTags());
1102
			} else {
1103
				$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
1104
			}
1105
1106
1107
			self::$cacheblock_cache_flushed = true;
1108
		}
1109
	}
1110
1111
	/**
1112
	 * @var Zend_Cache_Core
1113
	 */
1114
	protected $partialCacheStore = null;
1115
1116
	/**
1117
	 * Set the cache object to use when storing / retrieving partial cache blocks.
1118
	 *
1119
	 * @param Zend_Cache_Core $cache
1120
	 */
1121
	public function setPartialCacheStore($cache) {
1122
		$this->partialCacheStore = $cache;
1123
	}
1124
1125
	/**
1126
	 * Get the cache object to use when storing / retrieving partial cache blocks.
1127
	 *
1128
	 * @return Zend_Cache_Core
1129
	 */
1130
	public function getPartialCacheStore() {
1131
		return $this->partialCacheStore ? $this->partialCacheStore : self::defaultPartialCacheStore();
1132
	}
1133
1134
	/**
1135
	 * Flag whether to include the requirements in this response.
1136
	 *
1137
	 * @param boolean
1138
	 */
1139
	public function includeRequirements($incl = true) {
1140
		$this->includeRequirements = $incl;
1141
	}
1142
1143
	/**
1144
	 * An internal utility function to set up variables in preparation for including a compiled
1145
	 * template, then do the include
1146
	 *
1147
	 * Effectively this is the common code that both SSViewer#process and SSViewer_FromString#process call
1148
	 *
1149
	 * @param string $cacheFile - The path to the file that contains the template compiled to PHP
1150
	 * @param SS_Object $item - The item to use as the root scope for the template
1151
	 * @param array|null $overlay - Any variables to layer on top of the scope
1152
	 * @param array|null $underlay - Any variables to layer underneath the scope
1153
	 * @param SS_Object $inheritedScope - the current scope of a parent template including a sub-template
1154
	 *
1155
	 * @return string - The result of executing the template
1156
	 */
1157
	protected function includeGeneratedTemplate($cacheFile, $item, $overlay, $underlay, $inheritedScope = null) {
0 ignored issues
show
Coding Style introduced by
includeGeneratedTemplate uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1158
		if(isset($_GET['showtemplate']) && $_GET['showtemplate'] && Permission::check('ADMIN')) {
1159
			$lines = file($cacheFile);
1160
			echo "<h2>Template: $cacheFile</h2>";
1161
			echo "<pre>";
1162
			foreach($lines as $num => $line) {
1163
				echo str_pad($num+1,5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
1164
			}
1165
			echo "</pre>";
1166
		}
1167
1168
		$cache = $this->getPartialCacheStore();
1169
		$scope = new SSViewer_DataPresenter($item, $overlay, $underlay, $inheritedScope);
1170
		$val = '';
1171
1172
		include($cacheFile);
1173
1174
		return $val;
1175
	}
1176
1177
	/**
1178
	 * The process() method handles the "meat" of the template processing.
1179
	 *
1180
	 * It takes care of caching the output (via {@link SS_Cache}), as well as
1181
	 * replacing the special "$Content" and "$Layout" placeholders with their
1182
	 * respective subtemplates.
1183
	 *
1184
	 * The method injects extra HTML in the header via {@link Requirements::includeInHTML()}.
1185
	 *
1186
	 * Note: You can call this method indirectly by {@link ViewableData->renderWith()}.
1187
	 *
1188
	 * @param ViewableData $item
1189
	 * @param array|null $arguments - arguments to an included template
1190
	 * @param SS_Object $inheritedScope - the current scope of a parent template including a sub-template
1191
	 *
1192
	 * @return HTMLText Parsed template output.
1193
	 */
1194
	public function process($item, $arguments = null, $inheritedScope = null) {
0 ignored issues
show
Coding Style introduced by
process uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1195
		SSViewer::$topLevel[] = $item;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1196
1197
		if(isset($this->chosenTemplates['main'])) {
1198
			$template = $this->chosenTemplates['main'];
1199
		} else {
1200
			$keys = array_keys($this->chosenTemplates);
1201
			$key = reset($keys);
1202
			$template = $this->chosenTemplates[$key];
1203
		}
1204
1205
		$cacheFile = TEMP_FOLDER . "/.cache"
1206
			. str_replace(array('\\','/',':'), '.', Director::makeRelative(realpath($template)));
1207
		$lastEdited = filemtime($template);
1208
1209
		if(!file_exists($cacheFile) || filemtime($cacheFile) < $lastEdited) {
1210
			$content = file_get_contents($template);
1211
			$content = $this->parseTemplateContent($content, $template);
1212
1213
			$fh = fopen($cacheFile,'w');
1214
			fwrite($fh, $content);
1215
			fclose($fh);
1216
		}
1217
1218
		$underlay = array('I18NNamespace' => basename($template));
1219
1220
		// Makes the rendered sub-templates available on the parent item,
1221
		// through $Content and $Layout placeholders.
1222
		foreach(array('Content', 'Layout') as $subtemplate) {
1223
			if(isset($this->chosenTemplates[$subtemplate])) {
1224
				$subtemplateViewer = clone $this;
1225
				// Disable requirements - this will be handled by the parent template
1226
				$subtemplateViewer->includeRequirements(false);
1227
				// The subtemplate is the only file we want to process, so set it as the "main" template file
1228
				$subtemplateViewer->chosenTemplates = array('main' => $this->chosenTemplates[$subtemplate]);
1229
1230
				$underlay[$subtemplate] = $subtemplateViewer->process($item, $arguments);
1231
			}
1232
		}
1233
1234
		$output = $this->includeGeneratedTemplate($cacheFile, $item, $arguments, $underlay, $inheritedScope);
1235
1236
		if($this->includeRequirements) {
1237
			$output = Requirements::includeInHTML($template, $output);
1238
		}
1239
1240
		array_pop(SSViewer::$topLevel);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1241
1242
		// If we have our crazy base tag, then fix # links referencing the current page.
1243
1244
		$rewrite = Config::inst()->get('SSViewer', 'rewrite_hash_links');
1245
		if($this->rewriteHashlinks && $rewrite) {
1246
			if(strpos($output, '<base') !== false) {
1247
				if($rewrite === 'php') {
1248
					$thisURLRelativeToBase = "<?php echo Convert::raw2att(preg_replace(\"/^(\\\\/)+/\", \"/\", \$_SERVER['REQUEST_URI'])); ?>";
1249
				} else {
1250
					$thisURLRelativeToBase = Convert::raw2att(preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI']));
1251
				}
1252
1253
				$output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output);
1254
			}
1255
		}
1256
1257
		return DBField::create_field('HTMLText', $output, null, array('shortcodes' => false));
1258
	}
1259
1260
	/**
1261
	 * Execute the given template, passing it the given data.
1262
	 * Used by the <% include %> template tag to process templates.
1263
	 *
1264
	 * @param string $template Template name
1265
	 * @param mixed $data Data context
1266
	 * @param array $arguments Additional arguments
1267
	 * @return string Evaluated result
1268
	 */
1269
	public static function execute_template($template, $data, $arguments = null, $scope = null) {
1270
		$v = new SSViewer($template);
1271
		$v->includeRequirements(false);
1272
1273
		return $v->process($data, $arguments, $scope);
1274
	}
1275
1276
	/**
1277
	 * Execute the evaluated string, passing it the given data.
1278
	 * Used by partial caching to evaluate custom cache keys expressed using
1279
	 * template expressions
1280
	 *
1281
	 * @param string $content Input string
1282
	 * @param mixed $data Data context
1283
	 * @param array $arguments Additional arguments
1284
	 * @return string Evaluated result
1285
	 */
1286
	public static function execute_string($content, $data, $arguments = null) {
1287
		$v = SSViewer::fromString($content);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1288
		$v->includeRequirements(false);
1289
1290
		return $v->process($data, $arguments);
1291
	}
1292
1293
	public function parseTemplateContent($content, $template="") {
1294
		return $this->getParser()->compileString(
1295
			$content,
1296
			$template,
1297
			Director::isDev() && Config::inst()->get('SSViewer', 'source_file_comments')
1298
		);
1299
	}
1300
1301
	/**
1302
	 * Returns the filenames of the template that will be rendered.  It is a map that may contain
1303
	 * 'Content' & 'Layout', and will have to contain 'main'
1304
	 */
1305
	public function templates() {
1306
		return $this->chosenTemplates;
1307
	}
1308
1309
	/**
1310
	 * @param string $type "Layout" or "main"
1311
	 * @param string $file Full system path to the template file
1312
	 */
1313
	public function setTemplateFile($type, $file) {
1314
		$this->chosenTemplates[$type] = $file;
1315
	}
1316
1317
	/**
1318
	 * Return an appropriate base tag for the given template.
1319
	 * It will be closed on an XHTML document, and unclosed on an HTML document.
1320
	 *
1321
	 * @param $contentGeneratedSoFar The content of the template generated so far; it should contain
1322
	 * the DOCTYPE declaration.
1323
	 */
1324
	public static function get_base_tag($contentGeneratedSoFar) {
1325
		$base = Director::absoluteBaseURL();
1326
1327
		// Is the document XHTML?
1328
		if(preg_match('/<!DOCTYPE[^>]+xhtml/i', $contentGeneratedSoFar)) {
1329
			return "<base href=\"$base\" />";
1330
		} else {
1331
			return "<base href=\"$base\"><!--[if lte IE 6]></base><![endif]-->";
1332
		}
1333
	}
1334
}
1335
1336
/**
1337
 * Special SSViewer that will process a template passed as a string, rather than a filename.
1338
 * @package framework
1339
 * @subpackage view
1340
 */
1341
class SSViewer_FromString extends SSViewer {
1342
1343
	/**
1344
	 * The global template caching behaviour if no instance override is specified
1345
	 * @config
1346
	 * @var bool
1347
	 */
1348
	private static $cache_template = true;
1349
1350
	/**
1351
	 * The template to use
1352
	 * @var string
1353
	 */
1354
	protected $content;
1355
1356
	/**
1357
	 * Indicates whether templates should be cached
1358
	 * @var bool
1359
	 */
1360
	protected $cacheTemplate;
1361
1362
	public function __construct($content, TemplateParser $parser = null) {
1363
		if ($parser) {
1364
			$this->setParser($parser);
1365
		}
1366
1367
		$this->content = $content;
1368
	}
1369
1370
	public function process($item, $arguments = null, $scope = null) {
0 ignored issues
show
Coding Style introduced by
process uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1371
		$hash = sha1($this->content);
1372
		$cacheFile = TEMP_FOLDER . "/.cache.$hash";
1373
1374
		if(!file_exists($cacheFile) || isset($_GET['flush'])) {
1375
			$content = $this->parseTemplateContent($this->content, "string sha1=$hash");
1376
			$fh = fopen($cacheFile,'w');
1377
			fwrite($fh, $content);
1378
			fclose($fh);
1379
		}
1380
1381
		$val = $this->includeGeneratedTemplate($cacheFile, $item, $arguments, null, $scope);
1382
1383
		if ($this->cacheTemplate !== null) {
1384
			$cacheTemplate = $this->cacheTemplate;
1385
		} else {
1386
			$cacheTemplate = Config::inst()->get('SSViewer_FromString', 'cache_template');
1387
		}
1388
1389
		if (!$cacheTemplate) {
1390
			unlink($cacheFile);
1391
		}
1392
1393
		return $val;
1394
	}
1395
1396
	/**
1397
	 * @param boolean $cacheTemplate
1398
	 */
1399
	public function setCacheTemplate($cacheTemplate) {
1400
		$this->cacheTemplate = (bool) $cacheTemplate;
1401
	}
1402
1403
	/**
1404
	 * @return boolean
1405
	 */
1406
	public function getCacheTemplate() {
1407
		return $this->cacheTemplate;
1408
	}
1409
}
1410