統計
| ブランチ: | リビジョン:

pictcode_admin / lib / Cake / View / View.php @ 5ad38a95

履歴 | 表示 | アノテート | ダウンロード (34.649 KB)

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
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
98
 */
99
        public $helpers = array();
100

    
101
/**
102
 * Path to View.
103
 *
104
 * @var string
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
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 bool
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 bool
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
 * Holds an array of plugin paths.
256
 *
257
 * @var array
258
 */
259
        protected $_pathsForPlugin = array();
260

    
261
/**
262
 * The names of views and their parents used with View::extend();
263
 *
264
 * @var array
265
 */
266
        protected $_parents = array();
267

    
268
/**
269
 * The currently rendering view file. Used for resolving parent files.
270
 *
271
 * @var string
272
 */
273
        protected $_current = null;
274

    
275
/**
276
 * Currently rendering an element. Used for finding parent fragments
277
 * for elements.
278
 *
279
 * @var string
280
 */
281
        protected $_currentType = '';
282

    
283
/**
284
 * Content stack, used for nested templates that all use View::extend();
285
 *
286
 * @var array
287
 */
288
        protected $_stack = array();
289

    
290
/**
291
 * Instance of the CakeEventManager this View object is using
292
 * to dispatch inner events. Usually the manager is shared with
293
 * the controller, so it it possible to register view events in
294
 * the controller layer.
295
 *
296
 * @var CakeEventManager
297
 */
298
        protected $_eventManager = null;
299

    
300
/**
301
 * Whether the event manager was already configured for this object
302
 *
303
 * @var bool
304
 */
305
        protected $_eventManagerConfigured = false;
306

    
307
/**
308
 * Constant for view file type 'view'
309
 *
310
 * @var string
311
 */
312
        const TYPE_VIEW = 'view';
313

    
314
/**
315
 * Constant for view file type 'element'
316
 *
317
 * @var string
318
 */
319
        const TYPE_ELEMENT = 'element';
320

    
321
/**
322
 * Constant for view file type 'layout'
323
 *
324
 * @var string
325
 */
326
        const TYPE_LAYOUT = 'layout';
327

    
328
/**
329
 * Constructor
330
 *
331
 * @param Controller $controller A controller object to pull View::_passedVars from.
332
 */
333
        public function __construct(Controller $controller = null) {
334
                if (is_object($controller)) {
335
                        $count = count($this->_passedVars);
336
                        for ($j = 0; $j < $count; $j++) {
337
                                $var = $this->_passedVars[$j];
338
                                $this->{$var} = $controller->{$var};
339
                        }
340
                        $this->_eventManager = $controller->getEventManager();
341
                }
342
                if (empty($this->request) && !($this->request = Router::getRequest(true))) {
343
                        $this->request = new CakeRequest(null, false);
344
                        $this->request->base = '';
345
                        $this->request->here = $this->request->webroot = '/';
346
                }
347
                if (is_object($controller) && isset($controller->response)) {
348
                        $this->response = $controller->response;
349
                } else {
350
                        $this->response = new CakeResponse();
351
                }
352
                $this->Helpers = new HelperCollection($this);
353
                $this->Blocks = new ViewBlock();
354
                $this->loadHelpers();
355
                parent::__construct();
356
        }
357

    
358
/**
359
 * Returns the CakeEventManager manager instance that is handling any callbacks.
360
 * You can use this instance to register any new listeners or callbacks to the
361
 * controller events, or create your own events and trigger them at will.
362
 *
363
 * @return CakeEventManager
364
 */
365
        public function getEventManager() {
366
                if (empty($this->_eventManager)) {
367
                        $this->_eventManager = new CakeEventManager();
368
                }
369
                if (!$this->_eventManagerConfigured) {
370
                        $this->_eventManager->attach($this->Helpers);
371
                        $this->_eventManagerConfigured = true;
372
                }
373
                return $this->_eventManager;
374
        }
375

    
376
/**
377
 * Renders a piece of PHP with provided parameters and returns HTML, XML, or any other string.
378
 *
379
 * This realizes the concept of Elements, (or "partial layouts") and the $params array is used to send
380
 * data to be used in the element. Elements can be cached improving performance by using the `cache` option.
381
 *
382
 * @param string $name Name of template file in the/app/View/Elements/ folder,
383
 *   or `MyPlugin.template` to use the template element from MyPlugin. If the element
384
 *   is not found in the plugin, the normal view path cascade will be searched.
385
 * @param array $data Array of data to be made available to the rendered view (i.e. the Element)
386
 * @param array $options Array of options. Possible keys are:
387
 * - `cache` - Can either be `true`, to enable caching using the config in View::$elementCache. Or an array
388
 *   If an array, the following keys can be used:
389
 *   - `config` - Used to store the cached element in a custom cache configuration.
390
 *   - `key` - Used to define the key used in the Cache::write(). It will be prefixed with `element_`
391
 * - `plugin` - (deprecated!) Load an element from a specific plugin. This option is deprecated, and
392
 *              will be removed in CakePHP 3.0. Use `Plugin.element_name` instead.
393
 * - `callbacks` - Set to true to fire beforeRender and afterRender helper callbacks for this element.
394
 *   Defaults to false.
395
 * - `ignoreMissing` - Used to allow missing elements. Set to true to not trigger notices.
396
 * @return string Rendered Element
397
 */
398
        public function element($name, $data = array(), $options = array()) {
399
                $file = $plugin = null;
400

    
401
                if (isset($options['plugin'])) {
402
                        $name = Inflector::camelize($options['plugin']) . '.' . $name;
403
                }
404

    
405
                if (!isset($options['callbacks'])) {
406
                        $options['callbacks'] = false;
407
                }
408

    
409
                if (isset($options['cache'])) {
410
                        $contents = $this->_elementCache($name, $data, $options);
411
                        if ($contents !== false) {
412
                                return $contents;
413
                        }
414
                }
415

    
416
                $file = $this->_getElementFilename($name);
417
                if ($file) {
418
                        return $this->_renderElement($file, $data, $options);
419
                }
420

    
421
                if (empty($options['ignoreMissing'])) {
422
                        list ($plugin, $name) = pluginSplit($name, true);
423
                        $name = str_replace('/', DS, $name);
424
                        $file = $plugin . 'Elements' . DS . $name . $this->ext;
425
                        trigger_error(__d('cake_dev', 'Element Not Found: %s', $file), E_USER_NOTICE);
426
                }
427
        }
428

    
429
/**
430
 * Checks if an element exists
431
 *
432
 * @param string $name Name of template file in the /app/View/Elements/ folder,
433
 *   or `MyPlugin.template` to check the template element from MyPlugin. If the element
434
 *   is not found in the plugin, the normal view path cascade will be searched.
435
 * @return bool Success
436
 */
437
        public function elementExists($name) {
438
                return (bool)$this->_getElementFilename($name);
439
        }
440

    
441
/**
442
 * Renders view for given view file and layout.
443
 *
444
 * Render triggers helper callbacks, which are fired before and after the view are rendered,
445
 * as well as before and after the layout. The helper callbacks are called:
446
 *
447
 * - `beforeRender`
448
 * - `afterRender`
449
 * - `beforeLayout`
450
 * - `afterLayout`
451
 *
452
 * If View::$autoRender is false and no `$layout` is provided, the view will be returned bare.
453
 *
454
 * View and layout names can point to plugin views/layouts. Using the `Plugin.view` syntax
455
 * a plugin view/layout can be used instead of the app ones. If the chosen plugin is not found
456
 * the view will be located along the regular view path cascade.
457
 *
458
 * @param string $view Name of view file to use
459
 * @param string $layout Layout to use.
460
 * @return string|null Rendered content or null if content already rendered and returned earlier.
461
 * @triggers View.beforeRender $this, array($viewFileName)
462
 * @triggers View.afterRender $this, array($viewFileName)
463
 * @throws CakeException If there is an error in the view.
464
 */
465
        public function render($view = null, $layout = null) {
466
                if ($this->hasRendered) {
467
                        return null;
468
                }
469

    
470
                if ($view !== false && $viewFileName = $this->_getViewFileName($view)) {
471
                        $this->_currentType = static::TYPE_VIEW;
472
                        $this->getEventManager()->dispatch(new CakeEvent('View.beforeRender', $this, array($viewFileName)));
473
                        $this->Blocks->set('content', $this->_render($viewFileName));
474
                        $this->getEventManager()->dispatch(new CakeEvent('View.afterRender', $this, array($viewFileName)));
475
                }
476

    
477
                if ($layout === null) {
478
                        $layout = $this->layout;
479
                }
480
                if ($layout && $this->autoLayout) {
481
                        $this->Blocks->set('content', $this->renderLayout('', $layout));
482
                }
483
                $this->hasRendered = true;
484
                return $this->Blocks->get('content');
485
        }
486

    
487
/**
488
 * Renders a layout. Returns output from _render(). Returns false on error.
489
 * Several variables are created for use in layout.
490
 *
491
 * - `title_for_layout` - A backwards compatible place holder, you should set this value if you want more control.
492
 * - `content_for_layout` - contains rendered view file
493
 * - `scripts_for_layout` - Contains content added with addScript() as well as any content in
494
 *   the 'meta', 'css', and 'script' blocks. They are appended in that order.
495
 *
496
 * Deprecated features:
497
 *
498
 * - `$scripts_for_layout` is deprecated and will be removed in CakePHP 3.0.
499
 *   Use the block features instead. `meta`, `css` and `script` will be populated
500
 *   by the matching methods on HtmlHelper.
501
 * - `$title_for_layout` is deprecated and will be removed in CakePHP 3.0.
502
 *   Use the `title` block instead.
503
 * - `$content_for_layout` is deprecated and will be removed in CakePHP 3.0.
504
 *   Use the `content` block instead.
505
 *
506
 * @param string $content Content to render in a view, wrapped by the surrounding layout.
507
 * @param string $layout Layout name
508
 * @return mixed Rendered output, or false on error
509
 * @triggers View.beforeLayout $this, array($layoutFileName)
510
 * @triggers View.afterLayout $this, array($layoutFileName)
511
 * @throws CakeException if there is an error in the view.
512
 */
513
        public function renderLayout($content, $layout = null) {
514
                $layoutFileName = $this->_getLayoutFileName($layout);
515
                if (empty($layoutFileName)) {
516
                        return $this->Blocks->get('content');
517
                }
518

    
519
                if (empty($content)) {
520
                        $content = $this->Blocks->get('content');
521
                } else {
522
                        $this->Blocks->set('content', $content);
523
                }
524
                $this->getEventManager()->dispatch(new CakeEvent('View.beforeLayout', $this, array($layoutFileName)));
525

    
526
                $scripts = implode("\n\t", $this->_scripts);
527
                $scripts .= $this->Blocks->get('meta') . $this->Blocks->get('css') . $this->Blocks->get('script');
528

    
529
                $this->viewVars = array_merge($this->viewVars, array(
530
                        'content_for_layout' => $content,
531
                        'scripts_for_layout' => $scripts,
532
                ));
533

    
534
                $title = $this->Blocks->get('title');
535
                if ($title === '') {
536
                        if (isset($this->viewVars['title_for_layout'])) {
537
                                $title = $this->viewVars['title_for_layout'];
538
                        } else {
539
                                $title = Inflector::humanize($this->viewPath);
540
                        }
541
                }
542
                $this->viewVars['title_for_layout'] = $title;
543
                $this->Blocks->set('title', $title);
544

    
545
                $this->_currentType = static::TYPE_LAYOUT;
546
                $this->Blocks->set('content', $this->_render($layoutFileName));
547

    
548
                $this->getEventManager()->dispatch(new CakeEvent('View.afterLayout', $this, array($layoutFileName)));
549
                return $this->Blocks->get('content');
550
        }
551

    
552
/**
553
 * Render cached view. Works in concert with CacheHelper and Dispatcher to
554
 * render cached view files.
555
 *
556
 * @param string $filename the cache file to include
557
 * @param string $timeStart the page render start time
558
 * @return bool Success of rendering the cached file.
559
 */
560
        public function renderCache($filename, $timeStart) {
561
                $response = $this->response;
562
                ob_start();
563
                include $filename;
564

    
565
                $type = $response->mapType($response->type());
566
                if (Configure::read('debug') > 0 && $type === 'html') {
567
                        echo "<!-- Cached Render Time: " . round(microtime(true) - $timeStart, 4) . "s -->";
568
                }
569
                $out = ob_get_clean();
570

    
571
                if (preg_match('/^<!--cachetime:(\\d+)-->/', $out, $match)) {
572
                        if (time() >= $match['1']) {
573
                                //@codingStandardsIgnoreStart
574
                                @unlink($filename);
575
                                //@codingStandardsIgnoreEnd
576
                                unset($out);
577
                                return false;
578
                        }
579
                        return substr($out, strlen($match[0]));
580
                }
581
        }
582

    
583
/**
584
 * Returns a list of variables available in the current View context
585
 *
586
 * @return array Array of the set view variable names.
587
 */
588
        public function getVars() {
589
                return array_keys($this->viewVars);
590
        }
591

    
592
/**
593
 * Returns the contents of the given View variable(s)
594
 *
595
 * @param string $var The view var you want the contents of.
596
 * @return mixed The content of the named var if its set, otherwise null.
597
 * @deprecated 3.0.0 Will be removed in 3.0. Use View::get() instead.
598
 */
599
        public function getVar($var) {
600
                return $this->get($var);
601
        }
602

    
603
/**
604
 * Returns the contents of the given View variable.
605
 *
606
 * @param string $var The view var you want the contents of.
607
 * @param mixed $default The default/fallback content of $var.
608
 * @return mixed The content of the named var if its set, otherwise $default.
609
 */
610
        public function get($var, $default = null) {
611
                if (!isset($this->viewVars[$var])) {
612
                        return $default;
613
                }
614
                return $this->viewVars[$var];
615
        }
616

    
617
/**
618
 * Get the names of all the existing blocks.
619
 *
620
 * @return array An array containing the blocks.
621
 * @see ViewBlock::keys()
622
 */
623
        public function blocks() {
624
                return $this->Blocks->keys();
625
        }
626

    
627
/**
628
 * Start capturing output for a 'block'
629
 *
630
 * @param string $name The name of the block to capture for.
631
 * @return void
632
 * @see ViewBlock::start()
633
 */
634
        public function start($name) {
635
                $this->Blocks->start($name);
636
        }
637

    
638
/**
639
 * Start capturing output for a 'block' if it has no content
640
 *
641
 * @param string $name The name of the block to capture for.
642
 * @return void
643
 * @see ViewBlock::startIfEmpty()
644
 */
645
        public function startIfEmpty($name) {
646
                $this->Blocks->startIfEmpty($name);
647
        }
648

    
649
/**
650
 * Append to an existing or new block. Appending to a new
651
 * block will create the block.
652
 *
653
 * @param string $name Name of the block
654
 * @param mixed $value The content for the block.
655
 * @return void
656
 * @see ViewBlock::concat()
657
 */
658
        public function append($name, $value = null) {
659
                $this->Blocks->concat($name, $value);
660
        }
661

    
662
/**
663
 * Prepend to an existing or new block. Prepending to a new
664
 * block will create the block.
665
 *
666
 * @param string $name Name of the block
667
 * @param mixed $value The content for the block.
668
 * @return void
669
 * @see ViewBlock::concat()
670
 */
671
        public function prepend($name, $value = null) {
672
                $this->Blocks->concat($name, $value, ViewBlock::PREPEND);
673
        }
674

    
675
/**
676
 * Set the content for a block. This will overwrite any
677
 * existing content.
678
 *
679
 * @param string $name Name of the block
680
 * @param mixed $value The content for the block.
681
 * @return void
682
 * @see ViewBlock::set()
683
 */
684
        public function assign($name, $value) {
685
                $this->Blocks->set($name, $value);
686
        }
687

    
688
/**
689
 * Fetch the content for a block. If a block is
690
 * empty or undefined '' will be returned.
691
 *
692
 * @param string $name Name of the block
693
 * @param string $default Default text
694
 * @return string default The block content or $default if the block does not exist.
695
 * @see ViewBlock::get()
696
 */
697
        public function fetch($name, $default = '') {
698
                return $this->Blocks->get($name, $default);
699
        }
700

    
701
/**
702
 * Check if a block exists
703
 *
704
 * @param string $name Name of the block
705
 * @return bool
706
 */
707
        public function exists($name) {
708
                return $this->Blocks->exists($name);
709
        }
710

    
711
/**
712
 * End a capturing block. The compliment to View::start()
713
 *
714
 * @return void
715
 * @see ViewBlock::end()
716
 */
717
        public function end() {
718
                $this->Blocks->end();
719
        }
720

    
721
/**
722
 * Provides view or element extension/inheritance. Views can extends a
723
 * parent view and populate blocks in the parent template.
724
 *
725
 * @param string $name The view or element to 'extend' the current one with.
726
 * @return void
727
 * @throws LogicException when you extend a view with itself or make extend loops.
728
 * @throws LogicException when you extend an element which doesn't exist
729
 */
730
        public function extend($name) {
731
                if ($name[0] === '/' || $this->_currentType === static::TYPE_VIEW) {
732
                        $parent = $this->_getViewFileName($name);
733
                } else {
734
                        switch ($this->_currentType) {
735
                                case static::TYPE_ELEMENT:
736
                                        $parent = $this->_getElementFileName($name);
737
                                        if (!$parent) {
738
                                                list($plugin, $name) = $this->pluginSplit($name);
739
                                                $paths = $this->_paths($plugin);
740
                                                $defaultPath = $paths[0] . 'Elements' . DS;
741
                                                throw new LogicException(__d(
742
                                                        'cake_dev',
743
                                                        'You cannot extend an element which does not exist (%s).',
744
                                                        $defaultPath . $name . $this->ext
745
                                                ));
746
                                        }
747
                                        break;
748
                                case static::TYPE_LAYOUT:
749
                                        $parent = $this->_getLayoutFileName($name);
750
                                        break;
751
                                default:
752
                                        $parent = $this->_getViewFileName($name);
753
                        }
754
                }
755

    
756
                if ($parent == $this->_current) {
757
                        throw new LogicException(__d('cake_dev', 'You cannot have views extend themselves.'));
758
                }
759
                if (isset($this->_parents[$parent]) && $this->_parents[$parent] == $this->_current) {
760
                        throw new LogicException(__d('cake_dev', 'You cannot have views extend in a loop.'));
761
                }
762
                $this->_parents[$this->_current] = $parent;
763
        }
764

    
765
/**
766
 * Adds a script block or other element to be inserted in $scripts_for_layout in
767
 * the `<head />` of a document layout
768
 *
769
 * @param string $name Either the key name for the script, or the script content. Name can be used to
770
 *   update/replace a script element.
771
 * @param string $content The content of the script being added, optional.
772
 * @return void
773
 * @deprecated 3.0.0 Will be removed in 3.0. Superseded by blocks functionality.
774
 * @see View::start()
775
 */
776
        public function addScript($name, $content = null) {
777
                if (empty($content)) {
778
                        if (!in_array($name, array_values($this->_scripts))) {
779
                                $this->_scripts[] = $name;
780
                        }
781
                } else {
782
                        $this->_scripts[$name] = $content;
783
                }
784
        }
785

    
786
/**
787
 * Generates a unique, non-random DOM ID for an object, based on the object type and the target URL.
788
 *
789
 * @param string $object Type of object, i.e. 'form' or 'link'
790
 * @param string $url The object's target URL
791
 * @return string
792
 */
793
        public function uuid($object, $url) {
794
                $c = 1;
795
                $url = Router::url($url);
796
                $hash = $object . substr(md5($object . $url), 0, 10);
797
                while (in_array($hash, $this->uuids)) {
798
                        $hash = $object . substr(md5($object . $url . $c), 0, 10);
799
                        $c++;
800
                }
801
                $this->uuids[] = $hash;
802
                return $hash;
803
        }
804

    
805
/**
806
 * Allows a template or element to set a variable that will be available in
807
 * a layout or other element. Analogous to Controller::set().
808
 *
809
 * @param string|array $one A string or an array of data.
810
 * @param string|array $two Value in case $one is a string (which then works as the key).
811
 *    Unused if $one is an associative array, otherwise serves as the values to $one's keys.
812
 * @return void
813
 */
814
        public function set($one, $two = null) {
815
                $data = null;
816
                if (is_array($one)) {
817
                        if (is_array($two)) {
818
                                $data = array_combine($one, $two);
819
                        } else {
820
                                $data = $one;
821
                        }
822
                } else {
823
                        $data = array($one => $two);
824
                }
825
                if (!$data) {
826
                        return false;
827
                }
828
                $this->viewVars = $data + $this->viewVars;
829
        }
830
/**
831
 * Retrieve the current view type
832
 *
833
 * @return string
834
 */
835
        public function getCurrentType() {
836
                return $this->_currentType;
837
        }
838
/**
839
 * Magic accessor for helpers. Provides access to attributes that were deprecated.
840
 *
841
 * @param string $name Name of the attribute to get.
842
 * @return mixed
843
 */
844
        public function __get($name) {
845
                switch ($name) {
846
                        case 'base':
847
                        case 'here':
848
                        case 'webroot':
849
                        case 'data':
850
                                return $this->request->{$name};
851
                        case 'action':
852
                                return $this->request->params['action'];
853
                        case 'params':
854
                                return $this->request;
855
                        case 'output':
856
                                return $this->Blocks->get('content');
857
                }
858
                if (isset($this->Helpers->{$name})) {
859
                        $this->{$name} = $this->Helpers->{$name};
860
                        return $this->Helpers->{$name};
861
                }
862
                return $this->{$name};
863
        }
864

    
865
/**
866
 * Magic accessor for deprecated attributes.
867
 *
868
 * @param string $name Name of the attribute to set.
869
 * @param mixed $value Value of the attribute to set.
870
 * @return mixed
871
 */
872
        public function __set($name, $value) {
873
                switch ($name) {
874
                        case 'output':
875
                                return $this->Blocks->set('content', $value);
876
                        default:
877
                                $this->{$name} = $value;
878
                }
879
        }
880

    
881
/**
882
 * Magic isset check for deprecated attributes.
883
 *
884
 * @param string $name Name of the attribute to check.
885
 * @return bool
886
 */
887
        public function __isset($name) {
888
                if (isset($this->{$name})) {
889
                        return true;
890
                }
891
                $magicGet = array('base', 'here', 'webroot', 'data', 'action', 'params', 'output');
892
                if (in_array($name, $magicGet)) {
893
                        return $this->__get($name) !== null;
894
                }
895
                return false;
896
        }
897

    
898
/**
899
 * Interact with the HelperCollection to load all the helpers.
900
 *
901
 * @return void
902
 */
903
        public function loadHelpers() {
904
                $helpers = HelperCollection::normalizeObjectArray($this->helpers);
905
                foreach ($helpers as $properties) {
906
                        list(, $class) = pluginSplit($properties['class']);
907
                        $this->{$class} = $this->Helpers->load($properties['class'], $properties['settings']);
908
                }
909
        }
910

    
911
/**
912
 * Renders and returns output for given view filename with its
913
 * array of data. Handles parent/extended views.
914
 *
915
 * @param string $viewFile Filename of the view
916
 * @param array $data Data to include in rendered view. If empty the current View::$viewVars will be used.
917
 * @return string Rendered output
918
 * @triggers View.beforeRenderFile $this, array($viewFile)
919
 * @triggers View.afterRenderFile $this, array($viewFile, $content)
920
 * @throws CakeException when a block is left open.
921
 */
922
        protected function _render($viewFile, $data = array()) {
923
                if (empty($data)) {
924
                        $data = $this->viewVars;
925
                }
926
                $this->_current = $viewFile;
927
                $initialBlocks = count($this->Blocks->unclosed());
928

    
929
                $eventManager = $this->getEventManager();
930
                $beforeEvent = new CakeEvent('View.beforeRenderFile', $this, array($viewFile));
931

    
932
                $eventManager->dispatch($beforeEvent);
933
                $content = $this->_evaluate($viewFile, $data);
934

    
935
                $afterEvent = new CakeEvent('View.afterRenderFile', $this, array($viewFile, $content));
936

    
937
                $afterEvent->modParams = 1;
938
                $eventManager->dispatch($afterEvent);
939
                $content = $afterEvent->data[1];
940

    
941
                if (isset($this->_parents[$viewFile])) {
942
                        $this->_stack[] = $this->fetch('content');
943
                        $this->assign('content', $content);
944

    
945
                        $content = $this->_render($this->_parents[$viewFile]);
946
                        $this->assign('content', array_pop($this->_stack));
947
                }
948

    
949
                $remainingBlocks = count($this->Blocks->unclosed());
950

    
951
                if ($initialBlocks !== $remainingBlocks) {
952
                        throw new CakeException(__d('cake_dev', 'The "%s" block was left open. Blocks are not allowed to cross files.', $this->Blocks->active()));
953
                }
954

    
955
                return $content;
956
        }
957

    
958
/**
959
 * Sandbox method to evaluate a template / view script in.
960
 *
961
 * @param string $viewFile Filename of the view
962
 * @param array $dataForView Data to include in rendered view.
963
 *    If empty the current View::$viewVars will be used.
964
 * @return string Rendered output
965
 */
966
        protected function _evaluate($viewFile, $dataForView) {
967
                $this->__viewFile = $viewFile;
968
                extract($dataForView);
969
                ob_start();
970

    
971
                include $this->__viewFile;
972

    
973
                unset($this->__viewFile);
974
                return ob_get_clean();
975
        }
976

    
977
/**
978
 * Loads a helper. Delegates to the `HelperCollection::load()` to load the helper
979
 *
980
 * @param string $helperName Name of the helper to load.
981
 * @param array $settings Settings for the helper
982
 * @return Helper a constructed helper object.
983
 * @see HelperCollection::load()
984
 */
985
        public function loadHelper($helperName, $settings = array()) {
986
                return $this->Helpers->load($helperName, $settings);
987
        }
988

    
989
/**
990
 * Returns filename of given action's template file (.ctp) as a string.
991
 * CamelCased action names will be under_scored! This means that you can have
992
 * LongActionNames that refer to long_action_names.ctp views.
993
 *
994
 * @param string $name Controller action to find template filename for
995
 * @return string Template filename
996
 * @throws MissingViewException when a view file could not be found.
997
 */
998
        protected function _getViewFileName($name = null) {
999
                $subDir = null;
1000

    
1001
                if ($this->subDir !== null) {
1002
                        $subDir = $this->subDir . DS;
1003
                }
1004

    
1005
                if ($name === null) {
1006
                        $name = $this->view;
1007
                }
1008
                $name = str_replace('/', DS, $name);
1009
                list($plugin, $name) = $this->pluginSplit($name);
1010

    
1011
                if (strpos($name, DS) === false && $name[0] !== '.') {
1012
                        $name = $this->viewPath . DS . $subDir . Inflector::underscore($name);
1013
                } elseif (strpos($name, DS) !== false) {
1014
                        if ($name[0] === DS || $name[1] === ':') {
1015
                                $name = trim($name, DS);
1016
                        } elseif ($name[0] === '.') {
1017
                                $name = substr($name, 3);
1018
                        } elseif (!$plugin || $this->viewPath !== $this->name) {
1019
                                $name = $this->viewPath . DS . $subDir . $name;
1020
                        }
1021
                }
1022
                $paths = $this->_paths($plugin);
1023
                $exts = $this->_getExtensions();
1024
                foreach ($exts as $ext) {
1025
                        foreach ($paths as $path) {
1026
                                if (file_exists($path . $name . $ext)) {
1027
                                        return $path . $name . $ext;
1028
                                }
1029
                        }
1030
                }
1031
                throw new MissingViewException(array('file' => $name . $this->ext));
1032
        }
1033

    
1034
/**
1035
 * Splits a dot syntax plugin name into its plugin and filename.
1036
 * If $name does not have a dot, then index 0 will be null.
1037
 * It checks if the plugin is loaded, else filename will stay unchanged for filenames containing dot
1038
 *
1039
 * @param string $name The name you want to plugin split.
1040
 * @param bool $fallback If true uses the plugin set in the current CakeRequest when parsed plugin is not loaded
1041
 * @return array Array with 2 indexes. 0 => plugin name, 1 => filename
1042
 */
1043
        public function pluginSplit($name, $fallback = true) {
1044
                $plugin = null;
1045
                list($first, $second) = pluginSplit($name);
1046
                if (CakePlugin::loaded($first) === true) {
1047
                        $name = $second;
1048
                        $plugin = $first;
1049
                }
1050
                if (isset($this->plugin) && !$plugin && $fallback) {
1051
                        $plugin = $this->plugin;
1052
                }
1053
                return array($plugin, $name);
1054
        }
1055

    
1056
/**
1057
 * Returns layout filename for this template as a string.
1058
 *
1059
 * @param string $name The name of the layout to find.
1060
 * @return string Filename for layout file (.ctp).
1061
 * @throws MissingLayoutException when a layout cannot be located
1062
 */
1063
        protected function _getLayoutFileName($name = null) {
1064
                if ($name === null) {
1065
                        $name = $this->layout;
1066
                }
1067
                $subDir = null;
1068

    
1069
                if ($this->layoutPath !== null) {
1070
                        $subDir = $this->layoutPath . DS;
1071
                }
1072
                list($plugin, $name) = $this->pluginSplit($name);
1073
                $paths = $this->_paths($plugin);
1074
                $file = 'Layouts' . DS . $subDir . $name;
1075

    
1076
                $exts = $this->_getExtensions();
1077
                foreach ($exts as $ext) {
1078
                        foreach ($paths as $path) {
1079
                                if (file_exists($path . $file . $ext)) {
1080
                                        return $path . $file . $ext;
1081
                                }
1082
                        }
1083
                }
1084
                throw new MissingLayoutException(array('file' => $file . $this->ext));
1085
        }
1086

    
1087
/**
1088
 * Get the extensions that view files can use.
1089
 *
1090
 * @return array Array of extensions view files use.
1091
 */
1092
        protected function _getExtensions() {
1093
                $exts = array($this->ext);
1094
                if ($this->ext !== '.ctp') {
1095
                        $exts[] = '.ctp';
1096
                }
1097
                return $exts;
1098
        }
1099

    
1100
/**
1101
 * Finds an element filename, returns false on failure.
1102
 *
1103
 * @param string $name The name of the element to find.
1104
 * @return mixed Either a string to the element filename or false when one can't be found.
1105
 */
1106
        protected function _getElementFileName($name) {
1107
                list($plugin, $name) = $this->pluginSplit($name);
1108

    
1109
                $paths = $this->_paths($plugin);
1110
                $exts = $this->_getExtensions();
1111
                foreach ($exts as $ext) {
1112
                        foreach ($paths as $path) {
1113
                                if (file_exists($path . 'Elements' . DS . $name . $ext)) {
1114
                                        return $path . 'Elements' . DS . $name . $ext;
1115
                                }
1116
                        }
1117
                }
1118
                return false;
1119
        }
1120

    
1121
/**
1122
 * Return all possible paths to find view files in order
1123
 *
1124
 * @param string $plugin Optional plugin name to scan for view files.
1125
 * @param bool $cached Set to false to force a refresh of view paths. Default true.
1126
 * @return array paths
1127
 */
1128
        protected function _paths($plugin = null, $cached = true) {
1129
                if ($cached === true) {
1130
                        if ($plugin === null && !empty($this->_paths)) {
1131
                                return $this->_paths;
1132
                        }
1133
                        if ($plugin !== null && isset($this->_pathsForPlugin[$plugin])) {
1134
                                return $this->_pathsForPlugin[$plugin];
1135
                        }
1136
                }
1137
                $paths = array();
1138
                $viewPaths = App::path('View');
1139
                $corePaths = array_merge(App::core('View'), App::core('Console/Templates/skel/View'));
1140

    
1141
                if (!empty($plugin)) {
1142
                        $count = count($viewPaths);
1143
                        for ($i = 0; $i < $count; $i++) {
1144
                                if (!in_array($viewPaths[$i], $corePaths)) {
1145
                                        $paths[] = $viewPaths[$i] . 'Plugin' . DS . $plugin . DS;
1146
                                }
1147
                        }
1148
                        $paths = array_merge($paths, App::path('View', $plugin));
1149
                }
1150

    
1151
                $paths = array_unique(array_merge($paths, $viewPaths));
1152
                if (!empty($this->theme)) {
1153
                        $theme = Inflector::camelize($this->theme);
1154
                        $themePaths = array();
1155
                        foreach ($paths as $path) {
1156
                                if (strpos($path, DS . 'Plugin' . DS) === false) {
1157
                                        if ($plugin) {
1158
                                                $themePaths[] = $path . 'Themed' . DS . $theme . DS . 'Plugin' . DS . $plugin . DS;
1159
                                        }
1160
                                        $themePaths[] = $path . 'Themed' . DS . $theme . DS;
1161
                                }
1162
                        }
1163
                        $paths = array_merge($themePaths, $paths);
1164
                }
1165
                $paths = array_merge($paths, $corePaths);
1166
                if ($plugin !== null) {
1167
                        return $this->_pathsForPlugin[$plugin] = $paths;
1168
                }
1169
                return $this->_paths = $paths;
1170
        }
1171

    
1172
/**
1173
 * Checks if an element is cached and returns the cached data if present
1174
 *
1175
 * @param string $name Element name
1176
 * @param string $data Data
1177
 * @param array $options Element options
1178
 * @return string|null
1179
 */
1180
        protected function _elementCache($name, $data, $options) {
1181
                $plugin = null;
1182
                list($plugin, $name) = $this->pluginSplit($name);
1183

    
1184
                $underscored = null;
1185
                if ($plugin) {
1186
                        $underscored = Inflector::underscore($plugin);
1187
                }
1188
                $keys = array_merge(array($underscored, $name), array_keys($options), array_keys($data));
1189
                $this->elementCacheSettings = array(
1190
                        'config' => $this->elementCache,
1191
                        'key' => implode('_', $keys)
1192
                );
1193
                if (is_array($options['cache'])) {
1194
                        $defaults = array(
1195
                                'config' => $this->elementCache,
1196
                                'key' => $this->elementCacheSettings['key']
1197
                        );
1198
                        $this->elementCacheSettings = array_merge($defaults, $options['cache']);
1199
                }
1200
                $this->elementCacheSettings['key'] = 'element_' . $this->elementCacheSettings['key'];
1201
                return Cache::read($this->elementCacheSettings['key'], $this->elementCacheSettings['config']);
1202
        }
1203

    
1204
/**
1205
 * Renders an element and fires the before and afterRender callbacks for it
1206
 * and writes to the cache if a cache is used
1207
 *
1208
 * @param string $file Element file path
1209
 * @param array $data Data to render
1210
 * @param array $options Element options
1211
 * @return string
1212
 * @triggers View.beforeRender $this, array($file)
1213
 * @triggers View.afterRender $this, array($file, $element)
1214
 */
1215
        protected function _renderElement($file, $data, $options) {
1216
                $current = $this->_current;
1217
                $restore = $this->_currentType;
1218
                $this->_currentType = static::TYPE_ELEMENT;
1219

    
1220
                if ($options['callbacks']) {
1221
                        $this->getEventManager()->dispatch(new CakeEvent('View.beforeRender', $this, array($file)));
1222
                }
1223

    
1224
                $element = $this->_render($file, array_merge($this->viewVars, $data));
1225

    
1226
                if ($options['callbacks']) {
1227
                        $this->getEventManager()->dispatch(new CakeEvent('View.afterRender', $this, array($file, $element)));
1228
                }
1229

    
1230
                $this->_currentType = $restore;
1231
                $this->_current = $current;
1232

    
1233
                if (isset($options['cache'])) {
1234
                        Cache::write($this->elementCacheSettings['key'], $element, $this->elementCacheSettings['config']);
1235
                }
1236
                return $element;
1237
        }
1238
}