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\Module; 19 20use Fisharebest\Webtrees\Auth; 21use Fisharebest\Webtrees\Database; 22use Fisharebest\Webtrees\Tree; 23use Symfony\Component\HttpFoundation\Response; 24 25/** 26 * Class AbstractModule - common functions for blocks 27 */ 28abstract class AbstractModule 29{ 30 /** @var string The directory where the module is installed */ 31 private $directory; 32 33 /** @var string[] A cached copy of the module settings */ 34 private $settings; 35 36 /** @var string For custom modules - optional (recommended) version number */ 37 const CUSTOM_VERSION = ''; 38 39 /** @var string For custom modules - link for support, upgrades, etc. */ 40 const CUSTOM_WEBSITE = ''; 41 42 protected $layout = 'layouts/default'; 43 44 /** 45 * Create a new module. 46 * 47 * @param string $directory Where is this module installed 48 */ 49 public function __construct($directory) 50 { 51 $this->directory = $directory; 52 } 53 54 /** 55 * Get a block setting. 56 * 57 * @param int $block_id 58 * @param string $setting_name 59 * @param string $default_value 60 * 61 * @return string 62 */ 63 public function getBlockSetting(int $block_id, string $setting_name, string $default_value = ''): string 64 { 65 $setting_value = Database::prepare( 66 "SELECT setting_value FROM `##block_setting` WHERE block_id = :block_id AND setting_name = :setting_name" 67 )->execute([ 68 'block_id' => $block_id, 69 'setting_name' => $setting_name, 70 ])->fetchOne(); 71 72 return $setting_value ?? $default_value; 73 } 74 75 /** 76 * Set a block setting. 77 * 78 * @param int $block_id 79 * @param string $setting_name 80 * @param string $setting_value 81 * 82 * @return $this 83 */ 84 public function setBlockSetting(int $block_id, string $setting_name, string $setting_value): self 85 { 86 Database::prepare( 87 "REPLACE INTO `##block_setting` (block_id, setting_name, setting_value) VALUES (:block_id, :setting_name, :setting_value)" 88 )->execute([ 89 'block_id' => $block_id, 90 'setting_name' => $setting_name, 91 'setting_value' => $setting_value, 92 ]); 93 94 return $this; 95 } 96 97 /** 98 * How should this module be labelled on tabs, menus, etc.? 99 * 100 * @return string 101 */ 102 abstract public function getTitle(): string; 103 104 /** 105 * A sentence describing what this module does. 106 * 107 * @return string 108 */ 109 abstract public function getDescription(): string; 110 111 /** 112 * What is the default access level for this module? 113 * 114 * Some modules are aimed at admins or managers, and are not generally shown to users. 115 * 116 * @return int 117 */ 118 public function defaultAccessLevel(): int 119 { 120 // Returns one of: Auth::PRIV_HIDE, Auth::PRIV_PRIVATE, Auth::PRIV_USER, WT_PRIV_ADMIN 121 return Auth::PRIV_PRIVATE; 122 } 123 124 /** 125 * Provide a unique internal name for this module 126 * 127 * @return string 128 */ 129 public function getName(): string 130 { 131 return basename($this->directory); 132 } 133 134 /** 135 * Load all the settings for the module into a cache. 136 * 137 * Since modules may have many settings, and will probably want to use 138 * lots of them, load them all at once and cache them. 139 * 140 * @return void 141 */ 142 private function loadAllSettings() 143 { 144 if ($this->settings === null) { 145 $this->settings = Database::prepare( 146 "SELECT setting_name, setting_value FROM `##module_setting` WHERE module_name = ?" 147 )->execute([$this->getName()])->fetchAssoc(); 148 } 149 } 150 151 /** 152 * Get a module setting. Return a default if the setting is not set. 153 * 154 * @param string $setting_name 155 * @param string $default 156 * 157 * @return string 158 */ 159 public function getPreference($setting_name, $default = '') 160 { 161 $this->loadAllSettings(); 162 163 if (array_key_exists($setting_name, $this->settings)) { 164 return $this->settings[$setting_name]; 165 } 166 167 return $default; 168 } 169 170 /** 171 * Set a module setting. 172 * 173 * Since module settings are NOT NULL, setting a value to NULL will cause 174 * it to be deleted. 175 * 176 * @param string $setting_name 177 * @param string $setting_value 178 * 179 * @return $this 180 */ 181 public function setPreference($setting_name, $setting_value): self 182 { 183 $this->loadAllSettings(); 184 185 if (!array_key_exists($setting_name, $this->settings)) { 186 Database::prepare( 187 "INSERT INTO `##module_setting` (module_name, setting_name, setting_value) VALUES (?, ?, ?)" 188 )->execute([ 189 $this->getName(), 190 $setting_name, 191 $setting_value, 192 ]); 193 194 $this->settings[$setting_name] = $setting_value; 195 } elseif ($setting_value !== $this->getPreference($setting_name)) { 196 Database::prepare( 197 "UPDATE `##module_setting` SET setting_value = ? WHERE module_name = ? AND setting_name = ?" 198 )->execute([ 199 $setting_value, 200 $this->getName(), 201 $setting_name, 202 ]); 203 204 $this->settings[$setting_name] = $setting_value; 205 } else { 206 // Setting already exists, but with the same value - do nothing. 207 } 208 209 return $this; 210 } 211 212 /** 213 * Get a the current access level for a module 214 * 215 * @param Tree $tree 216 * @param string $component tab, block, menu, etc 217 * 218 * @return int 219 */ 220 public function getAccessLevel(Tree $tree, $component) 221 { 222 $access_level = Database::prepare( 223 "SELECT access_level FROM `##module_privacy` WHERE gedcom_id = :gedcom_id AND module_name = :module_name AND component = :component" 224 )->execute([ 225 'gedcom_id' => $tree->getTreeId(), 226 'module_name' => $this->getName(), 227 'component' => $component, 228 ])->fetchOne(); 229 230 if ($access_level === null) { 231 return $this->defaultAccessLevel(); 232 } 233 234 return (int) $access_level; 235 } 236 237 /** 238 * Create a response object from a view. 239 * 240 * @param string $view_name 241 * @param mixed[] $view_data 242 * @param int $status 243 * 244 * @return Response 245 */ 246 protected function viewResponse($view_name, $view_data, $status = Response::HTTP_OK): Response 247 { 248 // Make the view's data available to the layout. 249 $layout_data = $view_data; 250 251 // Render the view 252 $layout_data['content'] = view($view_name, $view_data); 253 254 // Insert the view into the layout 255 $html = view($this->layout, $layout_data); 256 257 return new Response($html, $status); 258 } 259} 260