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 .= '
';
$html .= '';
return $html;
}
// region sections
/**
* Get the link and image tag for the screenshot/thumbnail
*
* @return string The HTML code
*/
protected function thumbnail()
{
$screen = $this->extension->getScreenshotURL();
$thumb = $this->extension->getThumbnailURL();
$link = [];
$img = [
'width' => self::THUMB_WIDTH,
'height' => self::THUMB_HEIGHT,
'class' => 'shot',
'loading' => 'lazy',
'alt' => '',
];
if ($screen) {
$link = [
'href' => $screen,
'target' => '_blank',
'class' => 'extension_screenshot',
'title' => sprintf($this->getLang('screenshot'), $this->extension->getDisplayName())
];
$img['src'] = $thumb;
$img['alt'] = $link['title'];
} elseif ($this->extension->isTemplate()) {
$img['src'] = DOKU_BASE . 'lib/plugins/extension/images/template.png';
} else {
$img['src'] = DOKU_BASE . 'lib/plugins/extension/images/plugin.png';
}
$html = '';
if ($link) $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 = '
';
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
}