xref: /webtrees/app/View.php (revision 4459dc9a6d0c27769f8135175f3569e4fa287451)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2019 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16declare(strict_types=1);
17
18namespace Fisharebest\Webtrees;
19
20use Exception;
21use function ob_end_clean;
22use Throwable;
23
24/**
25 * Simple view/template class.
26 */
27class View
28{
29    // Where do our templates live
30    private const TEMPLATE_PATH = 'resources/views/';
31
32    // File extension for our template files.
33    private const TEMPLATE_EXTENSION = '.phtml';
34
35    /**
36     * @var string The (file) name of the view.
37     */
38    private $name;
39
40    /**
41     * @var mixed[] Data to be inserted into the view.
42     */
43    private $data;
44
45    /**
46     * @var mixed[] Data to be inserted into all views.
47     */
48    private static $shared_data = [];
49
50    /**
51     * @var string Implementation of Blade "stacks".
52     */
53    private static $stack;
54
55    /**
56     * @var array[] Implementation of Blade "stacks".
57     */
58    private static $stacks = [];
59
60    /**
61     * Createa view from a template name and optional data.
62     *
63     * @param string $name
64     * @param array  $data
65     */
66    public function __construct(string $name, $data = [])
67    {
68        $this->name = $name;
69        $this->data = $data;
70    }
71
72    /**
73     * Shared data that is available to all views.
74     *
75     * @param string $key
76     * @param mixed  $value
77     *
78     * @return void
79     */
80    public static function share(string $key, $value)
81    {
82        self::$shared_data[$key] = $value;
83    }
84
85    /**
86     * Implementation of Blade "stacks".
87     *
88     * @see https://laravel.com/docs/5.5/blade#stacks
89     *
90     * @param string $stack
91     *
92     * @return void
93     */
94    public static function push(string $stack)
95    {
96        self::$stack = $stack;
97        ob_start();
98    }
99
100    /**
101     * Implementation of Blade "stacks".
102     *
103     * @return void
104     */
105    public static function endpush()
106    {
107        $content = ob_get_clean();
108        $hash    = sha1($content);
109
110        self::$stacks[self::$stack][$hash] = $content;
111    }
112
113    /**
114     * Implementation of Blade "stacks".
115     *
116     * @param string $stack
117     *
118     * @return string
119     */
120    public static function stack(string $stack): string
121    {
122        $content = implode('', self::$stacks[$stack] ?? []);
123
124        self::$stacks[$stack] = [];
125
126        return $content;
127    }
128
129    /**
130     * Render a view.
131     *
132     * @return string
133     * @throws Throwable
134     */
135    public function render(): string
136    {
137        $variables_for_view = $this->data + self::$shared_data;
138        extract($variables_for_view);
139
140        try {
141            ob_start();
142            // Do not use require, so we can catch errors for missing files
143            include $this->getFilenameForView($this->name);
144
145            return ob_get_clean();
146        } catch (Throwable $ex) {
147            ob_end_clean();
148            throw $ex;
149        }
150    }
151
152    /**
153     * Allow a theme to override the default views.
154     *
155     * @param string $view_name
156     *
157     * @return string
158     * @throws Exception
159     */
160    public function getFilenameForView($view_name): string
161    {
162        foreach ($this->paths() as $path) {
163            $view_file = $path . $view_name . self::TEMPLATE_EXTENSION;
164
165            if (is_file($view_file)) {
166                return $view_file;
167            }
168        }
169
170        throw new Exception('View not found: ' . e($view_name));
171    }
172
173    /**
174     * Cerate and render a view in a single operation.
175     *
176     * @param string  $name
177     * @param mixed[] $data
178     *
179     * @return string
180     */
181    public static function make($name, $data = []): string
182    {
183        $view = new static($name, $data);
184
185        DebugBar::addView($name, $data);
186
187        return $view->render();
188    }
189
190    /**
191     * @return string[]
192     */
193    private function paths(): array
194    {
195        static $paths = [];
196
197        if (empty($paths)) {
198            // Module views
199            // @TODO - this includes disabled modules.
200            //$paths = glob(WT_ROOT . Webtrees::MODULES_PATH . '*/' . self::TEMPLATE_PATH);
201            // Theme views
202            // @TODO - this won't work during setup.
203            //$paths[] = WT_ROOT . Webtrees::THEMES_PATH . app(ModuleThemeInterface::class)->name() . '/' . self::TEMPLATE_PATH;
204            // Core views
205            $paths[] = WT_ROOT . self::TEMPLATE_PATH;
206
207            $paths = array_filter($paths, function (string $path): bool {
208                return is_dir($path);
209            });
210        }
211
212        return $paths;
213    }
214}
215