View   F
last analyzed

Complexity

Total Complexity 140

Size/Duplication

Total Lines 1153
Duplicated Lines 4.16 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 48
loc 1153
rs 0.6314
wmc 140
lcom 1
cbo 17

37 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 9 24 7
A getEventManager() 10 10 3
C element() 0 30 7
A elementExists() 0 3 1
C render() 0 22 7
B renderLayout() 0 31 4
B renderCache() 0 22 5
A getVars() 0 3 1
A getVar() 0 3 1
A get() 0 6 2
A blocks() 0 3 1
A start() 0 3 1
A startIfEmpty() 0 3 1
A append() 0 3 1
A prepend() 0 3 1
A assign() 0 3 1
A fetch() 0 3 1
A end() 0 3 1
D extend() 0 34 9
A addScript() 0 9 3
A uuid() 0 11 2
A set() 16 16 4
B __get() 13 20 9
A __set() 0 8 2
A __isset() 0 10 3
A loadHelpers() 0 7 2
B _render() 0 35 4
A _evaluate() 0 10 1
A loadHelper() 0 3 1
F _getViewFileName() 0 49 18
B pluginSplit() 0 12 5
B _getLayoutFileName() 0 23 6
A _getExtensions() 0 7 2
A _getElementFileName() 0 14 4
C _paths() 0 38 12
A _elementCache() 0 23 3
B _renderElement() 0 22 4

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like View often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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

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

1
<?php
2
/**
3
 * Methods for displaying presentation data in the view.
4
 *
5
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13
 * @link          http://cakephp.org CakePHP(tm) Project
14
 * @package       Cake.View
15
 * @since         CakePHP(tm) v 0.10.0.1076
16
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
17
 */
18
19
App::uses('HelperCollection', 'View');
20
App::uses('AppHelper', 'View/Helper');
21
App::uses('Router', 'Routing');
22
App::uses('ViewBlock', 'View');
23
App::uses('CakeEvent', 'Event');
24
App::uses('CakeEventManager', 'Event');
25
App::uses('CakeResponse', 'Network');
26
27
/**
28
 * View, the V in the MVC triad. View interacts with Helpers and view variables passed
29
 * in from the controller to render the results of the controller action. Often this is HTML,
30
 * but can also take the form of JSON, XML, PDF's or streaming files.
31
 *
32
 * CakePHP uses a two-step-view pattern. This means that the view content is rendered first,
33
 * and then inserted into the selected layout. This also means you can pass data from the view to the
34
 * layout using `$this->set()`
35
 *
36
 * Since 2.1, the base View class also includes support for themes by default. Theme views are regular
37
 * view files that can provide unique HTML and static assets. If theme views are not found for the
38
 * current view the default app view files will be used. You can set `$this->theme = 'mytheme'`
39
 * in your Controller to use the Themes.
40
 *
41
 * Example of theme path with `$this->theme = 'SuperHot';` Would be `app/View/Themed/SuperHot/Posts`
42
 *
43
 * @package       Cake.View
44
 * @property      CacheHelper $Cache
45
 * @property      FormHelper $Form
46
 * @property      HtmlHelper $Html
47
 * @property      JsHelper $Js
48
 * @property      NumberHelper $Number
49
 * @property      PaginatorHelper $Paginator
50
 * @property      RssHelper $Rss
51
 * @property      SessionHelper $Session
52
 * @property      TextHelper $Text
53
 * @property      TimeHelper $Time
54
 * @property      ViewBlock $Blocks
55
 */
56
class View extends Object {
57
58
/**
59
 * Helpers collection
60
 *
61
 * @var HelperCollection
62
 */
63
	public $Helpers;
64
65
/**
66
 * ViewBlock instance.
67
 *
68
 * @var ViewBlock
69
 */
70
	public $Blocks;
71
72
/**
73
 * Name of the plugin.
74
 *
75
 * @link http://manual.cakephp.org/chapter/plugins
76
 * @var string
77
 */
78
	public $plugin = null;
79
80
/**
81
 * Name of the controller.
82
 *
83
 * @var string Name of controller
84
 */
85
	public $name = null;
86
87
/**
88
 * Current passed params
89
 *
90
 * @var mixed
91
 */
92
	public $passedArgs = array();
93
94
/**
95
 * An array of names of built-in helpers to include.
96
 *
97
 * @var mixed A single name as a string or a list of names as an array.
98
 */
99
	public $helpers = array();
100
101
/**
102
 * Path to View.
103
 *
104
 * @var string Path to View
105
 */
106
	public $viewPath = null;
107
108
/**
109
 * Variables for the view
110
 *
111
 * @var array
112
 */
113
	public $viewVars = array();
114
115
/**
116
 * Name of view to use with this View.
117
 *
118
 * @var string
119
 */
120
	public $view = null;
121
122
/**
123
 * Name of layout to use with this View.
124
 *
125
 * @var string
126
 */
127
	public $layout = 'default';
128
129
/**
130
 * Path to Layout.
131
 *
132
 * @var string Path to Layout
133
 */
134
	public $layoutPath = null;
135
136
/**
137
 * Turns on or off CakePHP's conventional mode of applying layout files. On by default.
138
 * Setting to off means that layouts will not be automatically applied to rendered views.
139
 *
140
 * @var boolean
141
 */
142
	public $autoLayout = true;
143
144
/**
145
 * File extension. Defaults to CakePHP's template ".ctp".
146
 *
147
 * @var string
148
 */
149
	public $ext = '.ctp';
150
151
/**
152
 * Sub-directory for this view file. This is often used for extension based routing.
153
 * Eg. With an `xml` extension, $subDir would be `xml/`
154
 *
155
 * @var string
156
 */
157
	public $subDir = null;
158
159
/**
160
 * Theme name.
161
 *
162
 * @var string
163
 */
164
	public $theme = null;
165
166
/**
167
 * Used to define methods a controller that will be cached.
168
 *
169
 * @see Controller::$cacheAction
170
 * @var mixed
171
 */
172
	public $cacheAction = false;
173
174
/**
175
 * Holds current errors for the model validation.
176
 *
177
 * @var array
178
 */
179
	public $validationErrors = array();
180
181
/**
182
 * True when the view has been rendered.
183
 *
184
 * @var boolean
185
 */
186
	public $hasRendered = false;
187
188
/**
189
 * List of generated DOM UUIDs.
190
 *
191
 * @var array
192
 */
193
	public $uuids = array();
194
195
/**
196
 * An instance of a CakeRequest object that contains information about the current request.
197
 * This object contains all the information about a request and several methods for reading
198
 * additional information about the request.
199
 *
200
 * @var CakeRequest
201
 */
202
	public $request;
203
204
/**
205
 * Reference to the Response object
206
 *
207
 * @var CakeResponse
208
 */
209
	public $response;
210
211
/**
212
 * The Cache configuration View will use to store cached elements. Changing this will change
213
 * the default configuration elements are stored under. You can also choose a cache config
214
 * per element.
215
 *
216
 * @var string
217
 * @see View::element()
218
 */
219
	public $elementCache = 'default';
220
221
/**
222
 * Element cache settings
223
 *
224
 * @var array
225
 * @see View::_elementCache();
226
 * @see View::_renderElement
227
 */
228
	public $elementCacheSettings = array();
229
230
/**
231
 * List of variables to collect from the associated controller.
232
 *
233
 * @var array
234
 */
235
	protected $_passedVars = array(
236
		'viewVars', 'autoLayout', 'ext', 'helpers', 'view', 'layout', 'name', 'theme',
237
		'layoutPath', 'viewPath', 'request', 'plugin', 'passedArgs', 'cacheAction'
238
	);
239
240
/**
241
 * Scripts (and/or other <head /> tags) for the layout.
242
 *
243
 * @var array
244
 */
245
	protected $_scripts = array();
246
247
/**
248
 * Holds an array of paths.
249
 *
250
 * @var array
251
 */
252
	protected $_paths = array();
253
254
/**
255
 * The names of views and their parents used with View::extend();
256
 *
257
 * @var array
258
 */
259
	protected $_parents = array();
260
261
/**
262
 * The currently rendering view file. Used for resolving parent files.
263
 *
264
 * @var string
265
 */
266
	protected $_current = null;
267
268
/**
269
 * Currently rendering an element. Used for finding parent fragments
270
 * for elements.
271
 *
272
 * @var string
273
 */
274
	protected $_currentType = '';
275
276
/**
277
 * Content stack, used for nested templates that all use View::extend();
278
 *
279
 * @var array
280
 */
281
	protected $_stack = array();
282
283
/**
284
 * Instance of the CakeEventManager this View object is using
285
 * to dispatch inner events. Usually the manager is shared with
286
 * the controller, so it it possible to register view events in
287
 * the controller layer.
288
 *
289
 * @var CakeEventManager
290
 */
291
	protected $_eventManager = null;
292
293
/**
294
 * Whether the event manager was already configured for this object
295
 *
296
 * @var boolean
297
 */
298
	protected $_eventManagerConfigured = false;
299
300
/**
301
 * Constant for view file type 'view'
302
 *
303
 * @var string
304
 */
305
	const TYPE_VIEW = 'view';
306
307
/**
308
 * Constant for view file type 'element'
309
 *
310
 * @var string
311
 */
312
	const TYPE_ELEMENT = 'element';
313
314
/**
315
 * Constant for view file type 'layout'
316
 *
317
 * @var string
318
 */
319
	const TYPE_LAYOUT = 'layout';
320
321
/**
322
 * Constructor
323
 *
324
 * @param Controller $controller A controller object to pull View::_passedVars from.
325
 */
326
	public function __construct(Controller $controller = null) {
327
		if (is_object($controller)) {
328
			$count = count($this->_passedVars);
329 View Code Duplication
			for ($j = 0; $j < $count; $j++) {
330
				$var = $this->_passedVars[$j];
331
				$this->{$var} = $controller->{$var};
332
			}
333
			$this->_eventManager = $controller->getEventManager();
334
		}
335
		if (empty($this->request) && !($this->request = Router::getRequest(true))) {
336
			$this->request = new CakeRequest(null, false);
337
			$this->request->base = '';
338
			$this->request->here = $this->request->webroot = '/';
339
		}
340 View Code Duplication
		if (is_object($controller) && isset($controller->response)) {
341
			$this->response = $controller->response;
342
		} else {
343
			$this->response = new CakeResponse();
344
		}
345
		$this->Helpers = new HelperCollection($this);
346
		$this->Blocks = new ViewBlock();
347
		$this->loadHelpers();
348
		parent::__construct();
349
	}
350
351
/**
352
 * Returns the CakeEventManager manager instance that is handling any callbacks.
353
 * You can use this instance to register any new listeners or callbacks to the
354
 * controller events, or create your own events and trigger them at will.
355
 *
356
 * @return CakeEventManager
357
 */
358 View Code Duplication
	public function getEventManager() {
359
		if (empty($this->_eventManager)) {
360
			$this->_eventManager = new CakeEventManager();
361
		}
362
		if (!$this->_eventManagerConfigured) {
363
			$this->_eventManager->attach($this->Helpers);
364
			$this->_eventManagerConfigured = true;
365
		}
366
		return $this->_eventManager;
367
	}
368
369
/**
370
 * Renders a piece of PHP with provided parameters and returns HTML, XML, or any other string.
371
 *
372
 * This realizes the concept of Elements, (or "partial layouts") and the $params array is used to send
373
 * data to be used in the element. Elements can be cached improving performance by using the `cache` option.
374
 *
375
 * @param string $name Name of template file in the/app/View/Elements/ folder,
376
 *   or `MyPlugin.template` to use the template element from MyPlugin. If the element
377
 *   is not found in the plugin, the normal view path cascade will be searched.
378
 * @param array $data Array of data to be made available to the rendered view (i.e. the Element)
379
 * @param array $options Array of options. Possible keys are:
380
 * - `cache` - Can either be `true`, to enable caching using the config in View::$elementCache. Or an array
381
 *   If an array, the following keys can be used:
382
 *   - `config` - Used to store the cached element in a custom cache configuration.
383
 *   - `key` - Used to define the key used in the Cache::write(). It will be prefixed with `element_`
384
 * - `plugin` - Load an element from a specific plugin. This option is deprecated, see below.
385
 * - `callbacks` - Set to true to fire beforeRender and afterRender helper callbacks for this element.
386
 *   Defaults to false.
387
 * - `ignoreMissing` - Used to allow missing elements. Set to true to not trigger notices.
388
 * @return string Rendered Element
389
 * @deprecated The `$options['plugin']` is deprecated and will be removed in CakePHP 3.0. Use
390
 *   `Plugin.element_name` instead.
391
 */
392
	public function element($name, $data = array(), $options = array()) {
393
		$file = $plugin = null;
394
395
		if (isset($options['plugin'])) {
396
			$name = Inflector::camelize($options['plugin']) . '.' . $name;
397
		}
398
399
		if (!isset($options['callbacks'])) {
400
			$options['callbacks'] = false;
401
		}
402
403
		if (isset($options['cache'])) {
404
			$contents = $this->_elementCache($name, $data, $options);
405
			if ($contents !== false) {
406
				return $contents;
407
			}
408
		}
409
410
		$file = $this->_getElementFilename($name);
411
		if ($file) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $file of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
412
			return $this->_renderElement($file, $data, $options);
413
		}
414
415
		if (empty($options['ignoreMissing'])) {
416
			list ($plugin, $name) = pluginSplit($name, true);
417
			$name = str_replace('/', DS, $name);
418
			$file = $plugin . 'Elements' . DS . $name . $this->ext;
419
			trigger_error(__d('cake_dev', 'Element Not Found: %s', $file), E_USER_NOTICE);
420
		}
421
	}
422
423
/**
424
 * Checks if an element exists
425
 *
426
 * @param string $name Name of template file in the /app/View/Elements/ folder,
427
 *   or `MyPlugin.template` to check the template element from MyPlugin. If the element
428
 *   is not found in the plugin, the normal view path cascade will be searched.
429
 * @return boolean Success
430
 */
431
	public function elementExists($name) {
432
		return (bool)$this->_getElementFilename($name);
433
	}
434
435
/**
436
 * Renders view for given view file and layout.
437
 *
438
 * Render triggers helper callbacks, which are fired before and after the view are rendered,
439
 * as well as before and after the layout. The helper callbacks are called:
440
 *
441
 * - `beforeRender`
442
 * - `afterRender`
443
 * - `beforeLayout`
444
 * - `afterLayout`
445
 *
446
 * If View::$autoRender is false and no `$layout` is provided, the view will be returned bare.
447
 *
448
 * View and layout names can point to plugin views/layouts. Using the `Plugin.view` syntax
449
 * a plugin view/layout can be used instead of the app ones. If the chosen plugin is not found
450
 * the view will be located along the regular view path cascade.
451
 *
452
 * @param string $view Name of view file to use
453
 * @param string $layout Layout to use.
454
 * @return string|null Rendered content or null if content already rendered and returned earlier.
455
 * @throws CakeException If there is an error in the view.
456
 */
457
	public function render($view = null, $layout = null) {
458
		if ($this->hasRendered) {
459
			return;
460
		}
461
		$this->Blocks->set('content', '');
462
463
		if ($view !== false && $viewFileName = $this->_getViewFileName($view)) {
464
			$this->_currentType = self::TYPE_VIEW;
465
			$this->getEventManager()->dispatch(new CakeEvent('View.beforeRender', $this, array($viewFileName)));
466
			$this->Blocks->set('content', $this->_render($viewFileName));
467
			$this->getEventManager()->dispatch(new CakeEvent('View.afterRender', $this, array($viewFileName)));
468
		}
469
470
		if ($layout === null) {
471
			$layout = $this->layout;
472
		}
473
		if ($layout && $this->autoLayout) {
474
			$this->Blocks->set('content', $this->renderLayout('', $layout));
475
		}
476
		$this->hasRendered = true;
477
		return $this->Blocks->get('content');
478
	}
479
480
/**
481
 * Renders a layout. Returns output from _render(). Returns false on error.
482
 * Several variables are created for use in layout.
483
 *
484
 * - `title_for_layout` - A backwards compatible place holder, you should set this value if you want more control.
485
 * - `content_for_layout` - contains rendered view file
486
 * - `scripts_for_layout` - Contains content added with addScript() as well as any content in
487
 *   the 'meta', 'css', and 'script' blocks. They are appended in that order.
488
 *
489
 * Deprecated features:
490
 *
491
 * - `$scripts_for_layout` is deprecated and will be removed in CakePHP 3.0.
492
 *   Use the block features instead. `meta`, `css` and `script` will be populated
493
 *   by the matching methods on HtmlHelper.
494
 * - `$title_for_layout` is deprecated and will be removed in CakePHP 3.0
495
 * - `$content_for_layout` is deprecated and will be removed in CakePHP 3.0.
496
 *   Use the `content` block instead.
497
 *
498
 * @param string $content Content to render in a view, wrapped by the surrounding layout.
499
 * @param string $layout Layout name
500
 * @return mixed Rendered output, or false on error
501
 * @throws CakeException if there is an error in the view.
502
 */
503
	public function renderLayout($content, $layout = null) {
504
		$layoutFileName = $this->_getLayoutFileName($layout);
505
		if (empty($layoutFileName)) {
506
			return $this->Blocks->get('content');
507
		}
508
509
		if (empty($content)) {
510
			$content = $this->Blocks->get('content');
511
		} else {
512
			$this->Blocks->set('content', $content);
513
		}
514
		$this->getEventManager()->dispatch(new CakeEvent('View.beforeLayout', $this, array($layoutFileName)));
515
516
		$scripts = implode("\n\t", $this->_scripts);
517
		$scripts .= $this->Blocks->get('meta') . $this->Blocks->get('css') . $this->Blocks->get('script');
518
519
		$this->viewVars = array_merge($this->viewVars, array(
520
			'content_for_layout' => $content,
521
			'scripts_for_layout' => $scripts,
522
		));
523
524
		if (!isset($this->viewVars['title_for_layout'])) {
525
			$this->viewVars['title_for_layout'] = Inflector::humanize($this->viewPath);
526
		}
527
528
		$this->_currentType = self::TYPE_LAYOUT;
529
		$this->Blocks->set('content', $this->_render($layoutFileName));
530
531
		$this->getEventManager()->dispatch(new CakeEvent('View.afterLayout', $this, array($layoutFileName)));
532
		return $this->Blocks->get('content');
533
	}
534
535
/**
536
 * Render cached view. Works in concert with CacheHelper and Dispatcher to
537
 * render cached view files.
538
 *
539
 * @param string $filename the cache file to include
540
 * @param string $timeStart the page render start time
541
 * @return boolean Success of rendering the cached file.
542
 */
543
	public function renderCache($filename, $timeStart) {
544
		$response = $this->response;
545
		ob_start();
546
		include $filename;
547
548
		$type = $response->mapType($response->type());
549
		if (Configure::read('debug') > 0 && $type === 'html') {
550
			echo "<!-- Cached Render Time: " . round(microtime(true) - $timeStart, 4) . "s -->";
551
		}
552
		$out = ob_get_clean();
553
554
		if (preg_match('/^<!--cachetime:(\\d+)-->/', $out, $match)) {
555
			if (time() >= $match['1']) {
556
				//@codingStandardsIgnoreStart
557
				@unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
558
				//@codingStandardsIgnoreEnd
559
				unset($out);
560
				return false;
561
			}
562
			return substr($out, strlen($match[0]));
563
		}
564
	}
565
566
/**
567
 * Returns a list of variables available in the current View context
568
 *
569
 * @return array Array of the set view variable names.
570
 */
571
	public function getVars() {
572
		return array_keys($this->viewVars);
573
	}
574
575
/**
576
 * Returns the contents of the given View variable(s)
577
 *
578
 * @param string $var The view var you want the contents of.
579
 * @return mixed The content of the named var if its set, otherwise null.
580
 * @deprecated Will be removed in 3.0. Use View::get() instead.
581
 */
582
	public function getVar($var) {
583
		return $this->get($var);
584
	}
585
586
/**
587
 * Returns the contents of the given View variable or a block.
588
 * Blocks are checked before view variables.
589
 *
590
 * @param string $var The view var you want the contents of.
591
 * @return mixed The content of the named var if its set, otherwise null.
592
 */
593
	public function get($var) {
594
		if (!isset($this->viewVars[$var])) {
595
			return null;
596
		}
597
		return $this->viewVars[$var];
598
	}
599
600
/**
601
 * Get the names of all the existing blocks.
602
 *
603
 * @return array An array containing the blocks.
604
 * @see ViewBlock::keys()
605
 */
606
	public function blocks() {
607
		return $this->Blocks->keys();
608
	}
609
610
/**
611
 * Start capturing output for a 'block'
612
 *
613
 * @param string $name The name of the block to capture for.
614
 * @return void
615
 * @see ViewBlock::start()
616
 */
617
	public function start($name) {
618
		$this->Blocks->start($name);
619
	}
620
621
/**
622
 * Start capturing output for a 'block' if it has no content
623
 *
624
 * @param string $name The name of the block to capture for.
625
 * @return void
626
 * @see ViewBlock::startIfEmpty()
627
 */
628
	public function startIfEmpty($name) {
629
		$this->Blocks->startIfEmpty($name);
630
	}
631
632
/**
633
 * Append to an existing or new block. Appending to a new
634
 * block will create the block.
635
 *
636
 * @param string $name Name of the block
637
 * @param mixed $value The content for the block.
638
 * @return void
639
 * @see ViewBlock::concat()
640
 */
641
	public function append($name, $value = null) {
642
		$this->Blocks->concat($name, $value);
643
	}
644
645
/**
646
 * Prepend to an existing or new block. Prepending to a new
647
 * block will create the block.
648
 *
649
 * @param string $name Name of the block
650
 * @param mixed $value The content for the block.
651
 * @return void
652
 * @see ViewBlock::concat()
653
 */
654
	public function prepend($name, $value = null) {
655
		$this->Blocks->concat($name, $value, ViewBlock::PREPEND);
656
	}
657
658
/**
659
 * Set the content for a block. This will overwrite any
660
 * existing content.
661
 *
662
 * @param string $name Name of the block
663
 * @param mixed $value The content for the block.
664
 * @return void
665
 * @see ViewBlock::set()
666
 */
667
	public function assign($name, $value) {
668
		$this->Blocks->set($name, $value);
669
	}
670
671
/**
672
 * Fetch the content for a block. If a block is
673
 * empty or undefined '' will be returned.
674
 *
675
 * @param string $name Name of the block
676
 * @param string $default Default text
677
 * @return string $default The block content or $default if the block does not exist.
678
 * @see ViewBlock::get()
679
 */
680
	public function fetch($name, $default = '') {
681
		return $this->Blocks->get($name, $default);
682
	}
683
684
/**
685
 * End a capturing block. The compliment to View::start()
686
 *
687
 * @return void
688
 * @see ViewBlock::end()
689
 */
690
	public function end() {
691
		$this->Blocks->end();
692
	}
693
694
/**
695
 * Provides view or element extension/inheritance. Views can extends a
696
 * parent view and populate blocks in the parent template.
697
 *
698
 * @param string $name The view or element to 'extend' the current one with.
699
 * @return void
700
 * @throws LogicException when you extend a view with itself or make extend loops.
701
 * @throws LogicException when you extend an element which doesn't exist
702
 */
703
	public function extend($name) {
704
		if ($name[0] === '/' || $this->_currentType === self::TYPE_VIEW) {
705
			$parent = $this->_getViewFileName($name);
706
		} else {
707
			switch ($this->_currentType) {
708
				case self::TYPE_ELEMENT:
709
					$parent = $this->_getElementFileName($name);
710
					if (!$parent) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parent of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
711
						list($plugin, $name) = $this->pluginSplit($name);
712
						$paths = $this->_paths($plugin);
713
						$defaultPath = $paths[0] . 'Elements' . DS;
714
						throw new LogicException(__d(
715
							'cake_dev',
716
							'You cannot extend an element which does not exist (%s).',
717
							$defaultPath . $name . $this->ext
718
						));
719
					}
720
					break;
721
				case self::TYPE_LAYOUT:
722
					$parent = $this->_getLayoutFileName($name);
723
					break;
724
				default:
725
					$parent = $this->_getViewFileName($name);
726
			}
727
		}
728
729
		if ($parent == $this->_current) {
730
			throw new LogicException(__d('cake_dev', 'You cannot have views extend themselves.'));
731
		}
732
		if (isset($this->_parents[$parent]) && $this->_parents[$parent] == $this->_current) {
733
			throw new LogicException(__d('cake_dev', 'You cannot have views extend in a loop.'));
734
		}
735
		$this->_parents[$this->_current] = $parent;
736
	}
737
738
/**
739
 * Adds a script block or other element to be inserted in $scripts_for_layout in
740
 * the `<head />` of a document layout
741
 *
742
 * @param string $name Either the key name for the script, or the script content. Name can be used to
743
 *   update/replace a script element.
744
 * @param string $content The content of the script being added, optional.
745
 * @return void
746
 * @deprecated Will be removed in 3.0. Superseded by blocks functionality.
747
 * @see View::start()
748
 */
749
	public function addScript($name, $content = null) {
750
		if (empty($content)) {
751
			if (!in_array($name, array_values($this->_scripts))) {
752
				$this->_scripts[] = $name;
753
			}
754
		} else {
755
			$this->_scripts[$name] = $content;
756
		}
757
	}
758
759
/**
760
 * Generates a unique, non-random DOM ID for an object, based on the object type and the target URL.
761
 *
762
 * @param string $object Type of object, i.e. 'form' or 'link'
763
 * @param string $url The object's target URL
764
 * @return string
765
 */
766
	public function uuid($object, $url) {
767
		$c = 1;
768
		$url = Router::url($url);
769
		$hash = $object . substr(md5($object . $url), 0, 10);
770
		while (in_array($hash, $this->uuids)) {
771
			$hash = $object . substr(md5($object . $url . $c), 0, 10);
772
			$c++;
773
		}
774
		$this->uuids[] = $hash;
775
		return $hash;
776
	}
777
778
/**
779
 * Allows a template or element to set a variable that will be available in
780
 * a layout or other element. Analogous to Controller::set().
781
 *
782
 * @param string|array $one A string or an array of data.
783
 * @param string|array $two Value in case $one is a string (which then works as the key).
784
 *    Unused if $one is an associative array, otherwise serves as the values to $one's keys.
785
 * @return void
786
 */
787 View Code Duplication
	public function set($one, $two = null) {
788
		$data = null;
789
		if (is_array($one)) {
790
			if (is_array($two)) {
791
				$data = array_combine($one, $two);
792
			} else {
793
				$data = $one;
794
			}
795
		} else {
796
			$data = array($one => $two);
797
		}
798
		if (!$data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data 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...
799
			return false;
800
		}
801
		$this->viewVars = $data + $this->viewVars;
802
	}
803
804
/**
805
 * Magic accessor for helpers. Provides access to attributes that were deprecated.
806
 *
807
 * @param string $name Name of the attribute to get.
808
 * @return mixed
809
 */
810
	public function __get($name) {
811 View Code Duplication
		switch ($name) {
812
			case 'base':
813
			case 'here':
814
			case 'webroot':
815
			case 'data':
816
				return $this->request->{$name};
817
			case 'action':
818
				return $this->request->params['action'];
819
			case 'params':
820
				return $this->request;
821
			case 'output':
822
				return $this->Blocks->get('content');
823
		}
824
		if (isset($this->Helpers->{$name})) {
825
			$this->{$name} = $this->Helpers->{$name};
826
			return $this->Helpers->{$name};
827
		}
828
		return $this->{$name};
829
	}
830
831
/**
832
 * Magic accessor for deprecated attributes.
833
 *
834
 * @param string $name Name of the attribute to set.
835
 * @param mixed $value Value of the attribute to set.
836
 * @return mixed
837
 */
838
	public function __set($name, $value) {
839
		switch ($name) {
840
			case 'output':
841
				return $this->Blocks->set('content', $value);
842
			default:
843
				$this->{$name} = $value;
844
		}
845
	}
846
847
/**
848
 * Magic isset check for deprecated attributes.
849
 *
850
 * @param string $name Name of the attribute to check.
851
 * @return boolean
852
 */
853
	public function __isset($name) {
854
		if (isset($this->{$name})) {
855
			return true;
856
		}
857
		$magicGet = array('base', 'here', 'webroot', 'data', 'action', 'params', 'output');
858
		if (in_array($name, $magicGet)) {
859
			return $this->__get($name) !== null;
860
		}
861
		return false;
862
	}
863
864
/**
865
 * Interact with the HelperCollection to load all the helpers.
866
 *
867
 * @return void
868
 */
869
	public function loadHelpers() {
870
		$helpers = HelperCollection::normalizeObjectArray($this->helpers);
871
		foreach ($helpers as $properties) {
872
			list(, $class) = pluginSplit($properties['class']);
873
			$this->{$class} = $this->Helpers->load($properties['class'], $properties['settings']);
874
		}
875
	}
876
877
/**
878
 * Renders and returns output for given view filename with its
879
 * array of data. Handles parent/extended views.
880
 *
881
 * @param string $viewFile Filename of the view
882
 * @param array $data Data to include in rendered view. If empty the current View::$viewVars will be used.
883
 * @return string Rendered output
884
 * @throws CakeException when a block is left open.
885
 */
886
	protected function _render($viewFile, $data = array()) {
887
		if (empty($data)) {
888
			$data = $this->viewVars;
889
		}
890
		$this->_current = $viewFile;
891
		$initialBlocks = count($this->Blocks->unclosed());
892
893
		$eventManager = $this->getEventManager();
894
		$beforeEvent = new CakeEvent('View.beforeRenderFile', $this, array($viewFile));
895
896
		$eventManager->dispatch($beforeEvent);
897
		$content = $this->_evaluate($viewFile, $data);
898
899
		$afterEvent = new CakeEvent('View.afterRenderFile', $this, array($viewFile, $content));
900
901
		$afterEvent->modParams = 1;
0 ignored issues
show
Bug introduced by
The property modParams does not seem to exist in CakeEvent.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
902
		$eventManager->dispatch($afterEvent);
903
		$content = $afterEvent->data[1];
904
905
		if (isset($this->_parents[$viewFile])) {
906
			$this->_stack[] = $this->fetch('content');
907
			$this->assign('content', $content);
908
909
			$content = $this->_render($this->_parents[$viewFile]);
910
			$this->assign('content', array_pop($this->_stack));
911
		}
912
913
		$remainingBlocks = count($this->Blocks->unclosed());
914
915
		if ($initialBlocks !== $remainingBlocks) {
916
			throw new CakeException(__d('cake_dev', 'The "%s" block was left open. Blocks are not allowed to cross files.', $this->Blocks->active()));
917
		}
918
919
		return $content;
920
	}
921
922
/**
923
 * Sandbox method to evaluate a template / view script in.
924
 *
925
 * @param string $viewFn Filename of the view
0 ignored issues
show
Bug introduced by
There is no parameter named $viewFn. 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...
926
 * @param array $dataForView Data to include in rendered view.
927
 *    If empty the current View::$viewVars will be used.
928
 * @return string Rendered output
929
 */
930
	protected function _evaluate($viewFile, $dataForView) {
931
		$this->__viewFile = $viewFile;
0 ignored issues
show
Documentation introduced by
The property __viewFile does not exist on object<View>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
932
		extract($dataForView);
933
		ob_start();
934
935
		include $this->__viewFile;
936
937
		unset($this->__viewFile);
938
		return ob_get_clean();
939
	}
940
941
/**
942
 * Loads a helper. Delegates to the `HelperCollection::load()` to load the helper
943
 *
944
 * @param string $helperName Name of the helper to load.
945
 * @param array $settings Settings for the helper
946
 * @return Helper a constructed helper object.
947
 * @see HelperCollection::load()
948
 */
949
	public function loadHelper($helperName, $settings = array()) {
950
		return $this->Helpers->load($helperName, $settings);
951
	}
952
953
/**
954
 * Returns filename of given action's template file (.ctp) as a string.
955
 * CamelCased action names will be under_scored! This means that you can have
956
 * LongActionNames that refer to long_action_names.ctp views.
957
 *
958
 * @param string $name Controller action to find template filename for
959
 * @return string Template filename
960
 * @throws MissingViewException when a view file could not be found.
961
 */
962
	protected function _getViewFileName($name = null) {
963
		$subDir = null;
964
965
		if ($this->subDir !== null) {
966
			$subDir = $this->subDir . DS;
967
		}
968
969
		if ($name === null) {
970
			$name = $this->view;
971
		}
972
		$name = str_replace('/', DS, $name);
973
		list($plugin, $name) = $this->pluginSplit($name);
974
975
		if (strpos($name, DS) === false && $name[0] !== '.') {
976
			$name = $this->viewPath . DS . $subDir . Inflector::underscore($name);
977
		} elseif (strpos($name, DS) !== false) {
978
			if ($name[0] === DS || $name[1] === ':') {
979
				if (is_file($name)) {
980
					return $name;
981
				}
982
				$name = trim($name, DS);
983
			} elseif ($name[0] === '.') {
984
				$name = substr($name, 3);
985
			} elseif (!$plugin || $this->viewPath !== $this->name) {
986
				$name = $this->viewPath . DS . $subDir . $name;
987
			}
988
		}
989
		$paths = $this->_paths($plugin);
990
		$exts = $this->_getExtensions();
991
		foreach ($exts as $ext) {
992
			foreach ($paths as $path) {
993
				if (file_exists($path . $name . $ext)) {
994
					return $path . $name . $ext;
995
				}
996
			}
997
		}
998
		$defaultPath = $paths[0];
999
1000
		if ($this->plugin) {
1001
			$pluginPaths = App::path('plugins');
1002
			foreach ($paths as $path) {
1003
				if (strpos($path, $pluginPaths[0]) === 0) {
1004
					$defaultPath = $path;
1005
					break;
1006
				}
1007
			}
1008
		}
1009
		throw new MissingViewException(array('file' => $defaultPath . $name . $this->ext));
1010
	}
1011
1012
/**
1013
 * Splits a dot syntax plugin name into its plugin and filename.
1014
 * If $name does not have a dot, then index 0 will be null.
1015
 * It checks if the plugin is loaded, else filename will stay unchanged for filenames containing dot
1016
 *
1017
 * @param string $name The name you want to plugin split.
1018
 * @param boolean $fallback If true uses the plugin set in the current CakeRequest when parsed plugin is not loaded
1019
 * @return array Array with 2 indexes. 0 => plugin name, 1 => filename
1020
 */
1021
	public function pluginSplit($name, $fallback = true) {
1022
		$plugin = null;
1023
		list($first, $second) = pluginSplit($name);
1024
		if (CakePlugin::loaded($first) === true) {
1025
			$name = $second;
1026
			$plugin = $first;
1027
		}
1028
		if (isset($this->plugin) && !$plugin && $fallback) {
1029
			$plugin = $this->plugin;
1030
		}
1031
		return array($plugin, $name);
1032
	}
1033
1034
/**
1035
 * Returns layout filename for this template as a string.
1036
 *
1037
 * @param string $name The name of the layout to find.
1038
 * @return string Filename for layout file (.ctp).
1039
 * @throws MissingLayoutException when a layout cannot be located
1040
 */
1041
	protected function _getLayoutFileName($name = null) {
1042
		if ($name === null) {
1043
			$name = $this->layout;
1044
		}
1045
		$subDir = null;
1046
1047
		if ($this->layoutPath !== null) {
1048
			$subDir = $this->layoutPath . DS;
1049
		}
1050
		list($plugin, $name) = $this->pluginSplit($name);
1051
		$paths = $this->_paths($plugin);
1052
		$file = 'Layouts' . DS . $subDir . $name;
1053
1054
		$exts = $this->_getExtensions();
1055
		foreach ($exts as $ext) {
1056
			foreach ($paths as $path) {
1057
				if (file_exists($path . $file . $ext)) {
1058
					return $path . $file . $ext;
1059
				}
1060
			}
1061
		}
1062
		throw new MissingLayoutException(array('file' => $paths[0] . $file . $this->ext));
1063
	}
1064
1065
/**
1066
 * Get the extensions that view files can use.
1067
 *
1068
 * @return array Array of extensions view files use.
1069
 */
1070
	protected function _getExtensions() {
1071
		$exts = array($this->ext);
1072
		if ($this->ext !== '.ctp') {
1073
			$exts[] = '.ctp';
1074
		}
1075
		return $exts;
1076
	}
1077
1078
/**
1079
 * Finds an element filename, returns false on failure.
1080
 *
1081
 * @param string $name The name of the element to find.
1082
 * @return mixed Either a string to the element filename or false when one can't be found.
1083
 */
1084
	protected function _getElementFileName($name) {
1085
		list($plugin, $name) = $this->pluginSplit($name);
1086
1087
		$paths = $this->_paths($plugin);
1088
		$exts = $this->_getExtensions();
1089
		foreach ($exts as $ext) {
1090
			foreach ($paths as $path) {
1091
				if (file_exists($path . 'Elements' . DS . $name . $ext)) {
1092
					return $path . 'Elements' . DS . $name . $ext;
1093
				}
1094
			}
1095
		}
1096
		return false;
1097
	}
1098
1099
/**
1100
 * Return all possible paths to find view files in order
1101
 *
1102
 * @param string $plugin Optional plugin name to scan for view files.
1103
 * @param boolean $cached Set to false to force a refresh of view paths. Default true.
1104
 * @return array paths
1105
 */
1106
	protected function _paths($plugin = null, $cached = true) {
1107
		if ($plugin === null && $cached === true && !empty($this->_paths)) {
1108
			return $this->_paths;
1109
		}
1110
		$paths = array();
1111
		$viewPaths = App::path('View');
1112
		$corePaths = array_merge(App::core('View'), App::core('Console/Templates/skel/View'));
1113
1114
		if (!empty($plugin)) {
1115
			$count = count($viewPaths);
1116
			for ($i = 0; $i < $count; $i++) {
1117
				if (!in_array($viewPaths[$i], $corePaths)) {
1118
					$paths[] = $viewPaths[$i] . 'Plugin' . DS . $plugin . DS;
1119
				}
1120
			}
1121
			$paths = array_merge($paths, App::path('View', $plugin));
1122
		}
1123
1124
		$paths = array_unique(array_merge($paths, $viewPaths));
1125
		if (!empty($this->theme)) {
1126
			$theme = Inflector::camelize($this->theme);
1127
			$themePaths = array();
1128
			foreach ($paths as $path) {
1129
				if (strpos($path, DS . 'Plugin' . DS) === false) {
1130
					if ($plugin) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $plugin of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1131
						$themePaths[] = $path . 'Themed' . DS . $theme . DS . 'Plugin' . DS . $plugin . DS;
1132
					}
1133
					$themePaths[] = $path . 'Themed' . DS . $theme . DS;
1134
				}
1135
			}
1136
			$paths = array_merge($themePaths, $paths);
1137
		}
1138
		$paths = array_merge($paths, $corePaths);
1139
		if ($plugin !== null) {
1140
			return $paths;
1141
		}
1142
		return $this->_paths = $paths;
1143
	}
1144
1145
/**
1146
 * Checks if an element is cached and returns the cached data if present
1147
 *
1148
 * @param string $name Element name
1149
 * @param string $data Data
1150
 * @param array $options Element options
1151
 * @return string|null
1152
 */
1153
	protected function _elementCache($name, $data, $options) {
1154
		$plugin = null;
1155
		list($plugin, $name) = $this->pluginSplit($name);
1156
1157
		$underscored = null;
1158
		if ($plugin) {
1159
			$underscored = Inflector::underscore($plugin);
1160
		}
1161
		$keys = array_merge(array($underscored, $name), array_keys($options), array_keys($data));
1162
		$this->elementCacheSettings = array(
1163
			'config' => $this->elementCache,
1164
			'key' => implode('_', $keys)
1165
		);
1166
		if (is_array($options['cache'])) {
1167
			$defaults = array(
1168
				'config' => $this->elementCache,
1169
				'key' => $this->elementCacheSettings['key']
1170
			);
1171
			$this->elementCacheSettings = array_merge($defaults, $options['cache']);
1172
		}
1173
		$this->elementCacheSettings['key'] = 'element_' . $this->elementCacheSettings['key'];
1174
		return Cache::read($this->elementCacheSettings['key'], $this->elementCacheSettings['config']);
1175
	}
1176
1177
/**
1178
 * Renders an element and fires the before and afterRender callbacks for it
1179
 * and writes to the cache if a cache is used
1180
 *
1181
 * @param string $file Element file path
1182
 * @param array $data Data to render
1183
 * @param array $options Element options
1184
 * @return string
1185
 */
1186
	protected function _renderElement($file, $data, $options) {
1187
		if ($options['callbacks']) {
1188
			$this->getEventManager()->dispatch(new CakeEvent('View.beforeRender', $this, array($file)));
1189
		}
1190
1191
		$current = $this->_current;
1192
		$restore = $this->_currentType;
1193
1194
		$this->_currentType = self::TYPE_ELEMENT;
1195
		$element = $this->_render($file, array_merge($this->viewVars, $data));
1196
1197
		$this->_currentType = $restore;
1198
		$this->_current = $current;
1199
1200
		if ($options['callbacks']) {
1201
			$this->getEventManager()->dispatch(new CakeEvent('View.afterRender', $this, array($file, $element)));
1202
		}
1203
		if (isset($options['cache'])) {
1204
			Cache::write($this->elementCacheSettings['key'], $element, $this->elementCacheSettings['config']);
1205
		}
1206
		return $element;
1207
	}
1208
}
1209