1<?php 2/** 3 * DokuWiki Plugin extension (Helper Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Michael Hamann <michael@content-space.de> 7 */ 8 9/** 10 * Class helper_plugin_extension_list takes care of creating a HTML list of extensions 11 */ 12class helper_plugin_extension_list extends DokuWiki_Plugin 13{ 14 protected $form = ''; 15 /** @var helper_plugin_extension_gui */ 16 protected $gui; 17 18 /** 19 * Constructor 20 * 21 * loads additional helpers 22 */ 23 public function __construct() 24 { 25 $this->gui = plugin_load('helper', 'extension_gui'); 26 } 27 28 /** 29 * Initialize the extension table form 30 */ 31 public function startForm() 32 { 33 $this->form .= '<ul class="extensionList">'; 34 } 35 36 /** 37 * Build single row of extension table 38 * 39 * @param helper_plugin_extension_extension $extension The extension that shall be added 40 * @param bool $showinfo Show the info area 41 */ 42 public function addRow(helper_plugin_extension_extension $extension, $showinfo = false) 43 { 44 $this->startRow($extension); 45 $this->populateColumn('legend', $this->makeLegend($extension, $showinfo)); 46 $this->populateColumn('actions', $this->makeActions($extension)); 47 $this->endRow(); 48 } 49 50 /** 51 * Adds a header to the form 52 * 53 * @param string $id The id of the header 54 * @param string $header The content of the header 55 * @param int $level The level of the header 56 */ 57 public function addHeader($id, $header, $level = 2) 58 { 59 $this->form .='<h'.$level.' id="'.$id.'">'.hsc($header).'</h'.$level.'>'.DOKU_LF; 60 } 61 62 /** 63 * Adds a paragraph to the form 64 * 65 * @param string $data The content 66 */ 67 public function addParagraph($data) 68 { 69 $this->form .= '<p>'.hsc($data).'</p>'.DOKU_LF; 70 } 71 72 /** 73 * Add hidden fields to the form with the given data 74 * 75 * @param array $data key-value list of fields and their values to add 76 */ 77 public function addHidden(array $data) 78 { 79 $this->form .= '<div class="no">'; 80 foreach ($data as $key => $value) { 81 $this->form .= '<input type="hidden" name="'.hsc($key).'" value="'.hsc($value).'" />'; 82 } 83 $this->form .= '</div>'.DOKU_LF; 84 } 85 86 /** 87 * Add closing tags 88 */ 89 public function endForm() 90 { 91 $this->form .= '</ul>'; 92 } 93 94 /** 95 * Show message when no results are found 96 */ 97 public function nothingFound() 98 { 99 global $lang; 100 $this->form .= '<li class="notfound">'.$lang['nothingfound'].'</li>'; 101 } 102 103 /** 104 * Print the form 105 * 106 * @param bool $returnonly whether to return html or print 107 */ 108 public function render($returnonly = false) 109 { 110 if ($returnonly) return $this->form; 111 echo $this->form; 112 } 113 114 /** 115 * Start the HTML for the row for the extension 116 * 117 * @param helper_plugin_extension_extension $extension The extension 118 */ 119 private function startRow(helper_plugin_extension_extension $extension) 120 { 121 $this->form .= '<li id="extensionplugin__'.hsc($extension->getID()). 122 '" class="'.$this->makeClass($extension).'">'; 123 } 124 125 /** 126 * Add a column with the given class and content 127 * @param string $class The class name 128 * @param string $html The content 129 */ 130 private function populateColumn($class, $html) 131 { 132 $this->form .= '<div class="'.$class.' col">'.$html.'</div>'.DOKU_LF; 133 } 134 135 /** 136 * End the row 137 */ 138 private function endRow() 139 { 140 $this->form .= '</li>'.DOKU_LF; 141 } 142 143 /** 144 * Generate the link to the plugin homepage 145 * 146 * @param helper_plugin_extension_extension $extension The extension 147 * @return string The HTML code 148 */ 149 public function makeHomepageLink(helper_plugin_extension_extension $extension) 150 { 151 global $conf; 152 $url = $extension->getURL(); 153 if (strtolower(parse_url($url, PHP_URL_HOST)) == 'www.dokuwiki.org') { 154 $linktype = 'interwiki'; 155 } else { 156 $linktype = 'extern'; 157 } 158 $param = array( 159 'href' => $url, 160 'title' => $url, 161 'class' => ($linktype == 'extern') ? 'urlextern' : 'interwiki iw_doku', 162 'target' => $conf['target'][$linktype], 163 'rel' => ($linktype == 'extern') ? 'noopener' : '', 164 ); 165 if ($linktype == 'extern' && $conf['relnofollow']) { 166 $param['rel'] = implode(' ', [$param['rel'], 'ugc nofollow']); 167 } 168 $html = ' <a '. buildAttributes($param, true).'>'. 169 $this->getLang('homepage_link').'</a>'; 170 return $html; 171 } 172 173 /** 174 * Generate the class name for the row of the extension 175 * 176 * @param helper_plugin_extension_extension $extension The extension object 177 * @return string The class name 178 */ 179 public function makeClass(helper_plugin_extension_extension $extension) 180 { 181 $class = ($extension->isTemplate()) ? 'template' : 'plugin'; 182 if ($extension->isInstalled()) { 183 $class.=' installed'; 184 $class.= ($extension->isEnabled()) ? ' enabled':' disabled'; 185 if ($extension->updateAvailable()) $class .= ' updatable'; 186 } 187 if (!$extension->canModify()) $class.= ' notselect'; 188 if ($extension->isProtected()) $class.= ' protected'; 189 //if($this->showinfo) $class.= ' showinfo'; 190 return $class; 191 } 192 193 /** 194 * Generate a link to the author of the extension 195 * 196 * @param helper_plugin_extension_extension $extension The extension object 197 * @return string The HTML code of the link 198 */ 199 public function makeAuthor(helper_plugin_extension_extension $extension) 200 { 201 if ($extension->getAuthor()) { 202 $mailid = $extension->getEmailID(); 203 if ($mailid) { 204 $url = $this->gui->tabURL('search', array('q' => 'authorid:'.$mailid)); 205 $html = '<a href="'.$url.'" class="author" title="'.$this->getLang('author_hint').'" >'. 206 '<img src="//www.gravatar.com/avatar/'.$mailid. 207 '?s=20&d=mm" width="20" height="20" alt="" /> '. 208 hsc($extension->getAuthor()).'</a>'; 209 } else { 210 $html = '<span class="author">'.hsc($extension->getAuthor()).'</span>'; 211 } 212 $html = '<bdi>'.$html.'</bdi>'; 213 } else { 214 $html = '<em class="author">'.$this->getLang('unknown_author').'</em>'.DOKU_LF; 215 } 216 return $html; 217 } 218 219 /** 220 * Get the link and image tag for the screenshot/thumbnail 221 * 222 * @param helper_plugin_extension_extension $extension The extension object 223 * @return string The HTML code 224 */ 225 public function makeScreenshot(helper_plugin_extension_extension $extension) 226 { 227 $screen = $extension->getScreenshotURL(); 228 $thumb = $extension->getThumbnailURL(); 229 230 if ($screen) { 231 // use protocol independent URLs for images coming from us #595 232 $screen = str_replace('http://www.dokuwiki.org', '//www.dokuwiki.org', $screen); 233 $thumb = str_replace('http://www.dokuwiki.org', '//www.dokuwiki.org', $thumb); 234 235 $title = sprintf($this->getLang('screenshot'), hsc($extension->getDisplayName())); 236 $img = '<a href="'.hsc($screen).'" target="_blank" class="extension_screenshot">'. 237 '<img alt="'.$title.'" width="120" height="70" src="'.hsc($thumb).'" />'. 238 '</a>'; 239 } elseif ($extension->isTemplate()) { 240 $img = '<img alt="" width="120" height="70" src="'.DOKU_BASE. 241 'lib/plugins/extension/images/template.png" />'; 242 } else { 243 $img = '<img alt="" width="120" height="70" src="'.DOKU_BASE. 244 'lib/plugins/extension/images/plugin.png" />'; 245 } 246 $html = '<div class="screenshot" >'.$img.'<span></span></div>'.DOKU_LF; 247 return $html; 248 } 249 250 /** 251 * Extension main description 252 * 253 * @param helper_plugin_extension_extension $extension The extension object 254 * @param bool $showinfo Show the info section 255 * @return string The HTML code 256 */ 257 public function makeLegend(helper_plugin_extension_extension $extension, $showinfo = false) 258 { 259 $html = '<div>'; 260 $html .= '<h2>'; 261 $html .= sprintf( 262 $this->getLang('extensionby'), 263 '<bdi>'.hsc($extension->getDisplayName()).'</bdi>', 264 $this->makeAuthor($extension) 265 ); 266 $html .= '</h2>'.DOKU_LF; 267 268 $html .= $this->makeScreenshot($extension); 269 270 $popularity = $extension->getPopularity(); 271 if ($popularity !== false && !$extension->isBundled()) { 272 $popularityText = sprintf($this->getLang('popularity'), round($popularity*100, 2)); 273 $html .= '<div class="popularity" title="'.$popularityText.'">'. 274 '<div style="width: '.($popularity * 100).'%;">'. 275 '<span class="a11y">'.$popularityText.'</span>'. 276 '</div></div>'.DOKU_LF; 277 } 278 279 if ($extension->getDescription()) { 280 $html .= '<p><bdi>'; 281 $html .= hsc($extension->getDescription()).' '; 282 $html .= '</bdi></p>'.DOKU_LF; 283 } 284 285 $html .= $this->makeLinkbar($extension); 286 287 if ($showinfo) { 288 $url = $this->gui->tabURL(''); 289 $class = 'close'; 290 } else { 291 $url = $this->gui->tabURL('', array('info' => $extension->getID())); 292 $class = ''; 293 } 294 $html .= ' <a href="'.$url.'#extensionplugin__'.$extension->getID(). 295 '" class="info '.$class.'" title="'.$this->getLang('btn_info'). 296 '" data-extid="'.$extension->getID().'">'.$this->getLang('btn_info').'</a>'; 297 298 if ($showinfo) { 299 $html .= $this->makeInfo($extension); 300 } 301 $html .= $this->makeNoticeArea($extension); 302 $html .= '</div>'.DOKU_LF; 303 return $html; 304 } 305 306 /** 307 * Generate the link bar HTML code 308 * 309 * @param helper_plugin_extension_extension $extension The extension instance 310 * @return string The HTML code 311 */ 312 public function makeLinkbar(helper_plugin_extension_extension $extension) 313 { 314 global $conf; 315 $html = '<div class="linkbar">'; 316 $html .= $this->makeHomepageLink($extension); 317 318 $bugtrackerURL = $extension->getBugtrackerURL(); 319 if ($bugtrackerURL) { 320 if (strtolower(parse_url($bugtrackerURL, PHP_URL_HOST)) == 'www.dokuwiki.org') { 321 $linktype = 'interwiki'; 322 } else { 323 $linktype = 'extern'; 324 } 325 $param = array( 326 'href' => $bugtrackerURL, 327 'title' => $bugtrackerURL, 328 'class' => 'bugs', 329 'target' => $conf['target'][$linktype], 330 'rel' => ($linktype == 'extern') ? 'noopener' : '', 331 ); 332 if ($conf['relnofollow']) { 333 $param['rel'] = implode(' ', [$param['rel'], 'ugc nofollow']); 334 } 335 $html .= ' <a '.buildAttributes($param, true).'>'. 336 $this->getLang('bugs_features').'</a>'; 337 } 338 if ($extension->getTags()) { 339 $first = true; 340 $html .= ' <span class="tags">'.$this->getLang('tags').' '; 341 foreach ($extension->getTags() as $tag) { 342 if (!$first) { 343 $html .= ', '; 344 } else { 345 $first = false; 346 } 347 $url = $this->gui->tabURL('search', ['q' => 'tag:'.$tag]); 348 $html .= '<bdi><a href="'.$url.'">'.hsc($tag).'</a></bdi>'; 349 } 350 $html .= '</span>'; 351 } 352 $html .= '</div>'.DOKU_LF; 353 return $html; 354 } 355 356 /** 357 * Notice area 358 * 359 * @param helper_plugin_extension_extension $extension The extension 360 * @return string The HTML code 361 */ 362 public function makeNoticeArea(helper_plugin_extension_extension $extension) 363 { 364 $html = ''; 365 $missing_dependencies = $extension->getMissingDependencies(); 366 if (!empty($missing_dependencies)) { 367 $html .= '<div class="msg error">' . 368 sprintf( 369 $this->getLang('missing_dependency'), 370 '<bdi>' . implode(', ', $missing_dependencies) . '</bdi>' 371 ) . 372 '</div>'; 373 } 374 if ($extension->isInWrongFolder()) { 375 $html .= '<div class="msg error">' . 376 sprintf( 377 $this->getLang('wrong_folder'), 378 '<bdi>' . hsc($extension->getInstallName()) . '</bdi>', 379 '<bdi>' . hsc($extension->getBase()) . '</bdi>' 380 ) . 381 '</div>'; 382 } 383 if (($securityissue = $extension->getSecurityIssue()) !== false) { 384 $html .= '<div class="msg error">'. 385 sprintf($this->getLang('security_issue'), '<bdi>'.hsc($securityissue).'</bdi>'). 386 '</div>'; 387 } 388 if (($securitywarning = $extension->getSecurityWarning()) !== false) { 389 $html .= '<div class="msg notify">'. 390 sprintf($this->getLang('security_warning'), '<bdi>'.hsc($securitywarning).'</bdi>'). 391 '</div>'; 392 } 393 if ($extension->updateAvailable()) { 394 $html .= '<div class="msg notify">'. 395 sprintf($this->getLang('update_available'), hsc($extension->getLastUpdate())). 396 '</div>'; 397 } 398 if ($extension->hasDownloadURLChanged()) { 399 $html .= '<div class="msg notify">' . 400 sprintf( 401 $this->getLang('url_change'), 402 '<bdi>' . hsc($extension->getDownloadURL()) . '</bdi>', 403 '<bdi>' . hsc($extension->getLastDownloadURL()) . '</bdi>' 404 ) . 405 '</div>'; 406 } 407 return $html.DOKU_LF; 408 } 409 410 /** 411 * Create a link from the given URL 412 * 413 * Shortens the URL for display 414 * 415 * @param string $url 416 * @return string HTML link 417 */ 418 public function shortlink($url) 419 { 420 $link = parse_url($url); 421 422 $base = $link['host']; 423 if (!empty($link['port'])) $base .= $base.':'.$link['port']; 424 $long = $link['path']; 425 if (!empty($link['query'])) $long .= $link['query']; 426 427 $name = shorten($base, $long, 55); 428 429 $html = '<a href="'.hsc($url).'" class="urlextern">'.hsc($name).'</a>'; 430 return $html; 431 } 432 433 /** 434 * Plugin/template details 435 * 436 * @param helper_plugin_extension_extension $extension The extension 437 * @return string The HTML code 438 */ 439 public function makeInfo(helper_plugin_extension_extension $extension) 440 { 441 $default = $this->getLang('unknown'); 442 $html = '<dl class="details">'; 443 444 $html .= '<dt>'.$this->getLang('status').'</dt>'; 445 $html .= '<dd>'.$this->makeStatus($extension).'</dd>'; 446 447 if ($extension->getDonationURL()) { 448 $html .= '<dt>'.$this->getLang('donate').'</dt>'; 449 $html .= '<dd>'; 450 $html .= '<a href="'.$extension->getDonationURL().'" class="donate">'. 451 $this->getLang('donate_action').'</a>'; 452 $html .= '</dd>'; 453 } 454 455 if (!$extension->isBundled()) { 456 $html .= '<dt>'.$this->getLang('downloadurl').'</dt>'; 457 $html .= '<dd><bdi>'; 458 $html .= ($extension->getDownloadURL() 459 ? $this->shortlink($extension->getDownloadURL()) 460 : $default); 461 $html .= '</bdi></dd>'; 462 463 $html .= '<dt>'.$this->getLang('repository').'</dt>'; 464 $html .= '<dd><bdi>'; 465 $html .= ($extension->getSourcerepoURL() 466 ? $this->shortlink($extension->getSourcerepoURL()) 467 : $default); 468 $html .= '</bdi></dd>'; 469 } 470 471 if ($extension->isInstalled()) { 472 if ($extension->getInstalledVersion()) { 473 $html .= '<dt>'.$this->getLang('installed_version').'</dt>'; 474 $html .= '<dd>'; 475 $html .= hsc($extension->getInstalledVersion()); 476 $html .= '</dd>'; 477 } 478 if (!$extension->isBundled()) { 479 $html .= '<dt>'.$this->getLang('install_date').'</dt>'; 480 $html .= '<dd>'; 481 $html .= ($extension->getUpdateDate() 482 ? hsc($extension->getUpdateDate()) 483 : $this->getLang('unknown')); 484 $html .= '</dd>'; 485 } 486 } 487 if (!$extension->isInstalled() || $extension->updateAvailable()) { 488 $html .= '<dt>'.$this->getLang('available_version').'</dt>'; 489 $html .= '<dd>'; 490 $html .= ($extension->getLastUpdate() 491 ? hsc($extension->getLastUpdate()) 492 : $this->getLang('unknown')); 493 $html .= '</dd>'; 494 } 495 496 $html .= '<dt>'.$this->getLang('provides').'</dt>'; 497 $html .= '<dd><bdi>'; 498 $html .= ($extension->getTypes() 499 ? hsc(implode(', ', $extension->getTypes())) 500 : $default); 501 $html .= '</bdi></dd>'; 502 503 if (!$extension->isBundled() && $extension->getCompatibleVersions()) { 504 $html .= '<dt>'.$this->getLang('compatible').'</dt>'; 505 $html .= '<dd>'; 506 foreach ($extension->getCompatibleVersions() as $date => $version) { 507 $html .= '<bdi>'.$version['label'].' ('.$date.')</bdi>, '; 508 } 509 $html = rtrim($html, ', '); 510 $html .= '</dd>'; 511 } 512 if ($extension->getDependencies()) { 513 $html .= '<dt>'.$this->getLang('depends').'</dt>'; 514 $html .= '<dd>'; 515 $html .= $this->makeLinkList($extension->getDependencies()); 516 $html .= '</dd>'; 517 } 518 519 if ($extension->getSimilarExtensions()) { 520 $html .= '<dt>'.$this->getLang('similar').'</dt>'; 521 $html .= '<dd>'; 522 $html .= $this->makeLinkList($extension->getSimilarExtensions()); 523 $html .= '</dd>'; 524 } 525 526 if ($extension->getConflicts()) { 527 $html .= '<dt>'.$this->getLang('conflicts').'</dt>'; 528 $html .= '<dd>'; 529 $html .= $this->makeLinkList($extension->getConflicts()); 530 $html .= '</dd>'; 531 } 532 $html .= '</dl>'.DOKU_LF; 533 return $html; 534 } 535 536 /** 537 * Generate a list of links for extensions 538 * 539 * @param array $ext The extensions 540 * @return string The HTML code 541 */ 542 public function makeLinkList($ext) 543 { 544 $html = ''; 545 foreach ($ext as $link) { 546 $html .= '<bdi><a href="'. 547 $this->gui->tabURL('search', array('q'=>'ext:'.$link)).'">'. 548 hsc($link).'</a></bdi>, '; 549 } 550 return rtrim($html, ', '); 551 } 552 553 /** 554 * Display the action buttons if they are possible 555 * 556 * @param helper_plugin_extension_extension $extension The extension 557 * @return string The HTML code 558 */ 559 public function makeActions(helper_plugin_extension_extension $extension) 560 { 561 global $conf; 562 $html = ''; 563 $errors = ''; 564 565 if ($extension->isInstalled()) { 566 if (($canmod = $extension->canModify()) === true) { 567 if (!$extension->isProtected()) { 568 $html .= $this->makeAction('uninstall', $extension); 569 } 570 if ($extension->getDownloadURL()) { 571 if ($extension->updateAvailable()) { 572 $html .= $this->makeAction('update', $extension); 573 } else { 574 $html .= $this->makeAction('reinstall', $extension); 575 } 576 } 577 } else { 578 $errors .= '<p class="permerror">'.$this->getLang($canmod).'</p>'; 579 } 580 581 if (!$extension->isProtected() && !$extension->isTemplate()) { // no enable/disable for templates 582 if ($extension->isEnabled()) { 583 $html .= $this->makeAction('disable', $extension); 584 } else { 585 $html .= $this->makeAction('enable', $extension); 586 } 587 } 588 589 if ($extension->isGitControlled()) { 590 $errors .= '<p class="permerror">'.$this->getLang('git').'</p>'; 591 } 592 593 if ($extension->isEnabled() && 594 in_array('Auth', $extension->getTypes()) && 595 $conf['authtype'] != $extension->getID() 596 ) { 597 $errors .= '<p class="permerror">'.$this->getLang('auth').'</p>'; 598 } 599 } else { 600 if (($canmod = $extension->canModify()) === true) { 601 if ($extension->getDownloadURL()) { 602 $html .= $this->makeAction('install', $extension); 603 } 604 } else { 605 $errors .= '<div class="permerror">'.$this->getLang($canmod).'</div>'; 606 } 607 } 608 609 if (!$extension->isInstalled() && $extension->getDownloadURL()) { 610 $html .= ' <span class="version">'.$this->getLang('available_version').' '; 611 $html .= ($extension->getLastUpdate() 612 ? hsc($extension->getLastUpdate()) 613 : $this->getLang('unknown')).'</span>'; 614 } 615 616 return $html.' '.$errors.DOKU_LF; 617 } 618 619 /** 620 * Display an action button for an extension 621 * 622 * @param string $action The action 623 * @param helper_plugin_extension_extension $extension The extension 624 * @return string The HTML code 625 */ 626 public function makeAction($action, $extension) 627 { 628 $title = ''; 629 630 switch ($action) { 631 case 'install': 632 case 'reinstall': 633 $title = 'title="'.hsc($extension->getDownloadURL()).'"'; 634 break; 635 } 636 637 $classes = 'button '.$action; 638 $name = 'fn['.$action.']['.hsc($extension->getID()).']'; 639 640 $html = '<button class="'.$classes.'" name="'.$name.'" type="submit" '.$title.'>'. 641 $this->getLang('btn_'.$action).'</button> '; 642 return $html; 643 } 644 645 /** 646 * Plugin/template status 647 * 648 * @param helper_plugin_extension_extension $extension The extension 649 * @return string The description of all relevant statusses 650 */ 651 public function makeStatus(helper_plugin_extension_extension $extension) 652 { 653 $status = array(); 654 655 if ($extension->isInstalled()) { 656 $status[] = $this->getLang('status_installed'); 657 if ($extension->isProtected()) { 658 $status[] = $this->getLang('status_protected'); 659 } else { 660 $status[] = $extension->isEnabled() 661 ? $this->getLang('status_enabled') 662 : $this->getLang('status_disabled'); 663 } 664 } else { 665 $status[] = $this->getLang('status_not_installed'); 666 } 667 if (!$extension->canModify()) $status[] = $this->getLang('status_unmodifiable'); 668 if ($extension->isBundled()) $status[] = $this->getLang('status_bundled'); 669 $status[] = $extension->isTemplate() 670 ? $this->getLang('status_template') 671 : $this->getLang('status_plugin'); 672 return implode(', ', $status); 673 } 674} 675