Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_plugin.php - 802 lines - 25228 bytes - Summary - Text - Print

Description: Plugins panel.

   1  <?php
   2  
   3  /*
   4   * Textpattern Content Management System
   5   * https://textpattern.com/
   6   *
   7   * Copyright (C) 2020 The Textpattern Development Team
   8   *
   9   * This file is part of Textpattern.
  10   *
  11   * Textpattern is free software; you can redistribute it and/or
  12   * modify it under the terms of the GNU General Public License
  13   * as published by the Free Software Foundation, version 2.
  14   *
  15   * Textpattern is distributed in the hope that it will be useful,
  16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18   * GNU General Public License for more details.
  19   *
  20   * You should have received a copy of the GNU General Public License
  21   * along with Textpattern. If not, see <https://www.gnu.org/licenses/>.
  22   */
  23  
  24  /**
  25   * Plugins panel.
  26   *
  27   * @package Admin\Plugin
  28   */
  29  
  30  use Textpattern\Search\Filter;
  31  
  32  if (!defined('txpinterface')) {
  33      die('txpinterface is undefined.');
  34  }
  35  
  36  if ($event == 'plugin') {
  37      require_privs('plugin');
  38  
  39      $available_steps = array(
  40          'plugin_edit'       => true,
  41          'plugin_help'       => false,
  42          'plugin_list'       => false,
  43          'plugin_install'    => true,
  44          'plugin_save'       => true,
  45          'plugin_upload'     => true,
  46          'plugin_load'       => true,
  47          'plugin_verify'     => true,
  48          'switch_status'     => true,
  49          'plugin_multi_edit' => true,
  50          'plugin_change_pageby' => true,
  51      );
  52  
  53      if ($step && bouncer($step, $available_steps) && is_callable($step)) {
  54          $step();
  55      } else {
  56          plugin_list();
  57      }
  58  }
  59  
  60  /**
  61   * The main panel listing all installed plugins.
  62   *
  63   * @param string|array $message The activity message
  64   */
  65  
  66  function plugin_list($message = '')
  67  {
  68      global $event;
  69  
  70      pagetop(gTxt('tab_plugins'), $message);
  71  
  72      extract(gpsa(array(
  73              'page',
  74              'sort',
  75              'dir',
  76              'crit',
  77              'search_method',
  78      )));
  79  
  80      if ($sort === '') {
  81          $sort = get_pref('plugin_sort_column', 'name');
  82      } else {
  83          if (!in_array($sort, array('name', 'status', 'author', 'version', 'modified', 'load_order'))) {
  84              $sort = 'name';
  85          }
  86  
  87          set_pref('plugin_sort_column', $sort, 'plugin', PREF_HIDDEN, '', 0, PREF_PRIVATE);
  88      }
  89  
  90      if ($dir === '') {
  91          $dir = get_pref('plugin_sort_dir', 'asc');
  92      } else {
  93          $dir = ($dir == 'desc') ? "desc" : "asc";
  94          set_pref('plugin_sort_dir', $dir, 'plugin', PREF_HIDDEN, '', 0, PREF_PRIVATE);
  95      }
  96  
  97      $sort_sql = "$sort $dir";
  98      $switch_dir = ($dir == 'desc') ? 'asc' : 'desc';
  99  
 100      $search = new Filter($event,
 101          array(
 102              'name' => array(
 103                  'column' => 'txp_plugin.name',
 104                  'label'  => gTxt('plugin'),
 105              ),
 106              'author' => array(
 107                  'column' => 'txp_plugin.author',
 108                  'label'  => gTxt('author'),
 109              ),
 110              'author_uri' => array(
 111                  'column' => 'txp_plugin.author_uri',
 112                  'label'  => gTxt('website'),
 113              ),
 114              'description' => array(
 115                  'column' => 'txp_plugin.description',
 116                  'label'  => gTxt('description'),
 117              ),
 118              'code' => array(
 119                  'column' => 'txp_plugin.code',
 120                  'label'  => gTxt('code'),
 121              ),
 122              'help' => array(
 123                  'column' => 'txp_plugin.help',
 124                  'label'  => gTxt('help'),
 125              ),
 126              'textpack' => array(
 127                  'column' => 'txp_plugin.textpack',
 128                  'label'  => 'Textpack',
 129              ),
 130              'status' => array(
 131                  'column' => 'txp_plugin.status',
 132                  'label'  => gTxt('active'),
 133                  'type'   => 'boolean',
 134              ),
 135              'type' => array(
 136                  'column' => 'txp_plugin.type',
 137                  'label'  => gTxt('type'),
 138                  'type'   => 'numeric',
 139              ),
 140              'load_order' => array(
 141                  'column' => 'txp_plugin.load_order',
 142                  'label'  => gTxt('order'),
 143                  'type'   => 'numeric',
 144              ),
 145          )
 146      );
 147  
 148      $alias_yes = '1, Yes';
 149      $alias_no = '0, No';
 150      $search->setAliases('status', array($alias_no, $alias_yes));
 151  
 152      list($criteria, $crit, $search_method) = $search->getFilter();
 153  
 154      $search_render_options = array('placeholder' => 'search_plugins');
 155      $total = safe_count('txp_plugin', $criteria);
 156  
 157      $searchBlock =
 158          n.tag(
 159              $search->renderForm('plugin', $search_render_options),
 160              'div', array(
 161                  'class' => 'txp-layout-4col-3span',
 162                  'id'    => $event.'_control',
 163              )
 164          );
 165  
 166      $contentBlock = '';
 167      $existing_files = get_filenames(PLUGINPATH.DS, GLOB_ONLYDIR) or $existing_files = array();
 168  
 169      foreach (safe_column_num('name', 'txp_plugin', 1) as $name) {
 170          unset($existing_files[$name]);
 171      }
 172  
 173      $paginator = new \Textpattern\Admin\Paginator($event, 'plugin');
 174      $limit = $paginator->getLimit();
 175  
 176      list($page, $offset, $numPages) = pager($total, $limit, $page);
 177  
 178      if ($total < 1) {
 179          if ($crit !== '') {
 180              $contentBlock .= graf(
 181                  span(null, array('class' => 'ui-icon ui-icon-info')).' '.
 182                  gTxt('no_results_found'),
 183                  array('class' => 'alert-block information')
 184              );
 185          }
 186      } else {
 187          $rs = safe_rows_start(
 188              "name, status, author, author_uri, version, description, length(help) AS help, ABS(STRCMP(MD5(code), code_md5)) AS modified, load_order, flags, type",
 189              'txp_plugin',
 190              "$criteria ORDER BY $sort_sql LIMIT $offset, $limit"
 191          );
 192  
 193          $publicOn = get_pref('use_plugins');
 194          $adminOn = get_pref('admin_side_plugins');
 195  
 196          $contentBlock .=
 197              n.tag_start('form', array(
 198                  'class'  => 'multi_edit_form',
 199                  'id'     => 'plugin_form',
 200                  'name'   => 'longform',
 201                  'method' => 'post',
 202                  'action' => 'index.php',
 203              )).
 204              n.tag_start('div', array(
 205                  'class'      => 'txp-listtables',
 206                  'tabindex'   => 0,
 207                  'aria-label' => gTxt('list'),
 208              )).
 209              n.tag_start('table', array('class' => 'txp-list')).
 210              n.tag_start('thead').
 211              tr(
 212                  hCell(
 213                      fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'),
 214                          '', ' class="txp-list-col-multi-edit" scope="col" title="'.gTxt('toggle_all_selected').'"'
 215                  ).
 216                  column_head(
 217                      'plugin', 'name', 'plugin', true, $switch_dir, '', '',
 218                          (('name' == $sort) ? "$dir " : '').'txp-list-col-name'
 219                  ).
 220                  column_head(
 221                      'author', 'author', 'plugin', true, $switch_dir, '', '',
 222                          (('author' == $sort) ? "$dir " : '').'txp-list-col-author'
 223                  ).
 224                  column_head(
 225                      'version', 'version', 'plugin', true, $switch_dir, '', '',
 226                          (('version' == $sort) ? "$dir " : '').'txp-list-col-version'
 227                  ).
 228                  column_head(
 229                      'modified', 'modified', 'plugin', true, $switch_dir, '', '',
 230                          (('modified' == $sort) ? "$dir " : '').'txp-list-col-modified'
 231                  ).
 232                  hCell(gTxt(
 233                      'description'), '', ' class="txp-list-col-description" scope="col"'
 234                  ).
 235                  column_head(
 236                      'active', 'status', 'plugin', true, $switch_dir, '', '',
 237                          (('status' == $sort) ? "$dir " : '').'txp-list-col-status'
 238                  ).
 239                  column_head(
 240                      'order', 'load_order', 'plugin', true, $switch_dir, '', '',
 241                          (('load_order' == $sort) ? "$dir " : '').'txp-list-col-load-order'
 242                  ).
 243                  hCell(
 244                      gTxt('manage'), '', ' class="txp-list-col-manage" scope="col"'
 245                  )
 246              ).
 247              n.tag_end('thead').
 248              n.tag_start('tbody');
 249  
 250          while ($a = nextRow($rs)) {
 251              foreach ($a as $key => $value) {
 252                  $$key = txpspecialchars($value);
 253              }
 254  
 255              // Fix up the description for clean cases.
 256              $description = preg_replace(
 257                  array(
 258                      '#&lt;br /&gt;#',
 259                      '#&lt;(/?(a|b|i|em|strong))&gt;#',
 260                      '#&lt;a href=&quot;(https?|\.|\/|ftp)([A-Za-z0-9:/?.=_]+?)&quot;&gt;#',
 261                  ),
 262                  array(
 263                      '<br />',
 264                      '<$1>',
 265                      '<a href="$1$2">',
 266                  ),
 267                  $description
 268              );
 269  
 270              if (!empty($help)) {
 271                  $help = href(gTxt('help'), array(
 272                      'event' => 'plugin',
 273                      'step'  => 'plugin_help',
 274                      'name'  => $name,
 275                  ), array('class' => 'plugin-help'));
 276              }
 277  
 278              if ($flags & PLUGIN_HAS_PREFS) {
 279                  $plugin_prefs = span(
 280                      sp.span('&#124;', array('role' => 'separator')).
 281                      sp.href(gTxt('options'), array('event' => 'plugin_prefs.'.$name)),
 282                      array('class' => 'plugin-prefs')
 283                  );
 284              } else {
 285                  $plugin_prefs = '';
 286              }
 287  
 288              $manage = array();
 289  
 290              if ($help) {
 291                  $manage[] = $help;
 292              }
 293  
 294              if ($plugin_prefs) {
 295                  $manage[] = $plugin_prefs;
 296              }
 297  
 298              $manage_items = ($manage) ? join($manage) : '-';
 299              $edit_url = array(
 300                  'event'         => 'plugin',
 301                  'step'          => 'plugin_edit',
 302                  'name'          => $name,
 303                  'sort'          => $sort,
 304                  'dir'           => $dir,
 305                  'page'          => $page,
 306                  'search_method' => $search_method,
 307                  'crit'          => $crit,
 308                  '_txp_token'    => form_token(),
 309              );
 310  
 311              $statusLink = status_link($status, $name, yes_no($status));
 312              $statusDisplay = (!$publicOn && $type == 0) || (!$adminOn && in_array($type, array(3, 4))) || (!$publicOn && !$adminOn && in_array($type, array(0, 1, 3, 4, 5)))
 313                  ? tag($statusLink, 's')
 314                  : $statusLink;
 315  
 316              $contentBlock .= tr(
 317                  td(
 318                      fInput('checkbox', 'selected[]', $name), '', 'txp-list-col-multi-edit'
 319                  ).
 320                  hCell(
 321                      href($name, $edit_url), '', ' class="txp-list-col-name" scope="row"'
 322                  ).
 323                  td(
 324                      ($author_uri ? href($author, $a['author_uri'], array('rel' => 'external')) : $author), '', 'txp-list-col-author'
 325                  ).
 326                  td(
 327                      $version, '', 'txp-list-col-version'
 328                  ).
 329                  td(
 330                      ($modified ? span(gTxt('yes'), array('class' => 'warning')) : ''), '', 'txp-list-col-modified'
 331                  ).
 332                  td(
 333                      $description, '', 'txp-list-col-description'
 334                  ).
 335                  td(
 336                      $statusDisplay, '', 'txp-list-col-status'
 337                  ).
 338                  td(
 339                      $load_order, '', 'txp-list-col-load-order'
 340                  ).
 341                  td(
 342                      $manage_items, '', 'txp-list-col-manage'
 343                  ),
 344                  $status ? ' class="active"' : ''
 345              );
 346  
 347              unset($name);
 348          }
 349  
 350          $contentBlock .=
 351              n.tag_end('tbody').
 352              n.tag_end('table').
 353              n.tag_end('div'). // End of .txp-listtables.
 354              plugin_multiedit_form($page, $sort, $dir, $crit, $search_method).
 355              tInput().
 356              n.tag_end('form');
 357      }
 358  
 359      if (!is_dir(PLUGINPATH) || !is_writeable(PLUGINPATH)) {
 360          $createBlock =
 361              graf(
 362                  span(null, array('class' => 'ui-icon ui-icon-alert')).' '.
 363                  gTxt('plugin_dir_not_writeable', array('{plugindir}' => PLUGINPATH)),
 364                  array('class' => 'alert-block warning')
 365              ).n;
 366      } else {
 367          $createBlock = tag(plugin_form($existing_files), 'div', array('class' => 'txp-control-panel'));
 368      }
 369  
 370      $pageBlock = $paginator->render().
 371          nav_form('plugin', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit);
 372  
 373      $table = new \Textpattern\Admin\Table();
 374      echo $table->render(compact('total', 'crit') + array('heading' => 'tab_plugins'), $searchBlock, $createBlock, $contentBlock, $pageBlock);
 375  }
 376  
 377  /**
 378   * Toggles a plugin's status.
 379   */
 380  
 381  function switch_status()
 382  {
 383      extract(array_map('assert_string', gpsa(array('thing', 'value'))));
 384      $change = ($value == gTxt('yes')) ? 0 : 1;
 385  
 386      Txp::get('\Textpattern\Plugin\Plugin')->changestatus($thing, $change);
 387  
 388      echo gTxt($change ? 'yes' : 'no');
 389  }
 390  
 391  /**
 392   * Renders and outputs the plugin editor panel.
 393   */
 394  
 395  function plugin_edit()
 396  {
 397      $name = gps('name');
 398      pagetop(gTxt('edit_plugins'));
 399  
 400      echo plugin_edit_form($name);
 401  }
 402  
 403  /**
 404   * Plugin help viewer panel.
 405   */
 406  
 407  function plugin_help()
 408  {
 409      $name = gps('name');
 410  
 411      // Note that TEXTPATTERN_DEFAULT_LANG is not used here.
 412      // The assumption is that plugin help is in English, unless otherwise stated.
 413      $default_lang = $lang_plugin = 'en';
 414  
 415      pagetop(gTxt('plugin_help'));
 416      $help = ($name) ? safe_field('help', 'txp_plugin', "name = '".doSlash($name)."'") : '';
 417      $helpArray = do_list($help, n);
 418  
 419      if (preg_match('/^#@language\s+(.+)$/', $helpArray[0], $m)) {
 420          $lang_plugin = $m[1];
 421          $help = implode(n, array_slice($helpArray, 1));
 422      }
 423  
 424      if ($lang_plugin !== $default_lang) {
 425          $direction = safe_field('data', 'txp_lang', "lang = '".doSlash($lang_plugin)."' AND name='lang_dir'");
 426      }
 427  
 428      if (empty($direction) || !in_array($direction, array('ltr', 'rtl'))) {
 429          $direction = 'ltr';
 430      }
 431  
 432      echo n.tag($help, 'div', array(
 433          'class' => 'txp-layout-textbox',
 434          'lang'  => $lang_plugin,
 435          'dir'   => $direction,
 436      ));
 437  }
 438  
 439  /**
 440   * Renders an editor form for plugins.
 441   *
 442   * @param  string $name The plugin
 443   * @return string HTML
 444   */
 445  
 446  function plugin_edit_form($name = '')
 447  {
 448      assert_string($name);
 449      $code = ($name) ? fetch('code', 'txp_plugin', 'name', $name) : '';
 450      $thing = ($code) ? $code : '';
 451  
 452      return
 453          form(
 454              hed(gTxt('edit_plugin', array('{name}' => $name)), 2).
 455              '<textarea class="code" id="plugin_code" name="code" cols="'.INPUT_XLARGE.'" rows="'.TEXTAREA_HEIGHT_LARGE.'" dir="ltr">'.txpspecialchars($thing).'</textarea>'.
 456              graf(
 457                  sLink('plugin', '', gTxt('cancel'), 'txp-button').
 458                  fInput('submit', '', gTxt('save'), 'publish'),
 459                  array('class' => 'txp-edit-actions')
 460              ).
 461              eInput('plugin').
 462              sInput('plugin_save').
 463              hInput('name', $name).
 464              hInput('sort', gps('sort')).
 465              hInput('dir', gps('dir')).
 466              hInput('page', gps('page')).
 467              hInput('search_method', gps('search_method')).
 468              hInput('crit', gps('crit')).
 469              hInput('name', $name), '', '', 'post', 'edit-plugin-code', '', 'plugin_details');
 470  }
 471  
 472  /**
 473   * Saves edited plugin code.
 474   */
 475  
 476  function plugin_save()
 477  {
 478      extract(array_map('assert_string', gpsa(array('name', 'code'))));
 479  
 480      safe_update('txp_plugin', "code = '".doSlash($code)."'", "name = '".doSlash($name)."'");
 481      Txp::get('\Textpattern\Plugin\Plugin')->updateFile($name, $code);
 482      $message = gTxt('plugin_saved', array('{name}' => $name));
 483  
 484      plugin_list($message);
 485  }
 486  
 487  /**
 488   * Renders a status link.
 489   *
 490   * @param  string $status   The new status
 491   * @param  string $name     The plugin
 492   * @param  string $linktext The label
 493   * @return string HTML
 494   * @access private
 495   * @see    asyncHref()
 496   */
 497  
 498  function status_link($status, $name, $linktext)
 499  {
 500      return asyncHref(
 501          $linktext,
 502          array(
 503              'step'  => 'switch_status',
 504              'thing' => $name,
 505          )
 506      );
 507  }
 508  
 509  /**
 510   * Plugin installation's preview step.
 511   *
 512   * Outputs a panel displaying the plugin's source code
 513   * and the included help file.
 514   */
 515  
 516  function plugin_verify()
 517  {
 518      $plugin64 = assert_string(ps('plugin'));
 519  
 520      if ($plugin = Txp::get('\Textpattern\Plugin\Plugin')->extract($plugin64)) {
 521          $source = '';
 522          $textpack = '';
 523  
 524          if (isset($plugin['help_raw']) && empty($plugin['allow_html_help'])) {
 525              $textile = new \Textpattern\Textile\RestrictedParser();
 526              $help_source = $textile->setLite(false)->setImages(true)->parse($plugin['help_raw']);
 527          } else {
 528              $help_source = $plugin['help'] ? str_replace(array(t), array(sp.sp.sp.sp), txpspecialchars($plugin['help'])) : '';
 529          }
 530  
 531          if (isset($plugin['textpack'])) {
 532              $textpack = $plugin['textpack'];
 533          }
 534  
 535          $source .= txpspecialchars($plugin['code']);
 536          $sub = graf(
 537              sLink('plugin', '', gTxt('cancel'), 'txp-button').
 538              fInput('submit', '', gTxt('install'), 'publish'),
 539              array('class' => 'txp-edit-actions')
 540          );
 541  
 542          pagetop(gTxt('verify_plugin'));
 543          echo form(
 544              hed(gTxt('previewing_plugin'), 2).
 545              tag(
 546                  tag($source, 'code', array(
 547                      'class' => 'language-php',
 548                      'dir'   => 'ltr',
 549                  )),
 550                  'pre', array('id' => 'preview-plugin')
 551              ).
 552              ($help_source
 553                  ? hed(gTxt('plugin_help'), 2).
 554                      tag(
 555                          tag($help_source, 'code', array(
 556                              'class' => 'language-markup',
 557                              'dir'   => 'ltr',
 558                          )),
 559                          'pre', array('id' => 'preview-help')
 560                      )
 561                  : ''
 562              ).
 563              ($textpack
 564                  ? hed(tag('Textpack', 'bdi', array('dir' => 'ltr')), 2).
 565                      tag(
 566                          tag($textpack, 'code', array('dir' => 'ltr')), 'pre', array('id' => 'preview-textpack')
 567                      )
 568                  : ''
 569              ).
 570              $sub.
 571              sInput('plugin_install').
 572              eInput('plugin').
 573              hInput('plugin64', $plugin64), '', '', 'post', 'plugin-info', '', 'plugin_preview'
 574          );
 575  
 576          return;
 577      }
 578  
 579      plugin_list(array(gTxt('bad_plugin_code'), E_ERROR));
 580  }
 581  
 582  /**
 583   * Installs a plugin.
 584   */
 585  
 586  function plugin_install()
 587  {
 588      $plugin64 = assert_string(ps('plugin64'));
 589      $message = Txp::get('\Textpattern\Plugin\Plugin')->install($plugin64);
 590  
 591      plugin_list($message);
 592  }
 593  
 594  /**
 595   * Uploads a plugin.
 596   */
 597  
 598  function plugin_upload()
 599  {
 600      $plugin = array();
 601  
 602      if ($_FILES["theplugin"]["name"]) {
 603          $filename = $_FILES["theplugin"]["name"];
 604          $source = $_FILES["theplugin"]["tmp_name"];
 605          $target_path = rtrim(get_pref('tempdir', PLUGINPATH), DS).DS.$filename;
 606  
 607          if (move_uploaded_file($source, $target_path)) {
 608              extract(pathinfo($target_path));
 609  
 610              if (strtolower($extension) === 'php') {
 611                  $write = true;
 612                  $plugin = Txp::get('\Textpattern\Plugin\Plugin')->read(array($filename, $target_path));
 613              } elseif (class_exists('ZipArchive')) {
 614                  $zip = new ZipArchive();
 615                  $x = $zip->open($target_path);
 616  
 617                  if ($x === true) {
 618                      for ($i = 0; $i < $zip->numFiles; $i++) {
 619                          if (strpos($zip->getNameIndex($i), $filename.'/') !== 0) {
 620                              $makedir = true;
 621  
 622                              break;
 623                          }
 624                      }
 625  
 626                      $zip->extractTo(PLUGINPATH.(empty($makedir) ? '' : DS.$filename));
 627                      $zip->close();
 628                      $plugin = Txp::get('\Textpattern\Plugin\Plugin')->read($filename);
 629                  }
 630              }
 631  
 632              unlink($target_path);
 633          }
 634      }
 635  
 636      $message = Txp::get('\Textpattern\Plugin\Plugin')->install($plugin, null, !empty($write));
 637      plugin_list($message);
 638  }
 639  
 640  /**
 641   * Uploads a plugin.
 642   */
 643  
 644  function plugin_load()
 645  {
 646      $plugin = array();
 647  
 648      if ($filename = gps('filename')) {
 649          $plugin = Txp::get('\Textpattern\Plugin\Plugin')->read($filename);
 650      }
 651  
 652      $message = Txp::get('\Textpattern\Plugin\Plugin')->install($plugin);
 653      plugin_list($message);
 654  }
 655  
 656  /**
 657   * Renders a plugin installation form.
 658   *
 659   * @param  array  $existing_files
 660   * @return string HTML
 661   * @access private
 662   * @see    form()
 663   */
 664  
 665  function plugin_form($existing_files = array())
 666  {
 667      return tag(
 668          tag(gTxt('upload_plugin'), 'label', ' for="plugin-upload"').popHelp('upload_plugin').
 669          n.tag_void('input', array(
 670              'type'     => 'file',
 671              'name'     => 'theplugin',
 672              'id'       => 'plugin-upload',
 673              'accept'   => (class_exists('ZipArchive') ? "application/x-zip-compressed, application/zip, " : '').".php",
 674              'required' => 'required',
 675          )).
 676          fInput('submit', 'install_new', gTxt('upload')).
 677          eInput('plugin').
 678          sInput('plugin_upload').
 679          tInput().n, 'form', array(
 680              'class'        => 'plugin-file',
 681              'id'           => 'plugin_upload_form',
 682              'method'       => 'post',
 683              'action'       => 'index.php',
 684              'enctype'      => 'multipart/form-data'
 685          )
 686      ).br.
 687      ($existing_files ? form(
 688          eInput('plugin').
 689          sInput('plugin_load').
 690          tag(gTxt('import_from_disk'), 'label', array('for' => 'file-existing')).
 691          selectInput('filename', $existing_files, null, false, '', 'file-existing').
 692          fInput('submit', '', gTxt('import')),
 693          '', '', 'post', 'assign-existing-form txp-async-update', '', 'assign_file'
 694      ) : '').
 695      form(
 696          tag(gTxt('install_plugin'), 'label', ' for="plugin-install"').popHelp('install_plugin').
 697          n.'<textarea class="code" id="plugin-install" name="plugin" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_SMALL.'" dir="ltr" required="required"></textarea>'.
 698          fInput('submit', 'install_new', gTxt('upload')).
 699          eInput('plugin').
 700          sInput('plugin_verify'), '', '', 'post', 'plugin-data', '', 'plugin_install_form'
 701      );
 702  }
 703  
 704  /**
 705   * Updates pageby value.
 706   */
 707  
 708  function plugin_change_pageby()
 709  {
 710      global $event;
 711  
 712      Txp::get('\Textpattern\Admin\Paginator', $event, 'plugin')->change();
 713      plugin_list();
 714  }
 715  
 716  /**
 717   * Renders a multi-edit form widget for plugins.
 718   *
 719   * @param  int    $page          The current page
 720   * @param  string $sort          The sort criteria
 721   * @param  string $dir           The sort direction
 722   * @param  string $crit          The search term
 723   * @param  string $search_method The search method
 724   * @return string HTML
 725   */
 726  
 727  function plugin_multiedit_form($page, $sort, $dir, $crit, $search_method)
 728  {
 729      $orders = selectInput('order', array(
 730          1 => 1,
 731          2 => 2,
 732          3 => 3,
 733          4 => 4,
 734          5 => 5,
 735          6 => 6,
 736          7 => 7,
 737          8 => 8,
 738          9 => 9,
 739      ), 5, false);
 740  
 741      $methods = array(
 742          'changestatus' => array(
 743              'label' => gTxt('changestatus'),
 744              'html'  => onoffRadio('setStatus', 1),
 745          ),
 746          'changeorder'  => array(
 747              'label' => gTxt('changeorder'),
 748              'html'  => $orders,
 749          ),
 750          'update'       => gTxt('update_from_disk'),
 751          'delete'       => array(
 752              'label' => gTxt('delete'),
 753              'html' => checkbox2('sync', gps('sync'), 0, 'sync').n.
 754                  tag(gTxt('plugin_delete_entirely'), 'label', array('for' => 'sync'))
 755          )
 756      );
 757  
 758      return multi_edit($methods, 'plugin', 'plugin_multi_edit', $page, $sort, $dir, $crit, $search_method);
 759  }
 760  
 761  /**
 762   * Processes multi-edit actions.
 763   */
 764  
 765  function plugin_multi_edit()
 766  {
 767      $selected = ps('selected');
 768      $method = assert_string(ps('edit_method'));
 769  
 770      if (!$selected or !is_array($selected)) {
 771          return plugin_list();
 772      }
 773  
 774      $plugin = new \Textpattern\Plugin\Plugin();
 775  
 776      switch ($method) {
 777          case 'delete':
 778              foreach ($selected as $name) {
 779                  $plugin->delete($name);
 780              }
 781              break;
 782          case 'changestatus':
 783              foreach ($selected as $name) {
 784                  $plugin->changeStatus($name, ps('setStatus'));
 785              }
 786              break;
 787          case 'changeorder':
 788              foreach ($selected as $name) {
 789                  $plugin->changeOrder($name, ps('order'));
 790              }
 791              break;
 792          case 'update':
 793              foreach ($selected as $name) {
 794                  $plugin->install($plugin->read($name));
 795              }
 796              break;
 797      }
 798  
 799      $message = gTxt('plugin_'.($method == 'delete' ? 'deleted' : 'updated'), array('{name}' => join(', ', $selected)));
 800  
 801      plugin_list($message);
 802  }

title

Description

title

Description

title

Description

title

title

Body