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