Textpattern | PHP Cross Reference | Content Management Systems |
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 '#<br />#', 259 '#<(/?(a|b|i|em|strong))>#', 260 '#<a href="(https?|\.|\/|ftp)([A-Za-z0-9:/?.=_]+?)">#', 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('|', 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
Body
title
Description
Body
title
Description
Body
title
Body
title