xref: /dokuwiki/lib/plugins/extension/GuiExtension.php (revision 4fd6a1d7ae34e34afb3c0bae47639222f884a1b5)
1*4fd6a1d7SAndreas Gohr<?php
2*4fd6a1d7SAndreas Gohr
3*4fd6a1d7SAndreas Gohrnamespace dokuwiki\plugin\extension;
4*4fd6a1d7SAndreas Gohr
5*4fd6a1d7SAndreas Gohrclass GuiExtension extends Gui
6*4fd6a1d7SAndreas Gohr{
7*4fd6a1d7SAndreas Gohr    const THUMB_WIDTH = 120;
8*4fd6a1d7SAndreas Gohr    const THUMB_HEIGHT = 70;
9*4fd6a1d7SAndreas Gohr
10*4fd6a1d7SAndreas Gohr
11*4fd6a1d7SAndreas Gohr    protected Extension $extension;
12*4fd6a1d7SAndreas Gohr
13*4fd6a1d7SAndreas Gohr    public function __construct(Extension $extension)
14*4fd6a1d7SAndreas Gohr    {
15*4fd6a1d7SAndreas Gohr        parent::__construct();
16*4fd6a1d7SAndreas Gohr        $this->extension = $extension;
17*4fd6a1d7SAndreas Gohr    }
18*4fd6a1d7SAndreas Gohr
19*4fd6a1d7SAndreas Gohr
20*4fd6a1d7SAndreas Gohr    public function render()
21*4fd6a1d7SAndreas Gohr    {
22*4fd6a1d7SAndreas Gohr
23*4fd6a1d7SAndreas Gohr        $classes = $this->getClasses();
24*4fd6a1d7SAndreas Gohr
25*4fd6a1d7SAndreas Gohr        $html = "<section class=\"$classes\">";
26*4fd6a1d7SAndreas Gohr        $html .= $this->thumbnail();
27*4fd6a1d7SAndreas Gohr        $html .= $this->popularity();
28*4fd6a1d7SAndreas Gohr        $html .= $this->info();
29*4fd6a1d7SAndreas Gohr        $html .= $this->notices();
30*4fd6a1d7SAndreas Gohr        $html .= $this->mainLinks();
31*4fd6a1d7SAndreas Gohr        $html .= $this->details();
32*4fd6a1d7SAndreas Gohr        $html .= $this->actions();
33*4fd6a1d7SAndreas Gohr
34*4fd6a1d7SAndreas Gohr
35*4fd6a1d7SAndreas Gohr        $html .= '</section>';
36*4fd6a1d7SAndreas Gohr
37*4fd6a1d7SAndreas Gohr        return $html;
38*4fd6a1d7SAndreas Gohr    }
39*4fd6a1d7SAndreas Gohr
40*4fd6a1d7SAndreas Gohr    // region sections
41*4fd6a1d7SAndreas Gohr
42*4fd6a1d7SAndreas Gohr    /**
43*4fd6a1d7SAndreas Gohr     * Get the link and image tag for the screenshot/thumbnail
44*4fd6a1d7SAndreas Gohr     *
45*4fd6a1d7SAndreas Gohr     * @return string The HTML code
46*4fd6a1d7SAndreas Gohr     */
47*4fd6a1d7SAndreas Gohr    protected function thumbnail()
48*4fd6a1d7SAndreas Gohr    {
49*4fd6a1d7SAndreas Gohr        $screen = $this->extension->getScreenshotURL();
50*4fd6a1d7SAndreas Gohr        $thumb = $this->extension->getThumbnailURL();
51*4fd6a1d7SAndreas Gohr
52*4fd6a1d7SAndreas Gohr        $link = [];
53*4fd6a1d7SAndreas Gohr        $img = [
54*4fd6a1d7SAndreas Gohr            'width' => self::THUMB_WIDTH,
55*4fd6a1d7SAndreas Gohr            'height' => self::THUMB_HEIGHT,
56*4fd6a1d7SAndreas Gohr            'alt' => '',
57*4fd6a1d7SAndreas Gohr        ];
58*4fd6a1d7SAndreas Gohr
59*4fd6a1d7SAndreas Gohr        if ($screen) {
60*4fd6a1d7SAndreas Gohr            $link = [
61*4fd6a1d7SAndreas Gohr                'href' => $screen,
62*4fd6a1d7SAndreas Gohr                'target' => '_blank',
63*4fd6a1d7SAndreas Gohr                'class' => 'extension_screenshot',
64*4fd6a1d7SAndreas Gohr                'title' => sprintf($this->getLang('screenshot'), $this->extension->getDisplayName())
65*4fd6a1d7SAndreas Gohr            ];
66*4fd6a1d7SAndreas Gohr
67*4fd6a1d7SAndreas Gohr            $img['src'] = $thumb;
68*4fd6a1d7SAndreas Gohr            $img['alt'] = $link['title'];
69*4fd6a1d7SAndreas Gohr        } elseif ($this->extension->isTemplate()) {
70*4fd6a1d7SAndreas Gohr            $img['src'] = DOKU_BASE . 'lib/plugins/extension/images/template.png';
71*4fd6a1d7SAndreas Gohr        } else {
72*4fd6a1d7SAndreas Gohr            $img['src'] = DOKU_BASE . 'lib/plugins/extension/images/plugin.png';
73*4fd6a1d7SAndreas Gohr        }
74*4fd6a1d7SAndreas Gohr
75*4fd6a1d7SAndreas Gohr        $html = '<div class="screenshot">';
76*4fd6a1d7SAndreas Gohr        if ($link) $html .= '<a ' . buildAttributes($link) . '>';
77*4fd6a1d7SAndreas Gohr        $html .= '<img ' . buildAttributes($img) . ' />';
78*4fd6a1d7SAndreas Gohr        if ($link) $html .= '</a>';
79*4fd6a1d7SAndreas Gohr        $html .= '</div>';
80*4fd6a1d7SAndreas Gohr
81*4fd6a1d7SAndreas Gohr        return $html;
82*4fd6a1d7SAndreas Gohr
83*4fd6a1d7SAndreas Gohr    }
84*4fd6a1d7SAndreas Gohr
85*4fd6a1d7SAndreas Gohr    /**
86*4fd6a1d7SAndreas Gohr     * The main information about the extension
87*4fd6a1d7SAndreas Gohr     *
88*4fd6a1d7SAndreas Gohr     * @return string
89*4fd6a1d7SAndreas Gohr     */
90*4fd6a1d7SAndreas Gohr    protected function info()
91*4fd6a1d7SAndreas Gohr    {
92*4fd6a1d7SAndreas Gohr        $html = '<h2>';
93*4fd6a1d7SAndreas Gohr        $html .= '<bdi>' . hsc($this->extension->getDisplayName()) . '</bdi>';
94*4fd6a1d7SAndreas Gohr        if($this->extension->isBundled()) {
95*4fd6a1d7SAndreas Gohr            $html .= ' <span class="version">' . hsc('<'.$this->getLang('status_bundled').'>') . '</span>';
96*4fd6a1d7SAndreas Gohr        } elseif($this->extension->getInstalledVersion()) {
97*4fd6a1d7SAndreas Gohr            $html .= ' <span class="version">' . hsc($this->extension->getInstalledVersion()) . '</span>';
98*4fd6a1d7SAndreas Gohr        }
99*4fd6a1d7SAndreas Gohr        $html .= '</h2>';
100*4fd6a1d7SAndreas Gohr
101*4fd6a1d7SAndreas Gohr        $html .= $this->author();
102*4fd6a1d7SAndreas Gohr
103*4fd6a1d7SAndreas Gohr        $html .= '<p>' . hsc($this->extension->getDescription()) . '</p>';
104*4fd6a1d7SAndreas Gohr
105*4fd6a1d7SAndreas Gohr        return $html;
106*4fd6a1d7SAndreas Gohr    }
107*4fd6a1d7SAndreas Gohr
108*4fd6a1d7SAndreas Gohr    /**
109*4fd6a1d7SAndreas Gohr     * Display the available notices for the extension
110*4fd6a1d7SAndreas Gohr     *
111*4fd6a1d7SAndreas Gohr     * @return string
112*4fd6a1d7SAndreas Gohr     */
113*4fd6a1d7SAndreas Gohr    protected function notices()
114*4fd6a1d7SAndreas Gohr    {
115*4fd6a1d7SAndreas Gohr        $notices = Notice::list($this->extension);
116*4fd6a1d7SAndreas Gohr
117*4fd6a1d7SAndreas Gohr        $html = '';
118*4fd6a1d7SAndreas Gohr        foreach ($notices as $type => $messages) {
119*4fd6a1d7SAndreas Gohr            foreach ($messages as $message) {
120*4fd6a1d7SAndreas Gohr                $message = hsc($message);
121*4fd6a1d7SAndreas Gohr                $message = preg_replace('/`([^`]+)`/', '<bdi>$1</bdi>', $message);
122*4fd6a1d7SAndreas Gohr                $html .= '<div class="msg ' . $type . '">' . $message . '</div>';
123*4fd6a1d7SAndreas Gohr            }
124*4fd6a1d7SAndreas Gohr        }
125*4fd6a1d7SAndreas Gohr        return $html;
126*4fd6a1d7SAndreas Gohr    }
127*4fd6a1d7SAndreas Gohr
128*4fd6a1d7SAndreas Gohr    /**
129*4fd6a1d7SAndreas Gohr     * Generate the link bar HTML code
130*4fd6a1d7SAndreas Gohr     *
131*4fd6a1d7SAndreas Gohr     * @return string The HTML code
132*4fd6a1d7SAndreas Gohr     */
133*4fd6a1d7SAndreas Gohr    public function mainLinks()
134*4fd6a1d7SAndreas Gohr    {
135*4fd6a1d7SAndreas Gohr        $html = '<div class="linkbar">';
136*4fd6a1d7SAndreas Gohr
137*4fd6a1d7SAndreas Gohr
138*4fd6a1d7SAndreas Gohr        $homepage = $this->extension->getURL();
139*4fd6a1d7SAndreas Gohr        if ($homepage) {
140*4fd6a1d7SAndreas Gohr            $params = $this->prepareLinkAttributes($homepage, 'homepage');
141*4fd6a1d7SAndreas Gohr            $html .= ' <a ' . buildAttributes($params, true) . '>' . $this->getLang('homepage_link') . '</a>';
142*4fd6a1d7SAndreas Gohr        }
143*4fd6a1d7SAndreas Gohr
144*4fd6a1d7SAndreas Gohr        $bugtracker = $this->extension->getBugtrackerURL();
145*4fd6a1d7SAndreas Gohr        if ($bugtracker) {
146*4fd6a1d7SAndreas Gohr            $params = $this->prepareLinkAttributes($bugtracker, 'bugs');
147*4fd6a1d7SAndreas Gohr            $html .= ' <a ' . buildAttributes($params, true) . '>' . $this->getLang('bugs_features') . '</a>';
148*4fd6a1d7SAndreas Gohr        }
149*4fd6a1d7SAndreas Gohr
150*4fd6a1d7SAndreas Gohr        if ($this->extension->getDonationURL()) {
151*4fd6a1d7SAndreas Gohr            $params = $this->prepareLinkAttributes($this->extension->getDonationURL(), 'donate');
152*4fd6a1d7SAndreas Gohr            $html .= ' <a ' . buildAttributes($params, true) . '>' . $this->getLang('donate_action') . '</a>';
153*4fd6a1d7SAndreas Gohr        }
154*4fd6a1d7SAndreas Gohr
155*4fd6a1d7SAndreas Gohr
156*4fd6a1d7SAndreas Gohr
157*4fd6a1d7SAndreas Gohr        $html .= '</div>';
158*4fd6a1d7SAndreas Gohr
159*4fd6a1d7SAndreas Gohr        return $html;
160*4fd6a1d7SAndreas Gohr    }
161*4fd6a1d7SAndreas Gohr
162*4fd6a1d7SAndreas Gohr    /**
163*4fd6a1d7SAndreas Gohr     * Create the details section
164*4fd6a1d7SAndreas Gohr     *
165*4fd6a1d7SAndreas Gohr     * @return string
166*4fd6a1d7SAndreas Gohr     */
167*4fd6a1d7SAndreas Gohr    protected function details()
168*4fd6a1d7SAndreas Gohr    {
169*4fd6a1d7SAndreas Gohr        $html = '<details>';
170*4fd6a1d7SAndreas Gohr        $html .= '<summary>' . 'FIXME label' . '</summary>';
171*4fd6a1d7SAndreas Gohr
172*4fd6a1d7SAndreas Gohr
173*4fd6a1d7SAndreas Gohr        $default = $this->getLang('unknown');
174*4fd6a1d7SAndreas Gohr        $list = [];
175*4fd6a1d7SAndreas Gohr
176*4fd6a1d7SAndreas Gohr        if (!$this->extension->isBundled()) {
177*4fd6a1d7SAndreas Gohr            $list['downloadurl'] = $this->shortlink($this->extension->getDownloadURL(), 'download', $default);
178*4fd6a1d7SAndreas Gohr            $list['repository'] = $this->shortlink($this->extension->getSourcerepoURL(), 'repo', $default);
179*4fd6a1d7SAndreas Gohr        }
180*4fd6a1d7SAndreas Gohr
181*4fd6a1d7SAndreas Gohr        if ($this->extension->isInstalled()) {
182*4fd6a1d7SAndreas Gohr            if ($this->extension->isBundled()) {
183*4fd6a1d7SAndreas Gohr                $list['installed_version'] = $this->getLang('status_bundled');
184*4fd6a1d7SAndreas Gohr            } else {
185*4fd6a1d7SAndreas Gohr                if ($this->extension->getInstalledVersion()) {
186*4fd6a1d7SAndreas Gohr                    $list['installed_version'] = hsc($this->extension->getInstalledVersion());
187*4fd6a1d7SAndreas Gohr                }
188*4fd6a1d7SAndreas Gohr                if (!$this->extension->isBundled()) {
189*4fd6a1d7SAndreas Gohr                    $updateDate = $this->extension->getManager()->getLastUpdate();
190*4fd6a1d7SAndreas Gohr                    $list['install_date'] = $updateDate ? hsc($updateDate) : $default;
191*4fd6a1d7SAndreas Gohr                }
192*4fd6a1d7SAndreas Gohr            }
193*4fd6a1d7SAndreas Gohr        }
194*4fd6a1d7SAndreas Gohr
195*4fd6a1d7SAndreas Gohr        if (!$this->extension->isInstalled() || $this->extension->isUpdateAvailable()) {
196*4fd6a1d7SAndreas Gohr            $list['available_version'] = $this->extension->getLastUpdate()
197*4fd6a1d7SAndreas Gohr                ? hsc($this->extension->getLastUpdate())
198*4fd6a1d7SAndreas Gohr                : $default;
199*4fd6a1d7SAndreas Gohr        }
200*4fd6a1d7SAndreas Gohr
201*4fd6a1d7SAndreas Gohr
202*4fd6a1d7SAndreas Gohr        if (!$this->extension->isBundled() && $this->extension->getCompatibleVersions()) {
203*4fd6a1d7SAndreas Gohr            $list['compatible'] = join(', ', array_map(
204*4fd6a1d7SAndreas Gohr                function ($date, $version) {
205*4fd6a1d7SAndreas Gohr                    return '<bdi>' . $version['label'] . ' (' . $date . ')</bdi>';
206*4fd6a1d7SAndreas Gohr                },
207*4fd6a1d7SAndreas Gohr                array_keys($this->extension->getCompatibleVersions()),
208*4fd6a1d7SAndreas Gohr                array_values($this->extension->getCompatibleVersions())
209*4fd6a1d7SAndreas Gohr            ));
210*4fd6a1d7SAndreas Gohr        }
211*4fd6a1d7SAndreas Gohr
212*4fd6a1d7SAndreas Gohr        $tags = $this->extension->getTags();
213*4fd6a1d7SAndreas Gohr        if ($tags) {
214*4fd6a1d7SAndreas Gohr            $list['tags'] = join(', ', array_map(function ($tag) {
215*4fd6a1d7SAndreas Gohr                $url = $this->tabURL('search', ['q' => 'tag:' . $tag]);
216*4fd6a1d7SAndreas Gohr                return '<bdi><a href="' . $url . '">' . hsc($tag) . '</a></bdi>';
217*4fd6a1d7SAndreas Gohr            }, $tags));
218*4fd6a1d7SAndreas Gohr        }
219*4fd6a1d7SAndreas Gohr
220*4fd6a1d7SAndreas Gohr        if ($this->extension->getDependencyList()) {
221*4fd6a1d7SAndreas Gohr            $list['depends'] = $this->linkExtensions($this->extension->getDependencyList());
222*4fd6a1d7SAndreas Gohr        }
223*4fd6a1d7SAndreas Gohr
224*4fd6a1d7SAndreas Gohr        if ($this->extension->getSimilarList()) {
225*4fd6a1d7SAndreas Gohr            $list['similar'] = $this->linkExtensions($this->extension->getSimilarList());
226*4fd6a1d7SAndreas Gohr        }
227*4fd6a1d7SAndreas Gohr
228*4fd6a1d7SAndreas Gohr        if ($this->extension->getConflictList()) {
229*4fd6a1d7SAndreas Gohr            $list['conflicts'] = $this->linkExtensions($this->extension->getConflictList());
230*4fd6a1d7SAndreas Gohr        }
231*4fd6a1d7SAndreas Gohr
232*4fd6a1d7SAndreas Gohr        foreach ($list as $key => $value) {
233*4fd6a1d7SAndreas Gohr            $html .= '<dt>' . $this->getLang($key) . '</dt>';
234*4fd6a1d7SAndreas Gohr            $html .= '<dd>' . $value . '</dd>';
235*4fd6a1d7SAndreas Gohr        }
236*4fd6a1d7SAndreas Gohr
237*4fd6a1d7SAndreas Gohr        $html .= '</details>';
238*4fd6a1d7SAndreas Gohr        return $html;
239*4fd6a1d7SAndreas Gohr    }
240*4fd6a1d7SAndreas Gohr
241*4fd6a1d7SAndreas Gohr    /**
242*4fd6a1d7SAndreas Gohr     * Generate a link to the author of the extension
243*4fd6a1d7SAndreas Gohr     *
244*4fd6a1d7SAndreas Gohr     * @return string The HTML code of the link
245*4fd6a1d7SAndreas Gohr     */
246*4fd6a1d7SAndreas Gohr    protected function author()
247*4fd6a1d7SAndreas Gohr    {
248*4fd6a1d7SAndreas Gohr        if (!$this->extension->getAuthor()) {
249*4fd6a1d7SAndreas Gohr            return '<em class="author">' . $this->getLang('unknown_author') . '</em>';
250*4fd6a1d7SAndreas Gohr        }
251*4fd6a1d7SAndreas Gohr
252*4fd6a1d7SAndreas Gohr        $mailid = $this->extension->getEmailID();
253*4fd6a1d7SAndreas Gohr        if ($mailid) {
254*4fd6a1d7SAndreas Gohr            $url = $this->tabURL('search', ['q' => 'authorid:' . $mailid]);
255*4fd6a1d7SAndreas Gohr            $html = '<a href="' . $url . '" class="author" title="' . $this->getLang('author_hint') . '" >' .
256*4fd6a1d7SAndreas Gohr                '<img src="//www.gravatar.com/avatar/' . $mailid .
257*4fd6a1d7SAndreas Gohr                '?s=60&amp;d=mm" width="20" height="20" alt="" /> ' .
258*4fd6a1d7SAndreas Gohr                hsc($this->extension->getAuthor()) . '</a>';
259*4fd6a1d7SAndreas Gohr        } else {
260*4fd6a1d7SAndreas Gohr            $html = '<span class="author">' . hsc($this->extension->getAuthor()) . '</span>';
261*4fd6a1d7SAndreas Gohr        }
262*4fd6a1d7SAndreas Gohr        return '<bdi>' . $html . '</bdi>';
263*4fd6a1d7SAndreas Gohr    }
264*4fd6a1d7SAndreas Gohr
265*4fd6a1d7SAndreas Gohr    /**
266*4fd6a1d7SAndreas Gohr     * The popularity bar
267*4fd6a1d7SAndreas Gohr     *
268*4fd6a1d7SAndreas Gohr     * @return string
269*4fd6a1d7SAndreas Gohr     */
270*4fd6a1d7SAndreas Gohr    protected function popularity()
271*4fd6a1d7SAndreas Gohr    {
272*4fd6a1d7SAndreas Gohr        $popularity = $this->extension->getPopularity();
273*4fd6a1d7SAndreas Gohr        if (!$popularity) return '';
274*4fd6a1d7SAndreas Gohr        if ($this->extension->isBundled()) return '';
275*4fd6a1d7SAndreas Gohr
276*4fd6a1d7SAndreas Gohr        $popularityText = sprintf($this->getLang('popularity'), round($popularity * 100, 2));
277*4fd6a1d7SAndreas Gohr        return '<div class="popularity" title="' . $popularityText . '">' .
278*4fd6a1d7SAndreas Gohr            '<div style="width: ' . ($popularity * 100) . '%;">' .
279*4fd6a1d7SAndreas Gohr            '<span class="a11y">' . $popularityText . '</span>' .
280*4fd6a1d7SAndreas Gohr            '</div></div>';
281*4fd6a1d7SAndreas Gohr
282*4fd6a1d7SAndreas Gohr    }
283*4fd6a1d7SAndreas Gohr
284*4fd6a1d7SAndreas Gohr    protected function actions()
285*4fd6a1d7SAndreas Gohr    {
286*4fd6a1d7SAndreas Gohr        global $conf;
287*4fd6a1d7SAndreas Gohr
288*4fd6a1d7SAndreas Gohr        $html = '';
289*4fd6a1d7SAndreas Gohr        $actions = [];
290*4fd6a1d7SAndreas Gohr        $errors = [];
291*4fd6a1d7SAndreas Gohr
292*4fd6a1d7SAndreas Gohr        // show the available version if there is one
293*4fd6a1d7SAndreas Gohr        if ($this->extension->getDownloadURL() && $this->extension->getLastUpdate()) {
294*4fd6a1d7SAndreas Gohr            $html .= ' <span class="version">' . $this->getLang('available_version') . ' ' .
295*4fd6a1d7SAndreas Gohr                hsc($this->extension->getLastUpdate()) . '</span>';
296*4fd6a1d7SAndreas Gohr        }
297*4fd6a1d7SAndreas Gohr
298*4fd6a1d7SAndreas Gohr        // gather available actions and possible errors to show
299*4fd6a1d7SAndreas Gohr        try {
300*4fd6a1d7SAndreas Gohr            Installer::ensurePermissions($this->extension);
301*4fd6a1d7SAndreas Gohr
302*4fd6a1d7SAndreas Gohr            if ($this->extension->isInstalled()) {
303*4fd6a1d7SAndreas Gohr
304*4fd6a1d7SAndreas Gohr                if (!$this->extension->isProtected()) $actions[] = 'uninstall';
305*4fd6a1d7SAndreas Gohr                if ($this->extension->getDownloadURL()) {
306*4fd6a1d7SAndreas Gohr                    $actions[] = $this->extension->isUpdateAvailable() ? 'update' : 'reinstall';
307*4fd6a1d7SAndreas Gohr
308*4fd6a1d7SAndreas Gohr                    if ($this->extension->isGitControlled()) {
309*4fd6a1d7SAndreas Gohr                        $errors[] = $this->getLang('git');
310*4fd6a1d7SAndreas Gohr                    }
311*4fd6a1d7SAndreas Gohr                }
312*4fd6a1d7SAndreas Gohr
313*4fd6a1d7SAndreas Gohr                if (!$this->extension->isProtected() && !$this->extension->isTemplate()) { // no enable/disable for templates
314*4fd6a1d7SAndreas Gohr                    $actions[] = $this->extension->isEnabled() ? 'disable' : 'enable';
315*4fd6a1d7SAndreas Gohr
316*4fd6a1d7SAndreas Gohr                    if (
317*4fd6a1d7SAndreas Gohr                        $this->extension->isEnabled() &&
318*4fd6a1d7SAndreas Gohr                        in_array('Auth', $this->extension->getComponentTypes()) &&
319*4fd6a1d7SAndreas Gohr                        $conf['authtype'] != $this->extension->getID()
320*4fd6a1d7SAndreas Gohr                    ) {
321*4fd6a1d7SAndreas Gohr                        $errors[] = $this->getLang('auth');
322*4fd6a1d7SAndreas Gohr                    }
323*4fd6a1d7SAndreas Gohr                }
324*4fd6a1d7SAndreas Gohr            } else {
325*4fd6a1d7SAndreas Gohr                if ($this->extension->getDownloadURL()) {
326*4fd6a1d7SAndreas Gohr                    $actions[] = 'install';
327*4fd6a1d7SAndreas Gohr                }
328*4fd6a1d7SAndreas Gohr            }
329*4fd6a1d7SAndreas Gohr        } catch (\Exception $e) {
330*4fd6a1d7SAndreas Gohr            $errors[] = $e->getMessage();
331*4fd6a1d7SAndreas Gohr        }
332*4fd6a1d7SAndreas Gohr
333*4fd6a1d7SAndreas Gohr        foreach ($actions as $action) {
334*4fd6a1d7SAndreas Gohr            $html .= '<button name="fn[' . $action . '][' . $this->extension->getID() . ']" class="button" type="submit">' .
335*4fd6a1d7SAndreas Gohr                $this->getLang('btn_' . $action) . '</button>';
336*4fd6a1d7SAndreas Gohr        }
337*4fd6a1d7SAndreas Gohr
338*4fd6a1d7SAndreas Gohr        foreach ($errors as $error) {
339*4fd6a1d7SAndreas Gohr            $html .= '<div class="msg error">' . hsc($error) . '</div>';
340*4fd6a1d7SAndreas Gohr        }
341*4fd6a1d7SAndreas Gohr
342*4fd6a1d7SAndreas Gohr        return $html;
343*4fd6a1d7SAndreas Gohr    }
344*4fd6a1d7SAndreas Gohr
345*4fd6a1d7SAndreas Gohr
346*4fd6a1d7SAndreas Gohr    // endregion
347*4fd6a1d7SAndreas Gohr    // region utility functions
348*4fd6a1d7SAndreas Gohr
349*4fd6a1d7SAndreas Gohr    /**
350*4fd6a1d7SAndreas Gohr     * Create the classes representing the state of the extension
351*4fd6a1d7SAndreas Gohr     *
352*4fd6a1d7SAndreas Gohr     * @return string
353*4fd6a1d7SAndreas Gohr     */
354*4fd6a1d7SAndreas Gohr    protected function getClasses()
355*4fd6a1d7SAndreas Gohr    {
356*4fd6a1d7SAndreas Gohr        $classes = ['extension', $this->extension->getType()];
357*4fd6a1d7SAndreas Gohr        if ($this->extension->isInstalled()) $classes[] = 'installed';
358*4fd6a1d7SAndreas Gohr        if ($this->extension->isUpdateAvailable()) $classes[] = 'update';
359*4fd6a1d7SAndreas Gohr        $classes[] = $this->extension->isEnabled() ? 'enabled' : 'disabled';
360*4fd6a1d7SAndreas Gohr        return implode(' ', $classes);
361*4fd6a1d7SAndreas Gohr    }
362*4fd6a1d7SAndreas Gohr
363*4fd6a1d7SAndreas Gohr    /**
364*4fd6a1d7SAndreas Gohr     * Create an attributes array for a link
365*4fd6a1d7SAndreas Gohr     *
366*4fd6a1d7SAndreas Gohr     * Handles interwiki links to dokuwiki.org
367*4fd6a1d7SAndreas Gohr     *
368*4fd6a1d7SAndreas Gohr     * @param string $url The URL to link to
369*4fd6a1d7SAndreas Gohr     * @param string $class Additional classes to add
370*4fd6a1d7SAndreas Gohr     * @return array
371*4fd6a1d7SAndreas Gohr     */
372*4fd6a1d7SAndreas Gohr    protected function prepareLinkAttributes($url, $class)
373*4fd6a1d7SAndreas Gohr    {
374*4fd6a1d7SAndreas Gohr        global $conf;
375*4fd6a1d7SAndreas Gohr
376*4fd6a1d7SAndreas Gohr        $attributes = [
377*4fd6a1d7SAndreas Gohr            'href' => $url,
378*4fd6a1d7SAndreas Gohr            'class' => 'urlextern',
379*4fd6a1d7SAndreas Gohr            'target' => $conf['target']['extern'],
380*4fd6a1d7SAndreas Gohr            'rel' => 'noopener',
381*4fd6a1d7SAndreas Gohr            'title' => $url,
382*4fd6a1d7SAndreas Gohr        ];
383*4fd6a1d7SAndreas Gohr
384*4fd6a1d7SAndreas Gohr        if ($conf['relnofollow']) {
385*4fd6a1d7SAndreas Gohr            $attributes['rel'] .= ' ugc nofollow';
386*4fd6a1d7SAndreas Gohr        }
387*4fd6a1d7SAndreas Gohr
388*4fd6a1d7SAndreas Gohr        if (preg_match('/^https?:\/\/(www\.)?dokuwiki\.org\//i', $url)) {
389*4fd6a1d7SAndreas Gohr            $attributes['class'] = 'interwiki iw_doku';
390*4fd6a1d7SAndreas Gohr            $attributes['target'] = $conf['target']['interwiki'];
391*4fd6a1d7SAndreas Gohr            $attributes['rel'] = '';
392*4fd6a1d7SAndreas Gohr        }
393*4fd6a1d7SAndreas Gohr
394*4fd6a1d7SAndreas Gohr        $attributes['class'] .= ' ' . $class;
395*4fd6a1d7SAndreas Gohr        return $attributes;
396*4fd6a1d7SAndreas Gohr    }
397*4fd6a1d7SAndreas Gohr
398*4fd6a1d7SAndreas Gohr    /**
399*4fd6a1d7SAndreas Gohr     * Create a link from the given URL
400*4fd6a1d7SAndreas Gohr     *
401*4fd6a1d7SAndreas Gohr     * Shortens the URL for display
402*4fd6a1d7SAndreas Gohr     *
403*4fd6a1d7SAndreas Gohr     * @param string $url
404*4fd6a1d7SAndreas Gohr     * @param string $class Additional classes to add
405*4fd6a1d7SAndreas Gohr     * @param string $fallback If URL is empty return this fallback
406*4fd6a1d7SAndreas Gohr     * @return string  HTML link
407*4fd6a1d7SAndreas Gohr     */
408*4fd6a1d7SAndreas Gohr    protected function shortlink($url, $class, $fallback = '')
409*4fd6a1d7SAndreas Gohr    {
410*4fd6a1d7SAndreas Gohr        if (!$url) return hsc($fallback);
411*4fd6a1d7SAndreas Gohr
412*4fd6a1d7SAndreas Gohr        $link = parse_url($url);
413*4fd6a1d7SAndreas Gohr        $base = $link['host'];
414*4fd6a1d7SAndreas Gohr        if (!empty($link['port'])) $base .= $base . ':' . $link['port'];
415*4fd6a1d7SAndreas Gohr        $long = $link['path'];
416*4fd6a1d7SAndreas Gohr        if (!empty($link['query'])) $long .= $link['query'];
417*4fd6a1d7SAndreas Gohr
418*4fd6a1d7SAndreas Gohr        $name = shorten($base, $long, 55);
419*4fd6a1d7SAndreas Gohr
420*4fd6a1d7SAndreas Gohr        $params = $this->prepareLinkAttributes($url, $class);
421*4fd6a1d7SAndreas Gohr        $html = '<a ' . buildAttributes($params, true) . '>' . hsc($name) . '</a>';
422*4fd6a1d7SAndreas Gohr        return $html;
423*4fd6a1d7SAndreas Gohr    }
424*4fd6a1d7SAndreas Gohr
425*4fd6a1d7SAndreas Gohr    /**
426*4fd6a1d7SAndreas Gohr     * Generate a list of links for extensions
427*4fd6a1d7SAndreas Gohr     *
428*4fd6a1d7SAndreas Gohr     * Links to the search tab with the extension name
429*4fd6a1d7SAndreas Gohr     *
430*4fd6a1d7SAndreas Gohr     * @param array $extensions The extension names
431*4fd6a1d7SAndreas Gohr     * @return string The HTML code
432*4fd6a1d7SAndreas Gohr     */
433*4fd6a1d7SAndreas Gohr    public function linkExtensions($extensions)
434*4fd6a1d7SAndreas Gohr    {
435*4fd6a1d7SAndreas Gohr        $html = '';
436*4fd6a1d7SAndreas Gohr        foreach ($extensions as $link) {
437*4fd6a1d7SAndreas Gohr            $html .= '<bdi><a href="' .
438*4fd6a1d7SAndreas Gohr                $this->tabURL('search', ['q' => 'ext:' . $link]) . '">' .
439*4fd6a1d7SAndreas Gohr                hsc($link) . '</a></bdi>, ';
440*4fd6a1d7SAndreas Gohr        }
441*4fd6a1d7SAndreas Gohr        return rtrim($html, ', ');
442*4fd6a1d7SAndreas Gohr    }
443*4fd6a1d7SAndreas Gohr
444*4fd6a1d7SAndreas Gohr    // endregion
445*4fd6a1d7SAndreas Gohr
446*4fd6a1d7SAndreas Gohr
447*4fd6a1d7SAndreas Gohr    /**
448*4fd6a1d7SAndreas Gohr     * Extension main description
449*4fd6a1d7SAndreas Gohr     *
450*4fd6a1d7SAndreas Gohr     * @return string The HTML code
451*4fd6a1d7SAndreas Gohr     */
452*4fd6a1d7SAndreas Gohr    public function makeLegend()
453*4fd6a1d7SAndreas Gohr    {
454*4fd6a1d7SAndreas Gohr        $html = '<div>';
455*4fd6a1d7SAndreas Gohr        $html .= '<h2>';
456*4fd6a1d7SAndreas Gohr        $html .= sprintf(
457*4fd6a1d7SAndreas Gohr            $this->getLang('extensionby'),
458*4fd6a1d7SAndreas Gohr            '<bdi>' . hsc($this->extension->getDisplayName()) . '</bdi>',
459*4fd6a1d7SAndreas Gohr            $this->author()
460*4fd6a1d7SAndreas Gohr        );
461*4fd6a1d7SAndreas Gohr        $html .= '</h2>' . DOKU_LF;
462*4fd6a1d7SAndreas Gohr
463*4fd6a1d7SAndreas Gohr        $html .= $this->makeScreenshot();
464*4fd6a1d7SAndreas Gohr
465*4fd6a1d7SAndreas Gohr        $popularity = $this->extension->getPopularity();
466*4fd6a1d7SAndreas Gohr        if ($popularity !== false && !$this->extension->isBundled()) {
467*4fd6a1d7SAndreas Gohr            $popularityText = sprintf($this->getLang('popularity'), round($popularity * 100, 2));
468*4fd6a1d7SAndreas Gohr            $html .= '<div class="popularity" title="' . $popularityText . '">' .
469*4fd6a1d7SAndreas Gohr                '<div style="width: ' . ($popularity * 100) . '%;">' .
470*4fd6a1d7SAndreas Gohr                '<span class="a11y">' . $popularityText . '</span>' .
471*4fd6a1d7SAndreas Gohr                '</div></div>' . DOKU_LF;
472*4fd6a1d7SAndreas Gohr        }
473*4fd6a1d7SAndreas Gohr
474*4fd6a1d7SAndreas Gohr        if ($this->extension->getDescription()) {
475*4fd6a1d7SAndreas Gohr            $html .= '<p><bdi>';
476*4fd6a1d7SAndreas Gohr            $html .= hsc($this->extension->getDescription()) . ' ';
477*4fd6a1d7SAndreas Gohr            $html .= '</bdi></p>' . DOKU_LF;
478*4fd6a1d7SAndreas Gohr        }
479*4fd6a1d7SAndreas Gohr
480*4fd6a1d7SAndreas Gohr        $html .= $this->makeLinkbar();
481*4fd6a1d7SAndreas Gohr        $html .= $this->makeInfo();
482*4fd6a1d7SAndreas Gohr        $html .= $this->makeNoticeArea();
483*4fd6a1d7SAndreas Gohr        $html .= '</div>' . DOKU_LF;
484*4fd6a1d7SAndreas Gohr        return $html;
485*4fd6a1d7SAndreas Gohr    }
486*4fd6a1d7SAndreas Gohr
487*4fd6a1d7SAndreas Gohr
488*4fd6a1d7SAndreas Gohr    /**
489*4fd6a1d7SAndreas Gohr     * Plugin/template details
490*4fd6a1d7SAndreas Gohr     *
491*4fd6a1d7SAndreas Gohr     * @return string The HTML code
492*4fd6a1d7SAndreas Gohr     */
493*4fd6a1d7SAndreas Gohr    public
494*4fd6a1d7SAndreas Gohr    function makeInfo()
495*4fd6a1d7SAndreas Gohr    {
496*4fd6a1d7SAndreas Gohr        $default = $this->getLang('unknown');
497*4fd6a1d7SAndreas Gohr
498*4fd6a1d7SAndreas Gohr
499*4fd6a1d7SAndreas Gohr        $list = [];
500*4fd6a1d7SAndreas Gohr
501*4fd6a1d7SAndreas Gohr        $list['status'] = $this->makeStatus();
502*4fd6a1d7SAndreas Gohr
503*4fd6a1d7SAndreas Gohr
504*4fd6a1d7SAndreas Gohr        if ($this->extension->getDonationURL()) {
505*4fd6a1d7SAndreas Gohr            $list['donate'] = '<a href="' . $this->extension->getDonationURL() . '" class="donate">' .
506*4fd6a1d7SAndreas Gohr                $this->getLang('donate_action') . '</a>';
507*4fd6a1d7SAndreas Gohr        }
508*4fd6a1d7SAndreas Gohr
509*4fd6a1d7SAndreas Gohr        if (!$this->extension->isBundled()) {
510*4fd6a1d7SAndreas Gohr            $list['downloadurl'] = $this->shortlink($this->extension->getDownloadURL(), $default);
511*4fd6a1d7SAndreas Gohr            $list['repository'] = $this->shortlink($this->extension->getSourcerepoURL(), $default);
512*4fd6a1d7SAndreas Gohr        }
513*4fd6a1d7SAndreas Gohr
514*4fd6a1d7SAndreas Gohr        if ($this->extension->isInstalled()) {
515*4fd6a1d7SAndreas Gohr            if ($this->extension->getInstalledVersion()) {
516*4fd6a1d7SAndreas Gohr                $list['installed_version'] = hsc($this->extension->getInstalledVersion());
517*4fd6a1d7SAndreas Gohr            }
518*4fd6a1d7SAndreas Gohr            if (!$this->extension->isBundled()) {
519*4fd6a1d7SAndreas Gohr                $updateDate = $this->extension->getManager()->getLastUpdate();
520*4fd6a1d7SAndreas Gohr                $list['install_date'] = $updateDate ? hsc($updateDate) : $default;
521*4fd6a1d7SAndreas Gohr            }
522*4fd6a1d7SAndreas Gohr        }
523*4fd6a1d7SAndreas Gohr
524*4fd6a1d7SAndreas Gohr        if (!$this->extension->isInstalled() || $this->extension->isUpdateAvailable()) {
525*4fd6a1d7SAndreas Gohr            $list['available_version'] = $this->extension->getLastUpdate()
526*4fd6a1d7SAndreas Gohr                ? hsc($this->extension->getLastUpdate())
527*4fd6a1d7SAndreas Gohr                : $default;
528*4fd6a1d7SAndreas Gohr        }
529*4fd6a1d7SAndreas Gohr
530*4fd6a1d7SAndreas Gohr
531*4fd6a1d7SAndreas Gohr        if (!$this->extension->isBundled() && $this->extension->getCompatibleVersions()) {
532*4fd6a1d7SAndreas Gohr            $html .= '<dt>' . $this->getLang('compatible') . '</dt>';
533*4fd6a1d7SAndreas Gohr            $html .= '<dd>';
534*4fd6a1d7SAndreas Gohr            foreach ($this->extension->getCompatibleVersions() as $date => $version) {
535*4fd6a1d7SAndreas Gohr                $html .= '<bdi>' . $version['label'] . ' (' . $date . ')</bdi>, ';
536*4fd6a1d7SAndreas Gohr            }
537*4fd6a1d7SAndreas Gohr            $html = rtrim($html, ', ');
538*4fd6a1d7SAndreas Gohr            $html .= '</dd>';
539*4fd6a1d7SAndreas Gohr        }
540*4fd6a1d7SAndreas Gohr        if ($this->extension->getDependencyList()) {
541*4fd6a1d7SAndreas Gohr            $html .= '<dt>' . $this->getLang('depends') . '</dt>';
542*4fd6a1d7SAndreas Gohr            $html .= '<dd>';
543*4fd6a1d7SAndreas Gohr            $html .= $this->makeLinkList($extension->getDependencies());
544*4fd6a1d7SAndreas Gohr            $html .= '</dd>';
545*4fd6a1d7SAndreas Gohr        }
546*4fd6a1d7SAndreas Gohr
547*4fd6a1d7SAndreas Gohr        if ($this->extension->getSimilarExtensions()) {
548*4fd6a1d7SAndreas Gohr            $html .= '<dt>' . $this->getLang('similar') . '</dt>';
549*4fd6a1d7SAndreas Gohr            $html .= '<dd>';
550*4fd6a1d7SAndreas Gohr            $html .= $this->makeLinkList($extension->getSimilarExtensions());
551*4fd6a1d7SAndreas Gohr            $html .= '</dd>';
552*4fd6a1d7SAndreas Gohr        }
553*4fd6a1d7SAndreas Gohr
554*4fd6a1d7SAndreas Gohr        if ($this->extension->getConflicts()) {
555*4fd6a1d7SAndreas Gohr            $html .= '<dt>' . $this->getLang('conflicts') . '</dt>';
556*4fd6a1d7SAndreas Gohr            $html .= '<dd>';
557*4fd6a1d7SAndreas Gohr            $html .= $this->makeLinkList($extension->getConflicts());
558*4fd6a1d7SAndreas Gohr            $html .= '</dd>';
559*4fd6a1d7SAndreas Gohr        }
560*4fd6a1d7SAndreas Gohr        $html .= '</dl>' . DOKU_LF;
561*4fd6a1d7SAndreas Gohr        return $html;
562*4fd6a1d7SAndreas Gohr    }
563*4fd6a1d7SAndreas Gohr
564*4fd6a1d7SAndreas Gohr
565*4fd6a1d7SAndreas Gohr}
566