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