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 self::$stacks[self::$stack][] = ob_get_clean(); 109 } 110 111 /** 112 * Implementation of Blade "stacks". 113 * 114 * @param string $stack 115 * 116 * @return string 117 */ 118 public static function stack(string $stack): string 119 { 120 $content = implode('', self::$stacks[$stack] ?? []); 121 122 self::$stacks[$stack] = []; 123 124 return $content; 125 } 126 127 /** 128 * Render a view. 129 * 130 * @return string 131 * @throws Throwable 132 */ 133 public function render(): string 134 { 135 $variables_for_view = $this->data + self::$shared_data; 136 extract($variables_for_view); 137 138 try { 139 ob_start(); 140 // Do not use require, so we can catch errors for missing files 141 include $this->getFilenameForView($this->name); 142 143 return ob_get_clean(); 144 } catch (Throwable $ex) { 145 ob_end_clean(); 146 throw $ex; 147 } 148 } 149 150 /** 151 * Allow a theme to override the default views. 152 * 153 * @param string $view_name 154 * 155 * @return string 156 * @throws Exception 157 */ 158 public function getFilenameForView($view_name): string 159 { 160 foreach ($this->paths() as $path) { 161 $view_file = $path . $view_name . self::TEMPLATE_EXTENSION; 162 163 if (is_file($view_file)) { 164 return $view_file; 165 } 166 } 167 168 throw new Exception('View not found: ' . e($view_name)); 169 } 170 171 /** 172 * Cerate and render a view in a single operation. 173 * 174 * @param string $name 175 * @param mixed[] $data 176 * 177 * @return string 178 */ 179 public static function make($name, $data = []): string 180 { 181 $view = new static($name, $data); 182 183 DebugBar::addView($name, $data); 184 185 return $view->render(); 186 } 187 188 /** 189 * @return string[] 190 */ 191 private function paths(): array 192 { 193 static $paths = []; 194 195 if (empty($paths)) { 196 // Module views 197 // @TODO - this includes disabled modules. 198 $paths = glob(WT_ROOT . Webtrees::MODULES_PATH . '*/' . self::TEMPLATE_PATH); 199 // Theme views 200 $paths[] = WT_ROOT . Webtrees::THEMES_PATH . app()->make(ModuleThemeInterface::class)->name() . '/' . self::TEMPLATE_PATH; 201 // Core views 202 $paths[] = WT_ROOT . self::TEMPLATE_PATH; 203 204 $paths = array_filter($paths, function (string $path): bool { 205 return is_dir($path); 206 }); 207 } 208 209 return $paths; 210 } 211} 212