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