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            $dep = Extension::createFromId($dependency);
111            if (!$dep->isInstalled()) $missing[] = $dep;
112        }
113        if (!$missing) return;
114
115        $this->notices[self::ERROR][] = sprintf(
116            $this->getLang('missing_dependency'),
117            implode(', ', array_map(static fn(Extension $dep) => $dep->getId(true), $missing))
118        );
119    }
120
121    /**
122     * Check if installed dependencies are conflicting
123     * @return void
124     */
125    protected function checkConflicts()
126    {
127        $conflicts = $this->extension->getConflictList();
128        $found = [];
129        foreach ($conflicts as $conflict) {
130            $dep = Extension::createFromId($conflict);
131            if ($dep->isInstalled()) $found[] = $dep;
132        }
133        if (!$found) return;
134
135        $this->notices[self::WARNING][] = sprintf(
136            $this->getLang('found_conflict'),
137            implode(', ', array_map(static fn(Extension $dep) => $dep->getId(true), $found))
138        );
139    }
140
141    /**
142     * Check for security issues
143     * @return void
144     */
145    protected function checkSecurity()
146    {
147        if ($issue = $this->extension->getSecurityIssue()) {
148            $this->notices[self::SECURITY][] = sprintf($this->getLang('security_issue'), $issue);
149        }
150        if ($issue = $this->extension->getSecurityWarning()) {
151            $this->notices[self::SECURITY][] = sprintf($this->getLang('security_warning'), $issue);
152        }
153    }
154
155    /**
156     * Check if the extension is installed in correct folder
157     * @return void
158     */
159    protected function checkFolder()
160    {
161        if (!$this->extension->isInWrongFolder()) return;
162
163        $this->notices[self::ERROR][] = sprintf(
164            $this->getLang('wrong_folder'),
165            basename($this->extension->getCurrentDir()),
166            basename($this->extension->getInstallDir())
167        );
168    }
169
170    /**
171     * Check PHP requirements
172     * @return void
173     */
174    protected function checkPHPVersion()
175    {
176        try {
177            Installer::ensurePhpCompatibility($this->extension);
178        } catch (\Exception $e) {
179            $this->notices[self::ERROR][] = $e->getMessage();
180        }
181    }
182
183    /**
184     * Check for update message
185     * @return void
186     */
187    protected function checkUpdateMessage()
188    {
189        // only display this for installed extensions
190        if (!$this->extension->isInstalled()) return;
191        if ($msg = $this->extension->getUpdateMessage()) {
192            $this->notices[self::WARNING][] = sprintf($this->getLang('update_message'), $msg);
193        }
194    }
195
196    /**
197     * Check for URL changes
198     * @return void
199     */
200    protected function checkURLChange()
201    {
202        if (!$this->extension->hasChangedURL()) return;
203        $this->notices[self::WARNING][] = sprintf(
204            $this->getLang('url_change'),
205            $this->extension->getDownloadURL(),
206            $this->extension->getManager()->getDownloadURL()
207        );
208    }
209
210    /**
211     * Check if the extension dir has the correct permissions to change
212     *
213     * @return void
214     */
215    protected function checkPermissions()
216    {
217        try {
218            Installer::ensurePermissions($this->extension);
219        } catch (\Exception $e) {
220            $this->notices[self::ERROR][] = $e->getMessage();
221        }
222    }
223
224    /**
225     * Hint about unused auth plugins
226     *
227     * @return void
228     */
229    protected function checkUnusedAuth()
230    {
231        global $conf;
232        if (
233            $this->extension->isEnabled() &&
234            in_array('Auth', $this->extension->getComponentTypes()) &&
235            $conf['authtype'] != $this->extension->getID()
236        ) {
237            $this->notices[self::INFO][] = $this->getLang('auth');
238        }
239    }
240
241    /**
242     * Hint about installations by git
243     *
244     * @return void
245     */
246    protected function checkGit()
247    {
248        if ($this->extension->isGitControlled()) {
249            $this->notices[self::INFO][] = $this->getLang('git');
250        }
251    }
252}
253