extension = $extension;
    }
    public function render()
    {
        $classes = $this->getClasses();
        $html = "extension->getId()}\">";
        $html .= '';
        $html .= $this->thumbnail();
        $html .= '' .
            hsc($this->extension->getBase()) . '';
        $html .= $this->popularity();
        $html .= '
';
        $html .= '';
        $html .= $this->main();
        $html .= '
';
        $html .= '';
        $html .= $this->notices();
        $html .= '
';
        $html .= '';
        $html .= $this->details();
        $html .= '
';
        $html .= '';
        // show the available update if there is one
        if ($this->extension->isUpdateAvailable()) {
            $html .= ' 
' . $this->getLang('available_version') . ' ' .
                hsc($this->extension->getLastUpdate()) . '
';
        }
        $html .= $this->actions();
        $html .= '
![]() ';
        if ($link) $html .= '';
        return $html;
    }
    /**
     * The main information about the extension
     *
     * @return string
     */
    protected function main()
    {
        $html = '';
        $html .= '
';
        if ($link) $html .= '';
        return $html;
    }
    /**
     * The main information about the extension
     *
     * @return string
     */
    protected function main()
    {
        $html = '';
        $html .= '
';
        $html .= '
';
        $html .= sprintf($this->getLang('extensionby'), hsc($this->extension->getDisplayName()), $this->author());
        $html .= '
';
        $html .= '';
        if ($this->extension->isBundled()) {
            $html .= hsc('<' . $this->getLang('status_bundled') . '>');
        } elseif ($this->extension->getInstalledVersion()) {
            $html .= hsc($this->extension->getInstalledVersion());
        }
        $html .= '
';
        $html .= '';
        $html .= '' . hsc($this->extension->getDescription()) . '
';
        $html .= $this->mainLinks();
        return $html;
    }
    /**
     * Display the available notices for the extension
     *
     * @return string
     */
    protected function notices()
    {
        $notices = Notice::list($this->extension);
        $html = '';
        foreach ($notices as $type => $messages) {
            foreach ($messages as $message) {
                $message = hsc($message);
                $message = nl2br($message);
                $message = preg_replace('/`([^`]+)`/', '$1', $message);
                $message = sprintf(
                    '%s%s',
                    inlineSVG(Notice::icon($type)),
                    $message
                );
                $html .= '- ' . $message . ' 
';
            }
        }
        $html .= '
';
        return $html;
    }
    /**
     * Generate the link bar HTML code
     *
     * @return string The HTML code
     */
    public function mainLinks()
    {
        $html = '';
        return $html;
    }
    /**
     * Create the details section
     *
     * @return string
     */
    protected function details()
    {
        $html = '';
        $html .= '' . $this->getLang('details') . '
';
        $default = $this->getLang('unknown');
        $list = [];
        if (!$this->extension->isBundled()) {
            $list['downloadurl'] = $this->shortlink($this->extension->getDownloadURL(), 'download', $default);
            $list['repository'] = $this->shortlink($this->extension->getSourcerepoURL(), 'repo', $default);
        }
        if ($this->extension->isInstalled()) {
            if ($this->extension->isBundled()) {
                $list['installed_version'] = $this->getLang('status_bundled');
            } else {
                if ($this->extension->getInstalledVersion()) {
                    $list['installed_version'] = hsc($this->extension->getInstalledVersion());
                }
                if (!$this->extension->isBundled()) {
                    $installDate = $this->extension->getManager()->getInstallDate();
                    $list['installed'] = $installDate ? dformat($installDate->getTimestamp()) : $default;
                    $updateDate = $this->extension->getManager()->getLastUpdate();
                    $list['install_date'] = $updateDate ? dformat($updateDate->getTimestamp()) : $default;
                }
            }
        }
        if (!$this->extension->isInstalled() || $this->extension->isUpdateAvailable()) {
            $list['available_version'] = $this->extension->getLastUpdate()
                ? hsc($this->extension->getLastUpdate())
                : $default;
        }
        if (!$this->extension->isBundled() && $this->extension->getCompatibleVersions()) {
            $list['compatible'] = implode(', ', array_map(
                static fn($date, $version) => '' . $version['label'] . ' (' . $date . ')',
                array_keys($this->extension->getCompatibleVersions()),
                array_values($this->extension->getCompatibleVersions())
            ));
        }
        $list['provides'] = implode(', ', array_map('hsc', $this->extension->getComponentTypes()));
        $tags = $this->extension->getTags();
        if ($tags) {
            $list['tags'] = implode(', ', array_map(function ($tag) {
                $url = $this->tabURL('search', ['q' => 'tag:' . $tag]);
                return '' . hsc($tag) . '';
            }, $tags));
        }
        if ($this->extension->getDependencyList()) {
            $list['depends'] = $this->linkExtensions($this->extension->getDependencyList());
        }
        if ($this->extension->getSimilarList()) {
            $list['similar'] = $this->linkExtensions($this->extension->getSimilarList());
        }
        if ($this->extension->getConflictList()) {
            $list['conflicts'] = $this->linkExtensions($this->extension->getConflictList());
        }
        $html .= '';
        foreach ($list as $key => $value) {
            $html .= '- ' . rtrim($this->getLang($key), ':') . '';
            $html .= '
- ' . $value . '';
        }
        $html .= '
';
        $html .= ' ';
        return $html;
    }
    /**
     * Generate a link to the author of the extension
     *
     * @return string The HTML code of the link
     */
    protected function author()
    {
        if (!$this->extension->getAuthor()) {
            return '' . $this->getLang('unknown_author') . '';
        }
        $names = explode(',', $this->extension->getAuthor());
        $names = array_map('trim', $names);
        if (count($names) > 2) {
            $names = array_slice($names, 0, 2);
            $names[] = '…';
        }
        $name = implode(', ', $names);
        $mailid = $this->extension->getEmailID();
        if ($mailid) {
            $url = $this->tabURL('search', ['q' => 'authorid:' . $mailid]);
            $html = '' .
                ' ' .
                hsc($name) . '';
        } else {
            $html = '' . hsc($this->extension->getAuthor()) . '';
        }
        return '' . $html . '';
    }
    /**
     * The popularity bar
     *
     * @return string
     */
    protected function popularity()
    {
        $popularity = $this->extension->getPopularity();
        if (!$popularity) return '';
        if ($this->extension->isBundled()) return '';
        $popimg = '
 ' .
                hsc($name) . '';
        } else {
            $html = '' . hsc($this->extension->getAuthor()) . '';
        }
        return '' . $html . '';
    }
    /**
     * The popularity bar
     *
     * @return string
     */
    protected function popularity()
    {
        $popularity = $this->extension->getPopularity();
        if (!$popularity) return '';
        if ($this->extension->isBundled()) return '';
        $popimg = ' ';
        if ($popularity > 0.25) {
            $title = $this->getLang('popularity_high');
            $emoji = str_repeat($popimg, 3);
        } elseif ($popularity > 0.15) {
            $title = $this->getLang('popularity_medium');
            $emoji = str_repeat($popimg, 2);
        } elseif ($popularity > 0.05) {
            $title = $this->getLang('popularity_low');
            $emoji = str_repeat($popimg, 1);
        } else {
            return '';
        }
        $title .= ' (' . round($popularity * 100) . '%)';
        return '' . $emoji . '';
    }
    /**
     * Generate the action buttons
     *
     * @return string
     */
    protected function actions()
    {
        $html = '';
        $actions = [];
        // check permissions
        try {
            Installer::ensurePermissions($this->extension);
        } catch (\Exception $e) {
            return '';
        }
        // gather available actions
        if ($this->extension->isInstalled()) {
            if (!$this->extension->isProtected()) $actions[] = 'uninstall';
            if ($this->extension->getDownloadURL()) {
                $actions[] = $this->extension->isUpdateAvailable() ? 'update' : 'reinstall';
            }
            // no enable/disable for templates
            if (!$this->extension->isProtected() && !$this->extension->isTemplate()) {
                $actions[] = $this->extension->isEnabled() ? 'disable' : 'enable';
            }
        } elseif ($this->extension->getDownloadURL()) {
            $actions[] = 'install';
        }
        // output the buttons
        foreach ($actions as $action) {
            $attr = [
                'class' => 'button ' . $action,
                'type' => 'submit',
                'name' => 'fn[' . $action . '][' . $this->extension->getID() . ']',
            ];
            $html .= '';
        }
        return $html;
    }
    // endregion
    // region utility functions
    /**
     * Create the classes representing the state of the extension
     *
     * @return string
     */
    protected function getClasses()
    {
        $classes = ['extension', $this->extension->getType()];
        if ($this->extension->isInstalled()) $classes[] = 'installed';
        if ($this->extension->isUpdateAvailable()) $classes[] = 'update';
        $classes[] = $this->extension->isEnabled() ? 'enabled' : 'disabled';
        return implode(' ', $classes);
    }
    /**
     * Create an attributes array for a link
     *
     * Handles interwiki links to dokuwiki.org
     *
     * @param string $url The URL to link to
     * @param string $class Additional classes to add
     * @return array
     */
    protected function prepareLinkAttributes($url, $class)
    {
        global $conf;
        $attributes = [
            'href' => $url,
            'class' => 'urlextern',
            'target' => $conf['target']['extern'],
            'rel' => 'noopener',
            'title' => $url,
        ];
        if ($conf['relnofollow']) {
            $attributes['rel'] .= ' ugc nofollow';
        }
        if (preg_match('/^https?:\/\/(www\.)?dokuwiki\.org\//i', $url)) {
            $attributes['class'] = 'interwiki iw_doku';
            $attributes['target'] = $conf['target']['interwiki'];
            $attributes['rel'] = '';
        }
        $attributes['class'] .= ' ' . $class;
        return $attributes;
    }
    /**
     * Create a link from the given URL
     *
     * Shortens the URL for display
     *
     * @param string $url
     * @param string $class Additional classes to add
     * @param string $fallback If URL is empty return this fallback (raw HTML)
     * @return string  HTML link
     */
    protected function shortlink($url, $class, $fallback = '')
    {
        if (!$url) return $fallback;
        $link = parse_url($url);
        $base = $link['host'];
        if (!empty($link['port'])) $base .= $base . ':' . $link['port'];
        $long = $link['path'];
        if (!empty($link['query'])) $long .= $link['query'];
        $name = shorten($base, $long, 55);
        $params = $this->prepareLinkAttributes($url, $class);
        $html = '' . hsc($name) . '';
        return $html;
    }
    /**
     * Generate a list of links for extensions
     *
     * Links to the search tab with the extension name
     *
     * @param array $extensions The extension names
     * @return string The HTML code
     */
    public function linkExtensions($extensions)
    {
        $html = '';
        foreach ($extensions as $link) {
            $html .= '' .
                hsc($link) . ', ';
        }
        return rtrim($html, ', ');
    }
    // endregion
}
';
        if ($popularity > 0.25) {
            $title = $this->getLang('popularity_high');
            $emoji = str_repeat($popimg, 3);
        } elseif ($popularity > 0.15) {
            $title = $this->getLang('popularity_medium');
            $emoji = str_repeat($popimg, 2);
        } elseif ($popularity > 0.05) {
            $title = $this->getLang('popularity_low');
            $emoji = str_repeat($popimg, 1);
        } else {
            return '';
        }
        $title .= ' (' . round($popularity * 100) . '%)';
        return '' . $emoji . '';
    }
    /**
     * Generate the action buttons
     *
     * @return string
     */
    protected function actions()
    {
        $html = '';
        $actions = [];
        // check permissions
        try {
            Installer::ensurePermissions($this->extension);
        } catch (\Exception $e) {
            return '';
        }
        // gather available actions
        if ($this->extension->isInstalled()) {
            if (!$this->extension->isProtected()) $actions[] = 'uninstall';
            if ($this->extension->getDownloadURL()) {
                $actions[] = $this->extension->isUpdateAvailable() ? 'update' : 'reinstall';
            }
            // no enable/disable for templates
            if (!$this->extension->isProtected() && !$this->extension->isTemplate()) {
                $actions[] = $this->extension->isEnabled() ? 'disable' : 'enable';
            }
        } elseif ($this->extension->getDownloadURL()) {
            $actions[] = 'install';
        }
        // output the buttons
        foreach ($actions as $action) {
            $attr = [
                'class' => 'button ' . $action,
                'type' => 'submit',
                'name' => 'fn[' . $action . '][' . $this->extension->getID() . ']',
            ];
            $html .= '';
        }
        return $html;
    }
    // endregion
    // region utility functions
    /**
     * Create the classes representing the state of the extension
     *
     * @return string
     */
    protected function getClasses()
    {
        $classes = ['extension', $this->extension->getType()];
        if ($this->extension->isInstalled()) $classes[] = 'installed';
        if ($this->extension->isUpdateAvailable()) $classes[] = 'update';
        $classes[] = $this->extension->isEnabled() ? 'enabled' : 'disabled';
        return implode(' ', $classes);
    }
    /**
     * Create an attributes array for a link
     *
     * Handles interwiki links to dokuwiki.org
     *
     * @param string $url The URL to link to
     * @param string $class Additional classes to add
     * @return array
     */
    protected function prepareLinkAttributes($url, $class)
    {
        global $conf;
        $attributes = [
            'href' => $url,
            'class' => 'urlextern',
            'target' => $conf['target']['extern'],
            'rel' => 'noopener',
            'title' => $url,
        ];
        if ($conf['relnofollow']) {
            $attributes['rel'] .= ' ugc nofollow';
        }
        if (preg_match('/^https?:\/\/(www\.)?dokuwiki\.org\//i', $url)) {
            $attributes['class'] = 'interwiki iw_doku';
            $attributes['target'] = $conf['target']['interwiki'];
            $attributes['rel'] = '';
        }
        $attributes['class'] .= ' ' . $class;
        return $attributes;
    }
    /**
     * Create a link from the given URL
     *
     * Shortens the URL for display
     *
     * @param string $url
     * @param string $class Additional classes to add
     * @param string $fallback If URL is empty return this fallback (raw HTML)
     * @return string  HTML link
     */
    protected function shortlink($url, $class, $fallback = '')
    {
        if (!$url) return $fallback;
        $link = parse_url($url);
        $base = $link['host'];
        if (!empty($link['port'])) $base .= $base . ':' . $link['port'];
        $long = $link['path'];
        if (!empty($link['query'])) $long .= $link['query'];
        $name = shorten($base, $long, 55);
        $params = $this->prepareLinkAttributes($url, $class);
        $html = '' . hsc($name) . '';
        return $html;
    }
    /**
     * Generate a list of links for extensions
     *
     * Links to the search tab with the extension name
     *
     * @param array $extensions The extension names
     * @return string The HTML code
     */
    public function linkExtensions($extensions)
    {
        $html = '';
        foreach ($extensions as $link) {
            $html .= '' .
                hsc($link) . ', ';
        }
        return rtrim($html, ', ');
    }
    // endregion
}