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 */
16namespace Fisharebest\Webtrees\Module;
17
18use Fisharebest\Webtrees\Auth;
19use Fisharebest\Webtrees\Database;
20use Fisharebest\Webtrees\Tree;
21
22/**
23 * Class AbstractModule - common functions for blocks
24 */
25abstract class AbstractModule
26{
27    /** @var string The directory where the module is installed */
28    private $directory;
29
30    /** @var string[] A cached copy of the module settings */
31    private $settings;
32
33    /** @var string For custom modules - optional (recommended) version number */
34    const CUSTOM_VERSION = '';
35
36    /** @var string For custom modules - link for support, upgrades, etc. */
37    const CUSTOM_WEBSITE = '';
38
39    /**
40     * Create a new module.
41     *
42     * @param string $directory Where is this module installed
43     */
44    public function __construct($directory)
45    {
46        $this->directory = $directory;
47    }
48
49    /**
50     * Get a block setting.
51     *
52     * @param int         $block_id
53     * @param string      $setting_name
54     * @param string|null $default_value
55     *
56     * @return null|string
57     */
58    public function getBlockSetting($block_id, $setting_name, $default_value = null)
59    {
60        $setting_value = Database::prepare(
61            "SELECT setting_value FROM `##block_setting` WHERE block_id = :block_id AND setting_name = :setting_name"
62        )->execute(array(
63            'block_id'     => $block_id,
64            'setting_name' => $setting_name,
65        ))->fetchOne();
66
67        return $setting_value === null ? $default_value : $setting_value;
68    }
69
70    /**
71     * Set a block setting.
72     *
73     * @param int         $block_id
74     * @param string      $setting_name
75     * @param string|null $setting_value
76     *
77     * @return $this
78     */
79    public function setBlockSetting($block_id, $setting_name, $setting_value)
80    {
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(array(
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(array(
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    {
124        // Returns one of: Auth::PRIV_HIDE, Auth::PRIV_PRIVATE, Auth::PRIV_USER, WT_PRIV_ADMIN
125        return Auth::PRIV_PRIVATE;
126    }
127
128    /**
129     * Provide a unique internal name for this module
130     *
131     * @return string
132     */
133    public function getName()
134    {
135        return basename($this->directory);
136    }
137
138    /**
139     * Load all the settings for the module into a cache.
140     *
141     * Since modules may have many settings, and will probably want to use
142     * lots of them, load them all at once and cache them.
143     */
144    private function loadAllSettings()
145    {
146        if ($this->settings === null) {
147            $this->settings = Database::prepare(
148                "SELECT setting_name, setting_value FROM `##module_setting` WHERE module_name = ?"
149            )->execute(array($this->getName()))->fetchAssoc();
150        }
151    }
152
153    /**
154     * Get a module setting. Return a default if the setting is not set.
155     *
156     * @param string $setting_name
157     * @param string $default
158     *
159     * @return string|null
160     */
161    public function getSetting($setting_name, $default = null)
162    {
163        $this->loadAllSettings();
164
165        if (array_key_exists($setting_name, $this->settings)) {
166            return $this->settings[$setting_name];
167        } else {
168            return $default;
169        }
170    }
171
172    /**
173     * Set a module setting.
174     *
175     * Since module settings are NOT NULL, setting a value to NULL will cause
176     * it to be deleted.
177     *
178     * @param string $setting_name
179     * @param string $setting_value
180     */
181    public function setSetting($setting_name, $setting_value)
182    {
183        $this->loadAllSettings();
184
185        if ($setting_value === null) {
186            Database::prepare(
187                "DELETE FROM `##module_setting` WHERE module_name = ? AND setting_name = ?"
188            )->execute(array($this->getName(), $setting_name));
189            unset($this->settings[$setting_name]);
190        } elseif (!array_key_exists($setting_name, $this->settings)) {
191            Database::prepare(
192                "INSERT INTO `##module_setting` (module_name, setting_name, setting_value) VALUES (?, ?, ?)"
193            )->execute(array($this->getName(), $setting_name, $setting_value));
194            $this->settings[$setting_name] = $setting_value;
195        } elseif ($setting_value != $this->settings[$setting_name]) {
196            Database::prepare(
197                "UPDATE `##module_setting` SET setting_value = ? WHERE module_name = ? AND setting_name = ?"
198            )->execute(array($setting_value, $this->getName(), $setting_name));
199            $this->settings[$setting_name] = $setting_value;
200        } else {
201            // Setting already exists, but with the same value - do nothing.
202        }
203    }
204
205    /**
206     * This is a general purpose hook, allowing modules to respond to routes
207     * of the form module.php?mod=FOO&mod_action=BAR
208     *
209     * @param string $mod_action
210     */
211    public function modAction($mod_action)
212    {
213    }
214
215    /**
216     * Get a the current access level for a module
217     *
218     * @param Tree   $tree
219     * @param string $component tab, block, menu, etc
220     *
221     * @return int
222     */
223    public function getAccessLevel(Tree $tree, $component)
224    {
225        $access_level = Database::prepare(
226            "SELECT access_level FROM `##module_privacy` WHERE gedcom_id = :gedcom_id AND module_name = :module_name AND component = :component"
227        )->execute(array(
228            'gedcom_id'   => $tree->getTreeId(),
229            'module_name' => $this->getName(),
230            'component'   => $component,
231        ))->fetchOne();
232
233        if ($access_level === null) {
234            return $this->defaultAccessLevel();
235        } else {
236            return (int) $access_level;
237        }
238    }
239}
240