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()->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