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 Fisharebest\Webtrees\Module\ModuleThemeInterface; 22use function ob_end_clean; 23use Throwable; 24 25/** 26 * Simple view/template class. 27 */ 28class View 29{ 30 // Where do our templates live 31 private const TEMPLATE_PATH = 'resources/views/'; 32 33 // File extension for our template files. 34 private const TEMPLATE_EXTENSION = '.phtml'; 35 36 /** 37 * @var string The (file) name of the view. 38 */ 39 private $name; 40 41 /** 42 * @var mixed[] Data to be inserted into the view. 43 */ 44 private $data; 45 46 /** 47 * @var mixed[] Data to be inserted into all views. 48 */ 49 private static $shared_data = []; 50 51 /** 52 * @var string Implementation of Blade "stacks". 53 */ 54 private static $stack; 55 56 /** 57 * @var array[] Implementation of Blade "stacks". 58 */ 59 private static $stacks = []; 60 61 /** 62 * Createa view from a template name and optional data. 63 * 64 * @param string $name 65 * @param array $data 66 */ 67 public function __construct(string $name, $data = []) 68 { 69 $this->name = $name; 70 $this->data = $data; 71 } 72 73 /** 74 * Shared data that is available to all views. 75 * 76 * @param string $key 77 * @param mixed $value 78 * 79 * @return void 80 */ 81 public static function share(string $key, $value) 82 { 83 self::$shared_data[$key] = $value; 84 } 85 86 /** 87 * Implementation of Blade "stacks". 88 * 89 * @see https://laravel.com/docs/5.5/blade#stacks 90 * 91 * @param string $stack 92 * 93 * @return void 94 */ 95 public static function push(string $stack) 96 { 97 self::$stack = $stack; 98 ob_start(); 99 } 100 101 /** 102 * Implementation of Blade "stacks". 103 * 104 * @return void 105 */ 106 public static function endpush() 107 { 108 $content = ob_get_clean(); 109 $hash = sha1($content); 110 111 self::$stacks[self::$stack][$hash] = $content; 112 } 113 114 /** 115 * Implementation of Blade "stacks". 116 * 117 * @param string $stack 118 * 119 * @return string 120 */ 121 public static function stack(string $stack): string 122 { 123 $content = implode('', self::$stacks[$stack] ?? []); 124 125 self::$stacks[$stack] = []; 126 127 return $content; 128 } 129 130 /** 131 * Render a view. 132 * 133 * @return string 134 * @throws Throwable 135 */ 136 public function render(): string 137 { 138 $variables_for_view = $this->data + self::$shared_data; 139 extract($variables_for_view); 140 141 try { 142 ob_start(); 143 // Do not use require, so we can catch errors for missing files 144 include $this->getFilenameForView($this->name); 145 146 return ob_get_clean(); 147 } catch (Throwable $ex) { 148 ob_end_clean(); 149 throw $ex; 150 } 151 } 152 153 /** 154 * Allow a theme to override the default views. 155 * 156 * @param string $view_name 157 * 158 * @return string 159 * @throws Exception 160 */ 161 public function getFilenameForView($view_name): string 162 { 163 foreach ($this->paths() as $path) { 164 $view_file = $path . $view_name . self::TEMPLATE_EXTENSION; 165 166 if (is_file($view_file)) { 167 return $view_file; 168 } 169 } 170 171 throw new Exception('View not found: ' . e($view_name)); 172 } 173 174 /** 175 * Cerate and render a view in a single operation. 176 * 177 * @param string $name 178 * @param mixed[] $data 179 * 180 * @return string 181 */ 182 public static function make($name, $data = []): string 183 { 184 $view = new static($name, $data); 185 186 DebugBar::addView($name, $data); 187 188 return $view->render(); 189 } 190 191 /** 192 * @return string[] 193 */ 194 private function paths(): array 195 { 196 static $paths = []; 197 198 if (empty($paths)) { 199 // Module views 200 // @TODO - this includes disabled modules. 201 $paths = glob(WT_ROOT . Webtrees::MODULES_PATH . '*/' . self::TEMPLATE_PATH); 202 // Theme views 203 $paths[] = WT_ROOT . Webtrees::THEMES_PATH . app()->make(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