1 <?php
2 
3 namespace dokuwiki\plugin\extension;
4 
5 class 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