xref: /dokuwiki/lib/plugins/extension/Notice.php (revision fe58309edafb067d90f3f40ef3d416100d558a04)
1<?php
2
3namespace dokuwiki\plugin\extension;
4
5class Notice
6{
7    public const INFO = 'info';
8    public const WARNING = 'warning';
9    public const ERROR = 'error';
10    public const SECURITY = 'security';
11
12    protected const ICONS = [
13        self::INFO => 'I',
14        self::WARNING => 'W',
15        self::ERROR => 'E',
16        self::SECURITY => 'S',
17    ];
18
19    protected $notices = [
20        self::INFO => [],
21        self::WARNING => [],
22        self::ERROR => [],
23        self::SECURITY => [],
24    ];
25
26    /** @var \helper_plugin_extension */
27    protected $helper;
28
29    /** @var Extension */
30    protected Extension $extension;
31
32    /**
33     * Not public, use list() instead
34     * @param Extension $extension
35     */
36    protected function __construct(Extension $extension)
37    {
38        $this->helper = plugin_load('helper', 'extension');
39        $this->extension = $extension;
40
41        $this->checkSecurity();
42        $this->checkURLChange();
43        $this->checkFolder();
44        $this->checkPHPVersion();
45        $this->checkDependencies();
46        $this->checkConflicts();
47        $this->checkUpdateMessage();
48        $this->checkPermissions();
49        $this->checkUnusedAuth();
50        $this->checkGit();
51    }
52
53    /**
54     * Get all notices for the extension
55     *
56     * @return string[][] array of notices grouped by type
57     */
58    public static function list(Extension $extension): array
59    {
60        $self = new self($extension);
61        return $self->notices;
62    }
63
64    /**
65     * Return the icon path for a notice type
66     *
67     * @param string $type The notice type constant
68     * @return string
69     */
70    public static function icon($type): string
71    {
72        if (!isset(self::ICONS[$type])) throw new \RuntimeException('Unknown notice type: ' . $type);
73        return __DIR__ . '/images/' . $type . '.svg';
74    }
75
76    /**
77     * Return the character symbol for a notice type used on CLI
78     *
79     * @param string $type The notice type constant
80     * @return string
81     */
82    public static function symbol($type): string
83    {
84        if (!isset(self::ICONS[$type])) throw new \RuntimeException('Unknown notice type: ' . $type);
85        return self::ICONS[$type][0] ?? '';
86    }
87
88    /**
89     * Access a language string
90     *
91     * @param string $msg
92     * @return string
93     */
94    protected function getLang($msg)
95    {
96        return $this->helper->getLang($msg);
97    }
98
99    /**
100     * Check that all dependencies are met
101     * @return void
102     */
103    protected function checkDependencies()
104    {
105        if (!$this->extension->isInstalled()) return;
106
107        $dependencies = $this->extension->getDependencyList();
108        $missing = [];
109        foreach ($dependencies as $dependency) {
110            try {
111                $dep = Extension::createFromId($dependency);
112            } catch (\RuntimeException) {
113                // ignore malformed dependency ids in the repository metadata
114                continue;
115            }
116            if (!$dep->isInstalled()) $missing[] = $dep;
117        }
118        if (!$missing) return;
119
120        $this->notices[self::ERROR][] = sprintf(
121            $this->getLang('missing_dependency'),
122            implode(', ', array_map(static fn(Extension $dep) => $dep->getId(true), $missing))
123        );
124    }
125
126    /**
127     * Check if installed dependencies are conflicting
128     * @return void
129     */
130    protected function checkConflicts()
131    {
132        $conflicts = $this->extension->getConflictList();
133        $found = [];
134        foreach ($conflicts as $conflict) {
135            try {
136                $dep = Extension::createFromId($conflict);
137            } catch (\RuntimeException) {
138                // ignore malformed conflict ids in the repository metadata
139                continue;
140            }
141            if ($dep->isInstalled()) $found[] = $dep;
142        }
143        if (!$found) return;
144
145        $this->notices[self::WARNING][] = sprintf(
146            $this->getLang('found_conflict'),
147            implode(', ', array_map(static fn(Extension $dep) => $dep->getId(true), $found))
148        );
149    }
150
151    /**
152     * Check for security issues
153     * @return void
154     */
155    protected function checkSecurity()
156    {
157        if ($issue = $this->extension->getSecurityIssue()) {
158            $this->notices[self::SECURITY][] = sprintf($this->getLang('security_issue'), $issue);
159        }
160        if ($issue = $this->extension->getSecurityWarning()) {
161            $this->notices[self::SECURITY][] = sprintf($this->getLang('security_warning'), $issue);
162        }
163    }
164
165    /**
166     * Check if the extension is installed in correct folder
167     * @return void
168     */
169    protected function checkFolder()
170    {
171        if (!$this->extension->isInWrongFolder()) return;
172
173        $this->notices[self::ERROR][] = sprintf(
174            $this->getLang('wrong_folder'),
175            basename($this->extension->getCurrentDir()),
176            basename($this->extension->getInstallDir())
177        );
178    }
179
180    /**
181     * Check PHP requirements
182     * @return void
183     */
184    protected function checkPHPVersion()
185    {
186        try {
187            Installer::ensurePhpCompatibility($this->extension);
188        } catch (\Exception $e) {
189            $this->notices[self::ERROR][] = $e->getMessage();
190        }
191    }
192
193    /**
194     * Check for update message
195     * @return void
196     */
197    protected function checkUpdateMessage()
198    {
199        // only display this for installed extensions
200        if (!$this->extension->isInstalled()) return;
201        if ($msg = $this->extension->getUpdateMessage()) {
202            $this->notices[self::WARNING][] = sprintf($this->getLang('update_message'), $msg);
203        }
204    }
205
206    /**
207     * Check for URL changes
208     * @return void
209     */
210    protected function checkURLChange()
211    {
212        if (!$this->extension->hasChangedURL()) return;
213        $this->notices[self::WARNING][] = sprintf(
214            $this->getLang('url_change'),
215            $this->extension->getDownloadURL(),
216            $this->extension->getManager()->getDownloadURL()
217        );
218    }
219
220    /**
221     * Check if the extension dir has the correct permissions to change
222     *
223     * @return void
224     */
225    protected function checkPermissions()
226    {
227        try {
228            Installer::ensurePermissions($this->extension);
229        } catch (\Exception $e) {
230            $this->notices[self::ERROR][] = $e->getMessage();
231        }
232    }
233
234    /**
235     * Hint about unused auth plugins
236     *
237     * @return void
238     */
239    protected function checkUnusedAuth()
240    {
241        global $conf;
242        if (
243            $this->extension->isEnabled() &&
244            in_array('Auth', $this->extension->getComponentTypes()) &&
245            $conf['authtype'] != $this->extension->getID()
246        ) {
247            $this->notices[self::INFO][] = $this->getLang('auth');
248        }
249    }
250
251    /**
252     * Hint about installations by git
253     *
254     * @return void
255     */
256    protected function checkGit()
257    {
258        if ($this->extension->isGitControlled()) {
259            $this->notices[self::INFO][] = $this->getLang('git');
260        }
261    }
262}
263