You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

675 lines
24 KiB

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