Completed
Push — master ( a5657d...420c94 )
by Mark
30s queued 11s
created

src/View/View.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11
 * @link          https://cakephp.org CakePHP(tm) Project
12
 * @since         0.10.0
13
 * @license       https://www.opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\View;
16
17
use Cake\Cache\Cache;
18
use Cake\Core\App;
19
use Cake\Core\Plugin;
20
use Cake\Event\EventDispatcherInterface;
21
use Cake\Event\EventDispatcherTrait;
22
use Cake\Event\EventManager;
23
use Cake\Http\Response;
24
use Cake\Http\ServerRequest;
25
use Cake\Log\LogTrait;
26
use Cake\Routing\RequestActionTrait;
27
use Cake\Routing\Router;
28
use Cake\Utility\Inflector;
29
use Cake\View\Exception\MissingElementException;
30
use Cake\View\Exception\MissingHelperException;
31
use Cake\View\Exception\MissingLayoutException;
32
use Cake\View\Exception\MissingTemplateException;
33
use InvalidArgumentException;
34
use LogicException;
35
use RuntimeException;
36
37
/**
38
 * View, the V in the MVC triad. View interacts with Helpers and view variables passed
39
 * in from the controller to render the results of the controller action. Often this is HTML,
40
 * but can also take the form of JSON, XML, PDF's or streaming files.
41
 *
42
 * CakePHP uses a two-step-view pattern. This means that the template content is rendered first,
43
 * and then inserted into the selected layout. This also means you can pass data from the template to the
44
 * layout using `$this->set()`
45
 *
46
 * View class supports using plugins as themes. You can set
47
 *
48
 * ```
49
 * public function beforeRender(\Cake\Event\Event $event)
50
 * {
51
 *      $this->viewBuilder()->setTheme('SuperHot');
52
 * }
53
 * ```
54
 *
55
 * in your Controller to use plugin `SuperHot` as a theme. Eg. If current action
56
 * is PostsController::index() then View class will look for template file
57
 * `plugins/SuperHot/Template/Posts/index.ctp`. If a theme template
58
 * is not found for the current action the default app template file is used.
59
 *
60
 * @property \Cake\View\Helper\BreadcrumbsHelper $Breadcrumbs
61
 * @property \Cake\View\Helper\FlashHelper $Flash
62
 * @property \Cake\View\Helper\FormHelper $Form
63
 * @property \Cake\View\Helper\HtmlHelper $Html
64
 * @property \Cake\View\Helper\NumberHelper $Number
65
 * @property \Cake\View\Helper\PaginatorHelper $Paginator
66
 * @property \Cake\View\Helper\RssHelper $Rss
67
 * @property \Cake\View\Helper\SessionHelper $Session
68
 * @property \Cake\View\Helper\TextHelper $Text
69
 * @property \Cake\View\Helper\TimeHelper $Time
70
 * @property \Cake\View\Helper\UrlHelper $Url
71
 * @property \Cake\View\ViewBlock $Blocks
72
 * @property string $view
73
 * @property string $viewPath
74
 */
75
class View implements EventDispatcherInterface
76
{
77
78
    use CellTrait {
79
        cell as public;
80
    }
81
    use EventDispatcherTrait;
82
    use LogTrait;
83
    use RequestActionTrait;
84
    use ViewVarsTrait;
85
86
    /**
87
     * Helpers collection
88
     *
89
     * @var \Cake\View\HelperRegistry
90
     */
91
    protected $_helpers;
92
93
    /**
94
     * ViewBlock instance.
95
     *
96
     * @var \Cake\View\ViewBlock
97
     */
98
    protected $Blocks;
99
100
    /**
101
     * The name of the plugin.
102
     *
103
     * @var string|null
104
     */
105
    protected $plugin;
106
107
    /**
108
     * Name of the controller that created the View if any.
109
     *
110
     * @var string
111
     */
112
    protected $name;
113
114
    /**
115
     * Current passed params. Passed to View from the creating Controller for convenience.
116
     *
117
     * @var array
118
     * @deprecated 3.1.0 Use `$this->request->getParam('pass')` instead.
119
     */
120
    public $passedArgs = [];
121
122
    /**
123
     * An array of names of built-in helpers to include.
124
     *
125
     * @var array
126
     */
127
    protected $helpers = [];
128
129
    /**
130
     * The name of the subfolder containing templates for this View.
131
     *
132
     * @var string
133
     */
134
    protected $templatePath;
135
136
    /**
137
     * The name of the template file to render. The name specified
138
     * is the filename in /src/Template/<SubFolder> without the .ctp extension.
139
     *
140
     * @var string
141
     */
142
    protected $template;
143
144
    /**
145
     * The name of the layout file to render the template inside of. The name specified
146
     * is the filename of the layout in /src/Template/Layout without the .ctp
147
     * extension.
148
     *
149
     * @var string
150
     */
151
    protected $layout = 'default';
152
153
    /**
154
     * The name of the layouts subfolder containing layouts for this View.
155
     *
156
     * @var string
157
     */
158
    protected $layoutPath;
159
160
    /**
161
     * Turns on or off CakePHP's conventional mode of applying layout files. On by default.
162
     * Setting to off means that layouts will not be automatically applied to rendered templates.
163
     *
164
     * @var bool
165
     */
166
    protected $autoLayout = true;
167
168
    /**
169
     * File extension. Defaults to CakePHP's template ".ctp".
170
     *
171
     * @var string
172
     */
173
    protected $_ext = '.ctp';
174
175
    /**
176
     * Sub-directory for this template file. This is often used for extension based routing.
177
     * Eg. With an `xml` extension, $subDir would be `xml/`
178
     *
179
     * @var string
180
     */
181
    protected $subDir = '';
182
183
    /**
184
     * The view theme to use.
185
     *
186
     * @var string|null
187
     */
188
    protected $theme;
189
190
    /**
191
     * True when the view has been rendered.
192
     *
193
     * @var bool
194
     * @deprecated 3.7.0 The property is deprecated and will be removed in 4.0.0.
195
     */
196
    public $hasRendered = false;
197
198
    /**
199
     * List of generated DOM UUIDs.
200
     *
201
     * @var array
202
     * @deprecated 3.7.0 The property is deprecated and will be removed in 4.0.0.
203
     */
204
    public $uuids = [];
205
206
    /**
207
     * An instance of a \Cake\Http\ServerRequest object that contains information about the current request.
208
     * This object contains all the information about a request and several methods for reading
209
     * additional information about the request.
210
     *
211
     * @var \Cake\Http\ServerRequest
212
     */
213
    protected $request;
214
215
    /**
216
     * Reference to the Response object
217
     *
218
     * @var \Cake\Http\Response
219
     */
220
    protected $response;
221
222
    /**
223
     * The Cache configuration View will use to store cached elements. Changing this will change
224
     * the default configuration elements are stored under. You can also choose a cache config
225
     * per element.
226
     *
227
     * @var string
228
     * @see \Cake\View\View::element()
229
     */
230
    protected $elementCache = 'default';
231
232
    /**
233
     * List of variables to collect from the associated controller.
234
     *
235
     * @var array
236
     */
237
    protected $_passedVars = [
238
        'viewVars', 'autoLayout', 'helpers', 'template', 'layout', 'name', 'theme',
239
        'layoutPath', 'templatePath', 'plugin', 'passedArgs'
240
    ];
241
242
    /**
243
     * Holds an array of paths.
244
     *
245
     * @var array
246
     */
247
    protected $_paths = [];
248
249
    /**
250
     * Holds an array of plugin paths.
251
     *
252
     * @var array
253
     */
254
    protected $_pathsForPlugin = [];
255
256
    /**
257
     * The names of views and their parents used with View::extend();
258
     *
259
     * @var array
260
     */
261
    protected $_parents = [];
262
263
    /**
264
     * The currently rendering view file. Used for resolving parent files.
265
     *
266
     * @var string
267
     */
268
    protected $_current;
269
270
    /**
271
     * Currently rendering an element. Used for finding parent fragments
272
     * for elements.
273
     *
274
     * @var string
275
     */
276
    protected $_currentType = '';
277
278
    /**
279
     * Content stack, used for nested templates that all use View::extend();
280
     *
281
     * @var array
282
     */
283
    protected $_stack = [];
284
285
    /**
286
     * ViewBlock class.
287
     *
288
     * @var string
289
     */
290
    protected $_viewBlockClass = ViewBlock::class;
291
292
    /**
293
     * Constant for view file type 'view'
294
     *
295
     * @var string
296
     * @deprecated 3.1.0 Use TYPE_TEMPLATE instead.
297
     */
298
    const TYPE_VIEW = 'view';
299
300
    /**
301
     * Constant for view file type 'template'.
302
     *
303
     * @var string
304
     */
305
    const TYPE_TEMPLATE = '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 name of view file 'Element'
316
     *
317
     * @var string
318
     */
319
    const NAME_ELEMENT = 'Element';
320
321
    /**
322
     * Constant for view file type 'layout'
323
     *
324
     * @var string
325
     */
326
    const TYPE_LAYOUT = 'layout';
327
328
    /**
329
     * Constant for template folder  'Template'
330
     *
331
     * @var string
332
     */
333
    const NAME_TEMPLATE = 'Template';
334
335
    /**
336
     * Constructor
337
     *
338
     * @param \Cake\Http\ServerRequest|null $request Request instance.
339
     * @param \Cake\Http\Response|null $response Response instance.
340
     * @param \Cake\Event\EventManager|null $eventManager Event manager instance.
341
     * @param array $viewOptions View options. See View::$_passedVars for list of
342
     *   options which get set as class properties.
343
     */
344
    public function __construct(
345
        ServerRequest $request = null,
346
        Response $response = null,
347
        EventManager $eventManager = null,
348
        array $viewOptions = []
349
    ) {
350
        if (isset($viewOptions['view'])) {
351
            $this->setTemplate($viewOptions['view']);
352
        }
353
        if (isset($viewOptions['viewPath'])) {
354
            $this->setTemplatePath($viewOptions['viewPath']);
355
        }
356
        foreach ($this->_passedVars as $var) {
357
            if (isset($viewOptions[$var])) {
358
                $this->{$var} = $viewOptions[$var];
359
            }
360
        }
361
        if ($eventManager !== null) {
362
            $this->setEventManager($eventManager);
363
        }
364
        $this->request = $request ?: Router::getRequest(true);
365
        $this->response = $response ?: new Response();
366
        if (!$this->request) {
367
            $this->request = new ServerRequest([
368
                'base' => '',
369
                'url' => '',
370
                'webroot' => '/'
371
            ]);
372
        }
373
        $this->Blocks = new $this->_viewBlockClass();
374
        $this->initialize();
375
        $this->loadHelpers();
376
    }
377
378
    /**
379
     * Initialization hook method.
380
     *
381
     * Properties like $helpers etc. cannot be initialized statically in your custom
382
     * view class as they are overwritten by values from controller in constructor.
383
     * So this method allows you to manipulate them as required after view instance
384
     * is constructed.
385
     *
386
     * @return void
387
     */
388
    public function initialize()
389
    {
390
    }
391
392
    /**
393
     * Gets the request instance.
394
     *
395
     * @return \Cake\Http\ServerRequest
396
     * @since 3.7.0
397
     */
398
    public function getRequest()
399
    {
400
        return $this->request;
401
    }
402
403
    /**
404
     * Sets the request objects and configures a number of controller properties
405
     * based on the contents of the request. The properties that get set are:
406
     *
407
     * - $this->request - To the $request parameter
408
     * - $this->plugin - To the value returned by $request->getParam('plugin')
409
     * - $this->passedArgs - Same as $request->params['pass]
410
     *
411
     * @param \Cake\Http\ServerRequest $request Request instance.
412
     * @return $this
413
     * @since 3.7.0
414
     */
415 View Code Duplication
    public function setRequest(ServerRequest $request)
416
    {
417
        $this->request = $request;
418
        $this->plugin = $request->getParam('plugin');
419
420
        if ($request->getParam('pass')) {
421
            $this->passedArgs = $request->getParam('pass');
422
        }
423
424
        return $this;
425
    }
426
427
    /**
428
     * Gets the response instance.
429
     *
430
     * @return \Cake\Http\Response
431
     * @since 3.7.0
432
     */
433
    public function getResponse()
434
    {
435
        return $this->response;
436
    }
437
438
    /**
439
     * Sets the response instance.
440
     *
441
     * @param \Cake\Http\Response $response Response instance.
442
     * @return $this
443
     * @since 3.7.0
444
     */
445
    public function setResponse(Response $response)
446
    {
447
        $this->response = $response;
448
449
        return $this;
450
    }
451
452
    /**
453
     * Get path for templates files.
454
     *
455
     * @return string
456
     */
457
    public function getTemplatePath()
458
    {
459
        return $this->templatePath;
460
    }
461
462
    /**
463
     * Set path for templates files.
464
     *
465
     * @param string $path Path for template files.
466
     * @return $this
467
     */
468
    public function setTemplatePath($path)
469
    {
470
        $this->templatePath = $path;
471
472
        return $this;
473
    }
474
475
    /**
476
     * Get/set path for templates files.
477
     *
478
     * @deprecated 3.5.0 Use getTemplatePath()/setTemplatePath() instead.
479
     * @param string|null $path Path for template files. If null returns current path.
480
     * @return string|null
481
     */
482
    public function templatePath($path = null)
483
    {
484
        deprecationWarning(
485
            'View::templatePath() is deprecated. ' .
486
            'Use getTemplatePath()/setTemplatePath() instead.'
487
        );
488
489
        if ($path === null) {
490
            return $this->templatePath;
491
        }
492
493
        $this->templatePath = $path;
494
    }
495
496
    /**
497
     * Get path for layout files.
498
     *
499
     * @return string
500
     */
501
    public function getLayoutPath()
502
    {
503
        return $this->layoutPath;
504
    }
505
506
    /**
507
     * Set path for layout files.
508
     *
509
     * @param string $path Path for layout files.
510
     * @return $this
511
     */
512
    public function setLayoutPath($path)
513
    {
514
        $this->layoutPath = $path;
515
516
        return $this;
517
    }
518
519
    /**
520
     * Get/set path for layout files.
521
     *
522
     * @deprecated 3.5.0 Use getLayoutPath()/setLayoutPath() instead.
523
     * @param string|null $path Path for layout files. If null returns current path.
524
     * @return string|null
525
     */
526
    public function layoutPath($path = null)
527
    {
528
        deprecationWarning(
529
            'View::layoutPath() is deprecated. ' .
530
            'Use getLayoutPath()/setLayoutPath() instead.'
531
        );
532
533
        if ($path === null) {
534
            return $this->layoutPath;
535
        }
536
537
        $this->layoutPath = $path;
538
    }
539
540
    /**
541
     * Returns if CakePHP's conventional mode of applying layout files is enabled.
542
     * Disabled means that layouts will not be automatically applied to rendered views.
543
     *
544
     * @return bool
545
     */
546
    public function isAutoLayoutEnabled()
547
    {
548
        return $this->autoLayout;
549
    }
550
551
    /**
552
     * Turns on or off CakePHP's conventional mode of applying layout files.
553
     * On by default. Setting to off means that layouts will not be
554
     * automatically applied to rendered views.
555
     *
556
     * @param bool $enable Boolean to turn on/off.
557
     * @return $this
558
     */
559
    public function enableAutoLayout($enable = true)
560
    {
561
        $this->autoLayout = (bool)$enable;
562
563
        return $this;
564
    }
565
566
    /**
567
     * Turns off CakePHP's conventional mode of applying layout files.
568
569
     * Layouts will not be automatically applied to rendered views.
570
     *
571
     * @return $this
572
     */
573
    public function disableAutoLayout()
574
    {
575
        $this->autoLayout = false;
576
577
        return $this;
578
    }
579
580
    /**
581
     * Turns on or off CakePHP's conventional mode of applying layout files.
582
     * On by default. Setting to off means that layouts will not be
583
     * automatically applied to rendered templates.
584
     *
585
     * @deprecated 3.5.0 Use isAutoLayoutEnabled()/enableAutoLayout() instead.
586
     * @param bool|null $autoLayout Boolean to turn on/off. If null returns current value.
587
     * @return bool|null
588
     */
589
    public function autoLayout($autoLayout = null)
590
    {
591
        deprecationWarning(
592
            'View::autoLayout() is deprecated. ' .
593
            'Use isAutoLayoutEnabled()/enableAutoLayout() instead.'
594
        );
595
596
        if ($autoLayout === null) {
597
            return $this->autoLayout;
598
        }
599
600
        $this->autoLayout = $autoLayout;
601
    }
602
603
    /**
604
     * Get the current view theme.
605
     *
606
     * @return string|null
607
     */
608
    public function getTheme()
609
    {
610
        return $this->theme;
611
    }
612
613
    /**
614
     * Set the view theme to use.
615
     *
616
     * @param string|null $theme Theme name.
617
     * @return $this
618
     */
619
    public function setTheme($theme)
620
    {
621
        $this->theme = $theme;
622
623
        return $this;
624
    }
625
626
    /**
627
     * The view theme to use.
628
     *
629
     * @deprecated 3.5.0 Use getTheme()/setTheme() instead.
630
     * @param string|null $theme Theme name. If null returns current theme.
631
     * @return string|null
632
     */
633
    public function theme($theme = null)
634
    {
635
        deprecationWarning(
636
            'View::theme() is deprecated. ' .
637
            'Use getTheme()/setTheme() instead.'
638
        );
639
640
        if ($theme === null) {
641
            return $this->theme;
642
        }
643
644
        $this->theme = $theme;
645
    }
646
647
    /**
648
     * Get the name of the template file to render. The name specified is the
649
     * filename in /src/Template/<SubFolder> without the .ctp extension.
650
     *
651
     * @return string
652
     */
653
    public function getTemplate()
654
    {
655
        return $this->template;
656
    }
657
658
    /**
659
     * Set the name of the template file to render. The name specified is the
660
     * filename in /src/Template/<SubFolder> without the .ctp extension.
661
     *
662
     * @param string $name Template file name to set.
663
     * @return $this
664
     */
665
    public function setTemplate($name)
666
    {
667
        $this->template = $name;
668
669
        return $this;
670
    }
671
672
    /**
673
     * Get/set the name of the template file to render. The name specified is the
674
     * filename in /src/Template/<SubFolder> without the .ctp extension.
675
     *
676
     * @deprecated 3.5.0 Use getTemplate()/setTemplate() instead.
677
     * @param string|null $name Template file name to set. If null returns current name.
678
     * @return string|null
679
     */
680
    public function template($name = null)
681
    {
682
        deprecationWarning(
683
            'View::template() is deprecated. ' .
684
            'Use getTemplate()/setTemplate() instead.'
685
        );
686
687
        if ($name === null) {
688
            return $this->template;
689
        }
690
691
        $this->template = $name;
692
    }
693
694
    /**
695
     * Get the name of the layout file to render the template inside of.
696
     * The name specified is the filename of the layout in /src/Template/Layout
697
     * without the .ctp extension.
698
     *
699
     * @return string
700
     */
701
    public function getLayout()
702
    {
703
        return $this->layout;
704
    }
705
706
    /**
707
     * Set the name of the layout file to render the template inside of.
708
     * The name specified is the filename of the layout in /src/Template/Layout
709
     * without the .ctp extension.
710
     *
711
     * @param string $name Layout file name to set.
712
     * @return $this
713
     */
714
    public function setLayout($name)
715
    {
716
        $this->layout = $name;
717
718
        return $this;
719
    }
720
721
    /**
722
     * Get/set the name of the layout file to render the template inside of.
723
     * The name specified is the filename of the layout in /src/Template/Layout
724
     * without the .ctp extension.
725
     *
726
     * @deprecated 3.5.0 Use getLayout()/setLayout() instead.
727
     * @param string|null $name Layout file name to set. If null returns current name.
728
     * @return string|null
729
     */
730
    public function layout($name = null)
731
    {
732
        deprecationWarning(
733
            'View::layout() is deprecated. ' .
734
            'Use getLayout()/setLayout() instead.'
735
        );
736
737
        if ($name === null) {
738
            return $this->layout;
739
        }
740
741
        $this->layout = $name;
742
    }
743
744
    /**
745
     * Renders a piece of PHP with provided parameters and returns HTML, XML, or any other string.
746
     *
747
     * This realizes the concept of Elements, (or "partial layouts") and the $params array is used to send
748
     * data to be used in the element. Elements can be cached improving performance by using the `cache` option.
749
     *
750
     * @param string $name Name of template file in the /src/Template/Element/ folder,
751
     *   or `MyPlugin.template` to use the template element from MyPlugin. If the element
752
     *   is not found in the plugin, the normal view path cascade will be searched.
753
     * @param array $data Array of data to be made available to the rendered view (i.e. the Element)
754
     * @param array $options Array of options. Possible keys are:
755
     * - `cache` - Can either be `true`, to enable caching using the config in View::$elementCache. Or an array
756
     *   If an array, the following keys can be used:
757
     *   - `config` - Used to store the cached element in a custom cache configuration.
758
     *   - `key` - Used to define the key used in the Cache::write(). It will be prefixed with `element_`
759
     * - `callbacks` - Set to true to fire beforeRender and afterRender helper callbacks for this element.
760
     *   Defaults to false.
761
     * - `ignoreMissing` - Used to allow missing elements. Set to true to not throw exceptions.
762
     * - `plugin` - setting to false will force to use the application's element from plugin templates, when the
763
     *   plugin has element with same name. Defaults to true
764
     * @return string Rendered Element
765
     * @throws \Cake\View\Exception\MissingElementException When an element is missing and `ignoreMissing`
766
     *   is false.
767
     */
768
    public function element($name, array $data = [], array $options = [])
769
    {
770
        $options += ['callbacks' => false, 'cache' => null, 'plugin' => null];
771
        if (isset($options['cache'])) {
772
            $options['cache'] = $this->_elementCache($name, $data, $options);
773
        }
774
775
        $pluginCheck = $options['plugin'] !== false;
776
        $file = $this->_getElementFileName($name, $pluginCheck);
777
        if ($file && $options['cache']) {
778
            return $this->cache(function () use ($file, $data, $options) {
779
                echo $this->_renderElement($file, $data, $options);
780
            }, $options['cache']);
781
        }
782
        if ($file) {
783
            return $this->_renderElement($file, $data, $options);
784
        }
785
786
        if (empty($options['ignoreMissing'])) {
787
            list ($plugin, $name) = pluginSplit($name, true);
788
            $name = str_replace('/', DIRECTORY_SEPARATOR, $name);
789
            $file = $plugin . static::NAME_ELEMENT . DIRECTORY_SEPARATOR . $name . $this->_ext;
790
            throw new MissingElementException([$file]);
791
        }
792
    }
793
794
    /**
795
     * Create a cached block of view logic.
796
     *
797
     * This allows you to cache a block of view output into the cache
798
     * defined in `elementCache`.
799
     *
800
     * This method will attempt to read the cache first. If the cache
801
     * is empty, the $block will be run and the output stored.
802
     *
803
     * @param callable $block The block of code that you want to cache the output of.
804
     * @param array $options The options defining the cache key etc.
805
     * @return string The rendered content.
806
     * @throws \RuntimeException When $options is lacking a 'key' option.
807
     */
808
    public function cache(callable $block, array $options = [])
809
    {
810
        $options += ['key' => '', 'config' => $this->elementCache];
811
        if (empty($options['key'])) {
812
            throw new RuntimeException('Cannot cache content with an empty key');
813
        }
814
        $result = Cache::read($options['key'], $options['config']);
815
        if ($result) {
816
            return $result;
817
        }
818
        ob_start();
819
        $block();
820
        $result = ob_get_clean();
821
822
        Cache::write($options['key'], $result, $options['config']);
823
824
        return $result;
825
    }
826
827
    /**
828
     * Checks if an element exists
829
     *
830
     * @param string $name Name of template file in the /src/Template/Element/ folder,
831
     *   or `MyPlugin.template` to check the template element from MyPlugin. If the element
832
     *   is not found in the plugin, the normal view path cascade will be searched.
833
     * @return bool Success
834
     */
835
    public function elementExists($name)
836
    {
837
        return (bool)$this->_getElementFileName($name);
838
    }
839
840
    /**
841
     * Renders view for given template file and layout.
842
     *
843
     * Render triggers helper callbacks, which are fired before and after the template are rendered,
844
     * as well as before and after the layout. The helper callbacks are called:
845
     *
846
     * - `beforeRender`
847
     * - `afterRender`
848
     * - `beforeLayout`
849
     * - `afterLayout`
850
     *
851
     * If View::$autoRender is false and no `$layout` is provided, the template will be returned bare.
852
     *
853
     * Template and layout names can point to plugin templates/layouts. Using the `Plugin.template` syntax
854
     * a plugin template/layout can be used instead of the app ones. If the chosen plugin is not found
855
     * the template will be located along the regular view path cascade.
856
     *
857
     * @param string|false|null $view Name of view file to use
858
     * @param string|null $layout Layout to use.
859
     * @return string|null Rendered content or null if content already rendered and returned earlier.
860
     * @throws \Cake\Core\Exception\Exception If there is an error in the view.
861
     * @triggers View.beforeRender $this, [$viewFileName]
862
     * @triggers View.afterRender $this, [$viewFileName]
863
     */
864
    public function render($view = null, $layout = null)
865
    {
866
        if ($this->hasRendered) {
867
            return null;
868
        }
869
870
        $defaultLayout = null;
871
        if ($layout !== null) {
872
            $defaultLayout = $this->layout;
873
            $this->layout = $layout;
874
        }
875
876
        $viewFileName = $view !== false ? $this->_getViewFileName($view) : null;
877
        if ($viewFileName) {
878
            $this->_currentType = static::TYPE_TEMPLATE;
879
            $this->dispatchEvent('View.beforeRender', [$viewFileName]);
880
            $this->Blocks->set('content', $this->_render($viewFileName));
881
            $this->dispatchEvent('View.afterRender', [$viewFileName]);
882
        }
883
884
        if ($this->layout && $this->autoLayout) {
885
            $this->Blocks->set('content', $this->renderLayout('', $this->layout));
886
        }
887
        if ($layout !== null) {
888
            $this->layout = $defaultLayout;
889
        }
890
891
        $this->hasRendered = true;
892
893
        return $this->Blocks->get('content');
894
    }
895
896
    /**
897
     * Renders a layout. Returns output from _render(). Returns false on error.
898
     * Several variables are created for use in layout.
899
     *
900
     * @param string $content Content to render in a template, wrapped by the surrounding layout.
901
     * @param string|null $layout Layout name
902
     * @return mixed Rendered output, or false on error
903
     * @throws \Cake\Core\Exception\Exception if there is an error in the view.
904
     * @triggers View.beforeLayout $this, [$layoutFileName]
905
     * @triggers View.afterLayout $this, [$layoutFileName]
906
     */
907
    public function renderLayout($content, $layout = null)
908
    {
909
        $layoutFileName = $this->_getLayoutFileName($layout);
910
        if (empty($layoutFileName)) {
911
            return $this->Blocks->get('content');
912
        }
913
914
        if (!empty($content)) {
915
             $this->Blocks->set('content', $content);
916
        }
917
918
        $this->dispatchEvent('View.beforeLayout', [$layoutFileName]);
919
920
        $title = $this->Blocks->get('title');
921
        if ($title === '') {
922
            $title = Inflector::humanize($this->templatePath);
923
            $this->Blocks->set('title', $title);
924
        }
925
926
        $this->_currentType = static::TYPE_LAYOUT;
927
        $this->Blocks->set('content', $this->_render($layoutFileName));
928
929
        $this->dispatchEvent('View.afterLayout', [$layoutFileName]);
930
931
        return $this->Blocks->get('content');
932
    }
933
934
    /**
935
     * Returns a list of variables available in the current View context
936
     *
937
     * @return string[] Array of the set view variable names.
938
     */
939
    public function getVars()
940
    {
941
        return array_keys($this->viewVars);
0 ignored issues
show
Deprecated Code introduced by
The property Cake\View\ViewVarsTrait::$viewVars has been deprecated with message: 3.7.0 Use `$this->set()` instead, also see `$this->viewBuilder()->getVar()`.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
942
    }
943
944
    /**
945
     * Returns the contents of the given View variable.
946
     *
947
     * @param string $var The view var you want the contents of.
948
     * @param mixed $default The default/fallback content of $var.
949
     * @return mixed The content of the named var if its set, otherwise $default.
950
     */
951
    public function get($var, $default = null)
952
    {
953
        if (!isset($this->viewVars[$var])) {
0 ignored issues
show
Deprecated Code introduced by
The property Cake\View\ViewVarsTrait::$viewVars has been deprecated with message: 3.7.0 Use `$this->set()` instead, also see `$this->viewBuilder()->getVar()`.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
954
            return $default;
955
        }
956
957
        return $this->viewVars[$var];
0 ignored issues
show
Deprecated Code introduced by
The property Cake\View\ViewVarsTrait::$viewVars has been deprecated with message: 3.7.0 Use `$this->set()` instead, also see `$this->viewBuilder()->getVar()`.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
958
    }
959
960
    /**
961
     * Get the names of all the existing blocks.
962
     *
963
     * @return array An array containing the blocks.
964
     * @see \Cake\View\ViewBlock::keys()
965
     */
966
    public function blocks()
967
    {
968
        return $this->Blocks->keys();
969
    }
970
971
    /**
972
     * Start capturing output for a 'block'
973
     *
974
     * You can use start on a block multiple times to
975
     * append or prepend content in a capture mode.
976
     *
977
     * ```
978
     * // Append content to an existing block.
979
     * $this->start('content');
980
     * echo $this->fetch('content');
981
     * echo 'Some new content';
982
     * $this->end();
983
     *
984
     * // Prepend content to an existing block
985
     * $this->start('content');
986
     * echo 'Some new content';
987
     * echo $this->fetch('content');
988
     * $this->end();
989
     * ```
990
     *
991
     * @param string $name The name of the block to capture for.
992
     * @return $this
993
     * @see \Cake\View\ViewBlock::start()
994
     */
995
    public function start($name)
996
    {
997
        $this->Blocks->start($name);
998
999
        return $this;
1000
    }
1001
1002
    /**
1003
     * Append to an existing or new block.
1004
     *
1005
     * Appending to a new block will create the block.
1006
     *
1007
     * @param string $name Name of the block
1008
     * @param mixed $value The content for the block. Value will be type cast
1009
     *   to string.
1010
     * @return $this
1011
     * @see \Cake\View\ViewBlock::concat()
1012
     */
1013
    public function append($name, $value = null)
1014
    {
1015
        $this->Blocks->concat($name, $value);
1016
1017
        return $this;
1018
    }
1019
1020
    /**
1021
     * Prepend to an existing or new block.
1022
     *
1023
     * Prepending to a new block will create the block.
1024
     *
1025
     * @param string $name Name of the block
1026
     * @param mixed $value The content for the block. Value will be type cast
1027
     *   to string.
1028
     * @return $this
1029
     * @see \Cake\View\ViewBlock::concat()
1030
     */
1031
    public function prepend($name, $value)
1032
    {
1033
        $this->Blocks->concat($name, $value, ViewBlock::PREPEND);
1034
1035
        return $this;
1036
    }
1037
1038
    /**
1039
     * Set the content for a block. This will overwrite any
1040
     * existing content.
1041
     *
1042
     * @param string $name Name of the block
1043
     * @param mixed $value The content for the block. Value will be type cast
1044
     *   to string.
1045
     * @return $this
1046
     * @see \Cake\View\ViewBlock::set()
1047
     */
1048
    public function assign($name, $value)
1049
    {
1050
        $this->Blocks->set($name, $value);
1051
1052
        return $this;
1053
    }
1054
1055
    /**
1056
     * Reset the content for a block. This will overwrite any
1057
     * existing content.
1058
     *
1059
     * @param string $name Name of the block
1060
     * @return $this
1061
     * @see \Cake\View\ViewBlock::set()
1062
     */
1063
    public function reset($name)
1064
    {
1065
        $this->assign($name, '');
1066
1067
        return $this;
1068
    }
1069
1070
    /**
1071
     * Fetch the content for a block. If a block is
1072
     * empty or undefined '' will be returned.
1073
     *
1074
     * @param string $name Name of the block
1075
     * @param string $default Default text
1076
     * @return string The block content or $default if the block does not exist.
1077
     * @see \Cake\View\ViewBlock::get()
1078
     */
1079
    public function fetch($name, $default = '')
1080
    {
1081
        return $this->Blocks->get($name, $default);
1082
    }
1083
1084
    /**
1085
     * End a capturing block. The compliment to View::start()
1086
     *
1087
     * @return $this
1088
     * @see \Cake\View\ViewBlock::end()
1089
     */
1090
    public function end()
1091
    {
1092
        $this->Blocks->end();
1093
1094
        return $this;
1095
    }
1096
1097
    /**
1098
     * Check if a block exists
1099
     *
1100
     * @param string $name Name of the block
1101
     *
1102
     * @return bool
1103
     */
1104
    public function exists($name)
1105
    {
1106
        return $this->Blocks->exists($name);
1107
    }
1108
1109
    /**
1110
     * Provides template or element extension/inheritance. Views can extends a
1111
     * parent view and populate blocks in the parent template.
1112
     *
1113
     * @param string $name The template or element to 'extend' the current one with.
1114
     * @return $this
1115
     * @throws \LogicException when you extend a template with itself or make extend loops.
1116
     * @throws \LogicException when you extend an element which doesn't exist
1117
     */
1118
    public function extend($name)
1119
    {
1120
        if ($name[0] === '/' || $this->_currentType === static::TYPE_TEMPLATE) {
1121
            $parent = $this->_getViewFileName($name);
1122
        } else {
1123
            switch ($this->_currentType) {
1124
                case static::TYPE_ELEMENT:
1125
                    $parent = $this->_getElementFileName($name);
1126
                    if (!$parent) {
1127
                        list($plugin, $name) = $this->pluginSplit($name);
1128
                        $paths = $this->_paths($plugin);
1129
                        $defaultPath = $paths[0] . static::NAME_ELEMENT . DIRECTORY_SEPARATOR;
1130
                        throw new LogicException(sprintf(
1131
                            'You cannot extend an element which does not exist (%s).',
1132
                            $defaultPath . $name . $this->_ext
1133
                        ));
1134
                    }
1135
                    break;
1136
                case static::TYPE_LAYOUT:
1137
                    $parent = $this->_getLayoutFileName($name);
1138
                    break;
1139
                default:
1140
                    $parent = $this->_getViewFileName($name);
1141
            }
1142
        }
1143
1144
        if ($parent == $this->_current) {
1145
            throw new LogicException('You cannot have views extend themselves.');
1146
        }
1147
        if (isset($this->_parents[$parent]) && $this->_parents[$parent] == $this->_current) {
1148
            throw new LogicException('You cannot have views extend in a loop.');
1149
        }
1150
        $this->_parents[$this->_current] = $parent;
1151
1152
        return $this;
1153
    }
1154
1155
    /**
1156
     * Generates a unique, non-random DOM ID for an object, based on the object type and the target URL.
1157
     *
1158
     * @param string $object Type of object, i.e. 'form' or 'link'
1159
     * @param string $url The object's target URL
1160
     * @return string
1161
     * @deprecated 3.7.0 This method is deprecated and will be removed in 4.0.0.
1162
     */
1163
    public function uuid($object, $url)
1164
    {
1165
        deprecationWarning('View::uuid() is deprecated and will be removed in 4.0.0.');
1166
1167
        $c = 1;
1168
        $url = Router::url($url);
1169
        $hash = $object . substr(md5($object . $url), 0, 10);
1170
        while (in_array($hash, $this->uuids)) {
1171
            $hash = $object . substr(md5($object . $url . $c), 0, 10);
1172
            $c++;
1173
        }
1174
        $this->uuids[] = $hash;
1175
1176
        return $hash;
1177
    }
1178
1179
    /**
1180
     * Retrieve the current view type
1181
     *
1182
     * @return string
1183
     */
1184
    public function getCurrentType()
1185
    {
1186
        return $this->_currentType;
1187
    }
1188
1189
    /**
1190
     * Magic accessor for helpers.
1191
     *
1192
     * @param string $name Name of the attribute to get.
1193
     * @return mixed
1194
     */
1195
    public function __get($name)
1196
    {
1197
        try {
1198
            $registry = $this->helpers();
1199
            if (isset($registry->{$name})) {
1200
                $this->{$name} = $registry->{$name};
1201
1202
                return $registry->{$name};
1203
            }
1204
        } catch (MissingHelperException $exception) {
1205
        }
1206
1207
        $deprecated = [
1208
            'view' => 'getTemplate',
1209
            'viewPath' => 'getTemplatePath',
1210
        ];
1211
        if (isset($deprecated[$name])) {
1212
            $method = $deprecated[$name];
1213
            deprecationWarning(sprintf(
1214
                'View::$%s is deprecated. Use View::%s() instead.',
1215
                $name,
1216
                $method
1217
            ));
1218
1219
            return $this->{$method}();
1220
        }
1221
1222
        $protected = [
1223
            'templatePath' => 'getTemplatePath',
1224
            'template' => 'getTemplate',
1225
            'layout' => 'getLayout',
1226
            'layoutPath' => 'setLayoutPath',
1227
            'autoLayout' => 'isAutoLayoutEnabled',
1228
            'theme' => 'getTheme',
1229
            'request' => 'getRequest',
1230
            'response' => 'getResponse',
1231
            'subDir' => 'getSubdir',
1232
            'plugin' => 'getPlugin',
1233
            'name' => 'getName',
1234
        ];
1235
        if (isset($protected[$name])) {
1236
            $method = $protected[$name];
1237
            deprecationWarning(sprintf(
1238
                'View::$%s is protected now. Use View::%s() instead.',
1239
                $name,
1240
                $method
1241
            ));
1242
1243
            return $this->{$method}();
1244
        }
1245
1246
        if ($name === 'Blocks') {
1247
            deprecationWarning(
1248
                'View::$Blocks is protected now. ' .
1249
                'Use one of the wrapper methods like View::fetch() etc. instead.'
1250
            );
1251
1252
            return $this->Blocks;
1253
        }
1254
1255
        if ($name === 'helpers') {
1256
            deprecationWarning(
1257
                'View::$helpers is protected now. ' .
1258
                'Use the helper registry through View::helpers() to manage helpers.'
1259
            );
1260
1261
            return $this->helpers;
1262
        }
1263
1264
        if (!empty($exception)) {
1265
            throw $exception;
1266
        }
1267
1268
        return $this->{$name};
1269
    }
1270
1271
    /**
1272
     * Magic setter for deprecated properties.
1273
     *
1274
     * @param string $name Name to property.
1275
     * @param mixed $value Value for property.
1276
     * @return void
1277
     */
1278
    public function __set($name, $value)
1279
    {
1280
        $deprecated = [
1281
            'view' => 'setTemplate',
1282
            'viewPath' => 'setTemplatePath',
1283
        ];
1284
        if (isset($deprecated[$name])) {
1285
            $method = $deprecated[$name];
1286
            deprecationWarning(sprintf(
1287
                'View::$%s is deprecated. Use View::%s() instead.',
1288
                $name,
1289
                $method
1290
            ));
1291
1292
            $this->{$method}($value);
1293
1294
            return;
1295
        }
1296
1297
        $protected = [
1298
            'templatePath' => 'setTemplatePath',
1299
            'template' => 'setTemplate',
1300
            'layout' => 'setLayout',
1301
            'layoutPath' => 'setLayoutPath',
1302
            'autoLayout' => 'enableAutoLayout',
1303
            'theme' => 'setTheme',
1304
            'request' => 'setRequest',
1305
            'response' => 'setResponse',
1306
            'subDir' => 'setSubDir',
1307
            'plugin' => 'setPlugin',
1308
            'name' => 'setName',
1309
            'elementCache' => 'setElementCache',
1310
        ];
1311
        if (isset($protected[$name])) {
1312
            $method = $protected[$name];
1313
            deprecationWarning(sprintf(
1314
                'View::$%s is protected now. Use View::%s() instead.',
1315
                $name,
1316
                $method
1317
            ));
1318
1319
            $this->{$method}($value);
1320
1321
            return;
1322
        }
1323
1324
        if ($name === 'helpers') {
1325
            deprecationWarning(
1326
                'View::$helpers is protected now. ' .
1327
                'Use the helper registry through View::helpers() to manage helpers.'
1328
            );
1329
1330
            return $this->helpers = $value;
1331
        }
1332
1333
        $this->{$name} = $value;
1334
    }
1335
1336
    /**
1337
     * Interact with the HelperRegistry to load all the helpers.
1338
     *
1339
     * @return $this
1340
     */
1341
    public function loadHelpers()
1342
    {
1343
        $registry = $this->helpers();
1344
        $helpers = $registry->normalizeArray($this->helpers);
1345
        foreach ($helpers as $properties) {
1346
            $this->loadHelper($properties['class'], $properties['config']);
1347
        }
1348
1349
        return $this;
1350
    }
1351
1352
    /**
1353
     * Renders and returns output for given template filename with its
1354
     * array of data. Handles parent/extended templates.
1355
     *
1356
     * @param string $viewFile Filename of the view
1357
     * @param array $data Data to include in rendered view. If empty the current
1358
     *   View::$viewVars will be used.
1359
     * @return string Rendered output
1360
     * @throws \LogicException When a block is left open.
1361
     * @triggers View.beforeRenderFile $this, [$viewFile]
1362
     * @triggers View.afterRenderFile $this, [$viewFile, $content]
1363
     */
1364
    protected function _render($viewFile, $data = [])
1365
    {
1366
        if (empty($data)) {
1367
            $data = $this->viewVars;
0 ignored issues
show
Deprecated Code introduced by
The property Cake\View\ViewVarsTrait::$viewVars has been deprecated with message: 3.7.0 Use `$this->set()` instead, also see `$this->viewBuilder()->getVar()`.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1368
        }
1369
        $this->_current = $viewFile;
1370
        $initialBlocks = count($this->Blocks->unclosed());
1371
1372
        $this->dispatchEvent('View.beforeRenderFile', [$viewFile]);
1373
1374
        $content = $this->_evaluate($viewFile, $data);
1375
1376
        $afterEvent = $this->dispatchEvent('View.afterRenderFile', [$viewFile, $content]);
1377
        if ($afterEvent->getResult() !== null) {
1378
            $content = $afterEvent->getResult();
1379
        }
1380
1381
        if (isset($this->_parents[$viewFile])) {
1382
            $this->_stack[] = $this->fetch('content');
1383
            $this->assign('content', $content);
1384
1385
            $content = $this->_render($this->_parents[$viewFile]);
1386
            $this->assign('content', array_pop($this->_stack));
1387
        }
1388
1389
        $remainingBlocks = count($this->Blocks->unclosed());
1390
1391
        if ($initialBlocks !== $remainingBlocks) {
1392
            throw new LogicException(sprintf(
1393
                'The "%s" block was left open. Blocks are not allowed to cross files.',
1394
                $this->Blocks->active()
1395
            ));
1396
        }
1397
1398
        return $content;
1399
    }
1400
1401
    /**
1402
     * Sandbox method to evaluate a template / view script in.
1403
     *
1404
     * @param string $viewFile Filename of the view
1405
     * @param array $dataForView Data to include in rendered view.
1406
     * @return string Rendered output
1407
     */
1408
    protected function _evaluate($viewFile, $dataForView)
1409
    {
1410
        extract($dataForView);
1411
        ob_start();
1412
1413
        include func_get_arg(0);
1414
1415
        return ob_get_clean();
1416
    }
1417
1418
    /**
1419
     * Get the helper registry in use by this View class.
1420
     *
1421
     * @return \Cake\View\HelperRegistry
1422
     */
1423
    public function helpers()
1424
    {
1425
        if ($this->_helpers === null) {
1426
            $this->_helpers = new HelperRegistry($this);
1427
        }
1428
1429
        return $this->_helpers;
1430
    }
1431
1432
    /**
1433
     * Loads a helper. Delegates to the `HelperRegistry::load()` to load the helper
1434
     *
1435
     * @param string $name Name of the helper to load.
1436
     * @param array $config Settings for the helper
1437
     * @return \Cake\View\Helper a constructed helper object.
1438
     * @see \Cake\View\HelperRegistry::load()
1439
     */
1440
    public function loadHelper($name, array $config = [])
1441
    {
1442
        list(, $class) = pluginSplit($name);
1443
        $helpers = $this->helpers();
1444
1445
        return $this->{$class} = $helpers->load($name, $config);
1446
    }
1447
1448
    /**
1449
     * Set sub-directory for this template files.
1450
     *
1451
     * @param string $subDir Sub-directory name.
1452
     * @return $this
1453
     * @see \Cake\View\View::$subDir
1454
     * @since 3.7.0
1455
     */
1456
    public function setSubDir($subDir)
1457
    {
1458
        $this->subDir = $subDir;
1459
1460
        return $this;
1461
    }
1462
1463
    /**
1464
     * Get sub-directory for this template files.
1465
     *
1466
     * @return string
1467
     * @see \Cake\View\View::$subDir
1468
     * @since 3.7.0
1469
     */
1470
    public function getSubDir()
1471
    {
1472
        return $this->subDir;
1473
    }
1474
1475
    /**
1476
     * Returns the plugin name.
1477
     *
1478
     * @return string|null
1479
     * @since 3.7.0
1480
     */
1481
    public function getPlugin()
1482
    {
1483
        return $this->plugin;
1484
    }
1485
1486
    /**
1487
     * Sets the plugin name.
1488
     *
1489
     * @param string $name Plugin name.
1490
     * @return $this
1491
     * @since 3.7.0
1492
     */
1493
    public function setPlugin($name)
1494
    {
1495
        $this->plugin = $name;
1496
1497
        return $this;
1498
    }
1499
1500
    /**
1501
     * Set The cache configuration View will use to store cached elements
1502
     *
1503
     * @param string $elementCache Cache config name.
1504
     * @return $this
1505
     * @see \Cake\View\View::$elementCache
1506
     * @since 3.7.0
1507
     */
1508
    public function setElementCache($elementCache)
1509
    {
1510
        $this->elementCache = $elementCache;
1511
1512
        return $this;
1513
    }
1514
1515
    /**
1516
     * Returns filename of given action's template file (.ctp) as a string.
1517
     * CamelCased action names will be under_scored by default.
1518
     * This means that you can have LongActionNames that refer to
1519
     * long_action_names.ctp views. You can change the inflection rule by
1520
     * overriding _inflectViewFileName.
1521
     *
1522
     * @param string|null $name Controller action to find template filename for
1523
     * @return string Template filename
1524
     * @throws \Cake\View\Exception\MissingTemplateException when a view file could not be found.
1525
     */
1526
    protected function _getViewFileName($name = null)
1527
    {
1528
        $templatePath = $subDir = '';
1529
1530
        if ($this->templatePath) {
1531
            $templatePath = $this->templatePath . DIRECTORY_SEPARATOR;
1532
        }
1533
        if (strlen($this->subDir)) {
1534
            $subDir = $this->subDir . DIRECTORY_SEPARATOR;
1535
            // Check if templatePath already terminates with subDir
1536
            if ($templatePath != $subDir && substr($templatePath, -strlen($subDir)) == $subDir) {
1537
                $subDir = '';
1538
            }
1539
        }
1540
1541
        if ($name === null) {
1542
            $name = $this->template;
1543
        }
1544
1545
        list($plugin, $name) = $this->pluginSplit($name);
1546
        $name = str_replace('/', DIRECTORY_SEPARATOR, $name);
1547
1548
        if (strpos($name, DIRECTORY_SEPARATOR) === false && $name !== '' && $name[0] !== '.') {
1549
            $name = $templatePath . $subDir . $this->_inflectViewFileName($name);
1550
        } elseif (strpos($name, DIRECTORY_SEPARATOR) !== false) {
1551
            if ($name[0] === DIRECTORY_SEPARATOR || $name[1] === ':') {
1552
                $name = trim($name, DIRECTORY_SEPARATOR);
1553
            } elseif (!$plugin || $this->templatePath !== $this->name) {
1554
                $name = $templatePath . $subDir . $name;
1555
            } else {
1556
                $name = DIRECTORY_SEPARATOR . $subDir . $name;
1557
            }
1558
        }
1559
1560
        foreach ($this->_paths($plugin) as $path) {
1561 View Code Duplication
            if (file_exists($path . $name . $this->_ext)) {
1562
                return $this->_checkFilePath($path . $name . $this->_ext, $path);
1563
            }
1564
        }
1565
        throw new MissingTemplateException(['file' => $name . $this->_ext]);
1566
    }
1567
1568
    /**
1569
     * Change the name of a view template file into underscored format.
1570
     *
1571
     * @param string $name Name of file which should be inflected.
1572
     * @return string File name after conversion
1573
     */
1574
    protected function _inflectViewFileName($name)
1575
    {
1576
        return Inflector::underscore($name);
1577
    }
1578
1579
    /**
1580
     * Check that a view file path does not go outside of the defined template paths.
1581
     *
1582
     * Only paths that contain `..` will be checked, as they are the ones most likely to
1583
     * have the ability to resolve to files outside of the template paths.
1584
     *
1585
     * @param string $file The path to the template file.
1586
     * @param string $path Base path that $file should be inside of.
1587
     * @return string The file path
1588
     * @throws \InvalidArgumentException
1589
     */
1590
    protected function _checkFilePath($file, $path)
1591
    {
1592
        if (strpos($file, '..') === false) {
1593
            return $file;
1594
        }
1595
        $absolute = realpath($file);
1596
        if (strpos($absolute, $path) !== 0) {
1597
            throw new InvalidArgumentException(sprintf(
1598
                'Cannot use "%s" as a template, it is not within any view template path.',
1599
                $file
1600
            ));
1601
        }
1602
1603
        return $absolute;
1604
    }
1605
1606
    /**
1607
     * Splits a dot syntax plugin name into its plugin and filename.
1608
     * If $name does not have a dot, then index 0 will be null.
1609
     * It checks if the plugin is loaded, else filename will stay unchanged for filenames containing dot
1610
     *
1611
     * @param string $name The name you want to plugin split.
1612
     * @param bool $fallback If true uses the plugin set in the current Request when parsed plugin is not loaded
1613
     * @return array Array with 2 indexes. 0 => plugin name, 1 => filename
1614
     */
1615
    public function pluginSplit($name, $fallback = true)
1616
    {
1617
        $plugin = null;
1618
        list($first, $second) = pluginSplit($name);
1619
        if (Plugin::isLoaded($first) === true) {
1620
            $name = $second;
1621
            $plugin = $first;
1622
        }
1623
        if (isset($this->plugin) && !$plugin && $fallback) {
1624
            $plugin = $this->plugin;
1625
        }
1626
1627
        return [$plugin, $name];
1628
    }
1629
1630
    /**
1631
     * Returns layout filename for this template as a string.
1632
     *
1633
     * @param string|null $name The name of the layout to find.
1634
     * @return string Filename for layout file (.ctp).
1635
     * @throws \Cake\View\Exception\MissingLayoutException when a layout cannot be located
1636
     */
1637
    protected function _getLayoutFileName($name = null)
1638
    {
1639
        if ($name === null) {
1640
            $name = $this->layout;
1641
        }
1642
        $subDir = null;
1643
1644
        if ($this->layoutPath) {
1645
            $subDir = $this->layoutPath . DIRECTORY_SEPARATOR;
1646
        }
1647
        list($plugin, $name) = $this->pluginSplit($name);
1648
1649
        $layoutPaths = $this->_getSubPaths('Layout' . DIRECTORY_SEPARATOR . $subDir);
1650
1651
        foreach ($this->_paths($plugin) as $path) {
1652
            foreach ($layoutPaths as $layoutPath) {
1653
                $currentPath = $path . $layoutPath;
1654 View Code Duplication
                if (file_exists($currentPath . $name . $this->_ext)) {
1655
                    return $this->_checkFilePath($currentPath . $name . $this->_ext, $currentPath);
1656
                }
1657
            }
1658
        }
1659
        throw new MissingLayoutException([
1660
            'file' => $layoutPaths[0] . $name . $this->_ext
1661
        ]);
1662
    }
1663
1664
    /**
1665
     * Finds an element filename, returns false on failure.
1666
     *
1667
     * @param string $name The name of the element to find.
1668
     * @param bool $pluginCheck - if false will ignore the request's plugin if parsed plugin is not loaded
1669
     * @return string|false Either a string to the element filename or false when one can't be found.
1670
     */
1671
    protected function _getElementFileName($name, $pluginCheck = true)
1672
    {
1673
        list($plugin, $name) = $this->pluginSplit($name, $pluginCheck);
1674
1675
        $paths = $this->_paths($plugin);
1676
        $elementPaths = $this->_getSubPaths(static::NAME_ELEMENT);
1677
1678
        foreach ($paths as $path) {
1679
            foreach ($elementPaths as $elementPath) {
1680
                if (file_exists($path . $elementPath . DIRECTORY_SEPARATOR . $name . $this->_ext)) {
1681
                    return $path . $elementPath . DIRECTORY_SEPARATOR . $name . $this->_ext;
1682
                }
1683
            }
1684
        }
1685
1686
        return false;
1687
    }
1688
1689
    /**
1690
     * Find all sub templates path, based on $basePath
1691
     * If a prefix is defined in the current request, this method will prepend
1692
     * the prefixed template path to the $basePath, cascading up in case the prefix
1693
     * is nested.
1694
     * This is essentially used to find prefixed template paths for elements
1695
     * and layouts.
1696
     *
1697
     * @param string $basePath Base path on which to get the prefixed one.
1698
     * @return array Array with all the templates paths.
1699
     */
1700
    protected function _getSubPaths($basePath)
1701
    {
1702
        $paths = [$basePath];
1703
        if ($this->request->getParam('prefix')) {
1704
            $prefixPath = explode('/', $this->request->getParam('prefix'));
1705
            $path = '';
1706
            foreach ($prefixPath as $prefixPart) {
1707
                $path .= Inflector::camelize($prefixPart) . DIRECTORY_SEPARATOR;
1708
1709
                array_unshift(
1710
                    $paths,
1711
                    $path . $basePath
1712
                );
1713
            }
1714
        }
1715
1716
        return $paths;
1717
    }
1718
1719
    /**
1720
     * Return all possible paths to find view files in order
1721
     *
1722
     * @param string|null $plugin Optional plugin name to scan for view files.
1723
     * @param bool $cached Set to false to force a refresh of view paths. Default true.
1724
     * @return array paths
1725
     */
1726
    protected function _paths($plugin = null, $cached = true)
1727
    {
1728
        if ($cached === true) {
1729
            if ($plugin === null && !empty($this->_paths)) {
1730
                return $this->_paths;
1731
            }
1732
            if ($plugin !== null && isset($this->_pathsForPlugin[$plugin])) {
1733
                return $this->_pathsForPlugin[$plugin];
1734
            }
1735
        }
1736
        $templatePaths = App::path(static::NAME_TEMPLATE);
1737
        $pluginPaths = $themePaths = [];
1738
        if (!empty($plugin)) {
1739 View Code Duplication
            for ($i = 0, $count = count($templatePaths); $i < $count; $i++) {
1740
                $pluginPaths[] = $templatePaths[$i] . 'Plugin' . DIRECTORY_SEPARATOR . $plugin . DIRECTORY_SEPARATOR;
1741
            }
1742
            $pluginPaths = array_merge($pluginPaths, App::path(static::NAME_TEMPLATE, $plugin));
1743
        }
1744
1745
        if (!empty($this->theme)) {
1746
            $themePaths = App::path(static::NAME_TEMPLATE, Inflector::camelize($this->theme));
1747
1748
            if ($plugin) {
1749 View Code Duplication
                for ($i = 0, $count = count($themePaths); $i < $count; $i++) {
1750
                    array_unshift($themePaths, $themePaths[$i] . 'Plugin' . DIRECTORY_SEPARATOR . $plugin . DIRECTORY_SEPARATOR);
1751
                }
1752
            }
1753
        }
1754
1755
        $paths = array_merge(
1756
            $themePaths,
1757
            $pluginPaths,
1758
            $templatePaths,
1759
            [dirname(__DIR__) . DIRECTORY_SEPARATOR . static::NAME_TEMPLATE . DIRECTORY_SEPARATOR]
1760
        );
1761
1762
        if ($plugin !== null) {
1763
            return $this->_pathsForPlugin[$plugin] = $paths;
1764
        }
1765
1766
        return $this->_paths = $paths;
1767
    }
1768
1769
    /**
1770
     * Generate the cache configuration options for an element.
1771
     *
1772
     * @param string $name Element name
1773
     * @param array $data Data
1774
     * @param array $options Element options
1775
     * @return array Element Cache configuration.
1776
     */
1777
    protected function _elementCache($name, $data, $options)
1778
    {
1779
        if (isset($options['cache']['key'], $options['cache']['config'])) {
1780
            $cache = $options['cache'];
1781
            $cache['key'] = 'element_' . $cache['key'];
1782
1783
            return $cache;
1784
        }
1785
1786
        $plugin = null;
1787
        list($plugin, $name) = $this->pluginSplit($name);
1788
1789
        $underscored = null;
1790
        if ($plugin) {
1791
            $underscored = Inflector::underscore($plugin);
1792
        }
1793
1794
        $cache = $options['cache'];
1795
        unset($options['cache'], $options['callbacks'], $options['plugin']);
1796
        $keys = array_merge(
1797
            [$underscored, $name],
1798
            array_keys($options),
1799
            array_keys($data)
1800
        );
1801
        $config = [
1802
            'config' => $this->elementCache,
1803
            'key' => implode('_', $keys)
1804
        ];
1805
        if (is_array($cache)) {
1806
            $defaults = [
1807
                'config' => $this->elementCache,
1808
                'key' => $config['key']
1809
            ];
1810
            $config = $cache + $defaults;
1811
        }
1812
        $config['key'] = 'element_' . $config['key'];
1813
1814
        return $config;
1815
    }
1816
1817
    /**
1818
     * Renders an element and fires the before and afterRender callbacks for it
1819
     * and writes to the cache if a cache is used
1820
     *
1821
     * @param string $file Element file path
1822
     * @param array $data Data to render
1823
     * @param array $options Element options
1824
     * @return string
1825
     * @triggers View.beforeRender $this, [$file]
1826
     * @triggers View.afterRender $this, [$file, $element]
1827
     */
1828
    protected function _renderElement($file, $data, $options)
1829
    {
1830
        $current = $this->_current;
1831
        $restore = $this->_currentType;
1832
        $this->_currentType = static::TYPE_ELEMENT;
1833
1834
        if ($options['callbacks']) {
1835
            $this->dispatchEvent('View.beforeRender', [$file]);
1836
        }
1837
1838
        $element = $this->_render($file, array_merge($this->viewVars, $data));
0 ignored issues
show
Deprecated Code introduced by
The property Cake\View\ViewVarsTrait::$viewVars has been deprecated with message: 3.7.0 Use `$this->set()` instead, also see `$this->viewBuilder()->getVar()`.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1839
1840
        if ($options['callbacks']) {
1841
            $this->dispatchEvent('View.afterRender', [$file, $element]);
1842
        }
1843
1844
        $this->_currentType = $restore;
1845
        $this->_current = $current;
1846
1847
        return $element;
1848
    }
1849
}
1850