Textpattern | PHP Cross Reference | Content Management Systems |
Description: Plugins panel.
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * http://textpattern.com 6 * 7 * Copyright (C) 2005 Dean Allen 8 * Copyright (C) 2016 The Textpattern Development Team 9 * 10 * This file is part of Textpattern. 11 * 12 * Textpattern is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation, version 2. 15 * 16 * Textpattern is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with Textpattern. If not, see <http://www.gnu.org/licenses/>. 23 */ 24 25 /** 26 * Plugins panel. 27 * 28 * @package Admin\Plugin 29 */ 30 31 if (!defined('txpinterface')) { 32 die('txpinterface is undefined.'); 33 } 34 35 if ($event == 'plugin') { 36 require_privs('plugin'); 37 38 $available_steps = array( 39 'plugin_edit' => true, 40 'plugin_help' => false, 41 'plugin_list' => false, 42 'plugin_install' => true, 43 'plugin_save' => true, 44 'plugin_verify' => true, 45 'switch_status' => true, 46 'plugin_multi_edit' => true, 47 ); 48 49 if ($step && bouncer($step, $available_steps)) { 50 $step(); 51 } else { 52 plugin_list(); 53 } 54 } 55 56 /** 57 * The main panel listing all installed plugins. 58 * 59 * @param string|array $message The activity message 60 */ 61 62 function plugin_list($message = '') 63 { 64 global $event; 65 66 pagetop(gTxt('tab_plugins'), $message); 67 68 extract(gpsa(array( 69 'sort', 70 'dir', 71 ))); 72 73 if ($sort === '') { 74 $sort = get_pref('plugin_sort_column', 'name'); 75 } else { 76 if (!in_array($sort, array('name', 'status', 'author', 'version', 'modified', 'load_order'))) { 77 $sort = 'name'; 78 } 79 80 set_pref('plugin_sort_column', $sort, 'plugin', 2, '', 0, PREF_PRIVATE); 81 } 82 83 if ($dir === '') { 84 $dir = get_pref('plugin_sort_dir', 'asc'); 85 } else { 86 $dir = ($dir == 'desc') ? "desc" : "asc"; 87 set_pref('plugin_sort_dir', $dir, 'plugin', 2, '', 0, PREF_PRIVATE); 88 } 89 90 $sort_sql = "$sort $dir"; 91 92 $switch_dir = ($dir == 'desc') ? 'asc' : 'desc'; 93 94 echo n.'<div class="txp-layout">'. 95 n.tag( 96 hed(gTxt('tab_plugins'), 1, array('class' => 'txp-heading')), 97 'div', array('class' => 'txp-layout-1col') 98 ). 99 n.tag_start('div', array( 100 'class' => 'txp-layout-1col', 101 'id' => $event.'_container', 102 )). 103 n.tag(plugin_form(), 'div', array('class' => 'txp-control-panel')); 104 105 $rs = safe_rows_start( 106 "name, status, author, author_uri, version, description, length(help) AS help, ABS(STRCMP(MD5(code), code_md5)) AS modified, load_order, flags", 107 'txp_plugin', 108 "1 = 1 ORDER BY $sort_sql" 109 ); 110 111 if ($rs and numRows($rs) > 0) { 112 echo 113 n.tag_start('form', array( 114 'class' => 'multi_edit_form', 115 'id' => 'plugin_form', 116 'name' => 'longform', 117 'method' => 'post', 118 'action' => 'index.php', 119 )). 120 n.tag_start('div', array('class' => 'txp-listtables')). 121 n.tag_start('table', array('class' => 'txp-list')). 122 n.tag_start('thead'). 123 tr( 124 hCell( 125 fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'), 126 '', ' class="txp-list-col-multi-edit" scope="col" title="'.gTxt('toggle_all_selected').'"' 127 ). 128 column_head( 129 'plugin', 'name', 'plugin', true, $switch_dir, '', '', 130 (('name' == $sort) ? "$dir " : '').'txp-list-col-name' 131 ). 132 column_head( 133 'author', 'author', 'plugin', true, $switch_dir, '', '', 134 (('author' == $sort) ? "$dir " : '').'txp-list-col-author' 135 ). 136 column_head( 137 'version', 'version', 'plugin', true, $switch_dir, '', '', 138 (('version' == $sort) ? "$dir " : '').'txp-list-col-version' 139 ). 140 column_head( 141 'plugin_modified', 'modified', 'plugin', true, $switch_dir, '', '', 142 (('modified' == $sort) ? "$dir " : '').'txp-list-col-modified' 143 ). 144 hCell(gTxt( 145 'description'), '', ' class="txp-list-col-description" scope="col"' 146 ). 147 column_head( 148 'active', 'status', 'plugin', true, $switch_dir, '', '', 149 (('status' == $sort) ? "$dir " : '').'txp-list-col-status' 150 ). 151 column_head( 152 'order', 'load_order', 'plugin', true, $switch_dir, '', '', 153 (('load_order' == $sort) ? "$dir " : '').'txp-list-col-load-order' 154 ). 155 hCell( 156 gTxt('manage'), '', ' class="txp-list-col-manage" scope="col"' 157 ) 158 ). 159 n.tag_end('thead'). 160 n.tag_start('tbody'); 161 162 while ($a = nextRow($rs)) { 163 foreach ($a as $key => $value) { 164 $$key = txpspecialchars($value); 165 } 166 167 // Fix up the description for clean cases. 168 $description = preg_replace( 169 array( 170 '#<br />#', 171 '#<(/?(a|b|i|em|strong))>#', 172 '#<a href="(https?|\.|\/|ftp)([A-Za-z0-9:/?.=_]+?)">#', 173 ), 174 array( 175 '<br />', 176 '<$1>', 177 '<a href="$1$2">', 178 ), 179 $description 180 ); 181 182 if (!empty($help)) { 183 $help = href(gTxt('help'), array( 184 'event' => 'plugin', 185 'step' => 'plugin_help', 186 'name' => $name, 187 ), array('class' => 'plugin-help')); 188 } 189 190 if ($flags & PLUGIN_HAS_PREFS) { 191 $plugin_prefs = span( 192 sp.span('|', array('role' => 'separator')). 193 sp.href(gTxt('plugin_prefs'), array('event' => 'plugin_prefs.'.$name)), 194 array('class' => 'plugin-prefs') 195 ); 196 } else { 197 $plugin_prefs = ''; 198 } 199 200 $manage = array(); 201 202 if ($help) { 203 $manage[] = $help; 204 } 205 206 if ($plugin_prefs) { 207 $manage[] = $plugin_prefs; 208 } 209 210 $manage_items = ($manage) ? join($manage) : '-'; 211 $edit_url = eLink('plugin', 'plugin_edit', 'name', $name, $name); 212 213 echo tr( 214 td( 215 fInput('checkbox', 'selected[]', $name), '', 'txp-list-col-multi-edit' 216 ). 217 hCell( 218 $edit_url, '', ' class="txp-list-col-name" scope="row"' 219 ). 220 td( 221 href($author, $a['author_uri'], array('rel' => 'external')), '', 'txp-list-col-author' 222 ). 223 td( 224 $version, '', 'txp-list-col-version' 225 ). 226 td( 227 ($modified ? span(gTxt('yes'), array('class' => 'warning')) : ''), '', 'txp-list-col-modified' 228 ). 229 td( 230 $description, '', 'txp-list-col-description' 231 ). 232 td( 233 status_link($status, $name, yes_no($status)), '', 'txp-list-col-status' 234 ). 235 td( 236 $load_order, '', 'txp-list-col-load-order' 237 ). 238 td( 239 $manage_items, '', 'txp-list-col-manage' 240 ), 241 $status ? ' class="active"' : '' 242 ); 243 244 unset($name, $page, $deletelink); 245 } 246 247 echo 248 n.tag_end('tbody'). 249 n.tag_end('table'). 250 n.tag_end('div'). // End of .txp-listtables. 251 plugin_multiedit_form('', $sort, $dir, '', ''). 252 tInput(). 253 n.tag_end('form'); 254 } 255 256 echo n.tag_end('div'). // End of .txp-layout-1col. 257 n.'</div>'; // End of .txp-layout. 258 } 259 260 /** 261 * Toggles a plugin's status. 262 */ 263 264 function switch_status() 265 { 266 extract(array_map('assert_string', gpsa(array('thing', 'value')))); 267 $change = ($value == gTxt('yes')) ? 0 : 1; 268 269 safe_update('txp_plugin', "status = $change", "name = '".doSlash($thing)."'"); 270 271 if (safe_field('flags', 'txp_plugin', "name = '".doSlash($thing)."'") & PLUGIN_LIFECYCLE_NOTIFY) { 272 load_plugin($thing, true); 273 $message = callback_event("plugin_lifecycle.$thing", $change ? 'enabled' : 'disabled'); 274 } 275 276 echo gTxt($change ? 'yes' : 'no'); 277 } 278 279 /** 280 * Renders and outputs the plugin editor panel. 281 */ 282 283 function plugin_edit() 284 { 285 global $event; 286 287 $name = gps('name'); 288 pagetop(gTxt('edit_plugins')); 289 290 echo plugin_edit_form($name); 291 } 292 293 /** 294 * Plugin help viewer panel. 295 */ 296 297 function plugin_help() 298 { 299 global $event; 300 301 $name = gps('name'); 302 pagetop(gTxt('plugin_help')); 303 $help = ($name) ? safe_field('help', 'txp_plugin', "name = '".doSlash($name)."'") : ''; 304 echo n.tag($help, 'div', array('class' => 'txp-layout-textbox')); 305 } 306 307 /** 308 * Renders an editor form for plugins. 309 * 310 * @param string $name The plugin 311 * @return string HTML 312 */ 313 314 function plugin_edit_form($name = '') 315 { 316 assert_string($name); 317 $code = ($name) ? fetch('code', 'txp_plugin', 'name', $name) : ''; 318 $thing = ($code) ? $code : ''; 319 320 return 321 form( 322 hed(gTxt('edit_plugin', array('{name}' => $name)), 2). 323 graf('<textarea class="code" id="plugin_code" name="code" cols="'.INPUT_XLARGE.'" rows="'.TEXTAREA_HEIGHT_LARGE.'" dir="ltr">'.txpspecialchars($thing).'</textarea>', ' class="edit-plugin-code"'). 324 graf( 325 sLink('plugin', '', gTxt('cancel'), 'txp-button'). 326 fInput('submit', '', gTxt('save'), 'publish'), 327 array('class' => 'txp-edit-actions') 328 ). 329 eInput('plugin'). 330 sInput('plugin_save'). 331 hInput('name', $name), '', '', 'post', '', '', 'plugin_details'); 332 } 333 334 /** 335 * Saves edited plugin code. 336 */ 337 338 function plugin_save() 339 { 340 extract(doSlash(array_map('assert_string', gpsa(array('name', 'code'))))); 341 342 safe_update('txp_plugin', "code = '$code'", "name = '$name'"); 343 344 $message = gTxt('plugin_saved', array('{name}' => $name)); 345 346 plugin_list($message); 347 } 348 349 /** 350 * Renders a status link. 351 * 352 * @param string $status The new status 353 * @param string $name The plugin 354 * @param string $linktext The label 355 * @return string HTML 356 * @access private 357 * @see asyncHref() 358 */ 359 360 function status_link($status, $name, $linktext) 361 { 362 return asyncHref( 363 $linktext, 364 array('step' => 'switch_status', 'thing' => $name) 365 ); 366 } 367 368 /** 369 * Plugin installation's preview step. 370 * 371 * Outputs a panel displaying the plugin's source code 372 * and the included help file. 373 */ 374 375 function plugin_verify() 376 { 377 global $event; 378 379 if (ps('txt_plugin')) { 380 $plugin = join("\n", file($_FILES['theplugin']['tmp_name'])); 381 } else { 382 $plugin = assert_string(ps('plugin')); 383 } 384 385 // Check for pre-4.0 style plugin. 386 if (strpos($plugin, '$plugin=\'') !== false) { 387 // Try to increase PCRE's backtrack limit in PHP 5.2+ to accommodate to 388 // x-large plugins. See https://bugs.php.net/bug.php?id=40846. 389 @ini_set('pcre.backtrack_limit', '1000000'); 390 $plugin = preg_replace('@.*\$plugin=\'([\w=+/]+)\'.*@s', '$1', $plugin); 391 // Have we hit yet another PCRE restriction? 392 if ($plugin === null) { 393 plugin_list(array(gTxt('plugin_pcre_error', array('{errno}' => preg_last_error())), E_ERROR)); 394 395 return; 396 } 397 } 398 399 // Strip out #comment lines. 400 $plugin = preg_replace('/^#.*$/m', '', $plugin); 401 402 if ($plugin === null) { 403 plugin_list(array(gTxt('plugin_pcre_error', array('{errno}' => preg_last_error())), E_ERROR)); 404 405 return; 406 } 407 408 if (isset($plugin)) { 409 $plugin_encoded = $plugin; 410 $plugin = base64_decode($plugin); 411 412 if (strncmp($plugin, "\x1F\x8B", 2) === 0) { 413 if (function_exists('gzinflate')) { 414 $plugin = gzinflate(substr($plugin, 10)); 415 } else { 416 plugin_list(array(gTxt('plugin_compression_unsupported'), E_ERROR)); 417 418 return; 419 } 420 } 421 422 if ($plugin = @unserialize($plugin)) { 423 if (is_array($plugin)) { 424 $source = ''; 425 426 if (isset($plugin['help_raw']) && empty($plugin['allow_html_help'])) { 427 $textile = new \Textpattern\Textile\Parser(); 428 $help_source = $textile->textileRestricted($plugin['help_raw'], 0, 0); 429 } else { 430 $help_source = highlight_string($plugin['help'], true); 431 } 432 433 $source .= highlight_string('<?php'.$plugin['code'].'?>', true); 434 $sub = graf( 435 sLink('plugin', '', gTxt('cancel'), 'txp-button'). 436 fInput('submit', '', gTxt('install'), 'publish'), 437 array('class' => 'txp-edit-actions') 438 ); 439 440 pagetop(gTxt('verify_plugin')); 441 echo form( 442 hed(gTxt('previewing_plugin'), 2). 443 tag($source, 'div', ' class="code" id="preview-plugin" dir="ltr"'). 444 hed(gTxt('plugin_help').':', 2). 445 tag($help_source, 'div', ' class="code" id="preview-help" dir="ltr"'). 446 $sub. 447 sInput('plugin_install'). 448 eInput('plugin'). 449 hInput('plugin64', $plugin_encoded), '', '', 'post', 'plugin-info', '', 'plugin_preview' 450 ); 451 452 return; 453 } 454 } 455 } 456 457 plugin_list(array(gTxt('bad_plugin_code'), E_ERROR)); 458 } 459 460 /** 461 * Installs a plugin. 462 */ 463 464 function plugin_install() 465 { 466 $plugin = assert_string(ps('plugin64')); 467 468 if (strpos($plugin, '$plugin=\'') !== false) { 469 @ini_set('pcre.backtrack_limit', '1000000'); 470 $plugin = preg_replace('@.*\$plugin=\'([\w=+/]+)\'.*@s', '$1', $plugin); 471 } 472 473 $plugin = preg_replace('/^#.*$/m', '', $plugin); 474 475 if (trim($plugin)) { 476 $plugin = base64_decode($plugin); 477 478 if (strncmp($plugin, "\x1F\x8B", 2) === 0) { 479 $plugin = gzinflate(substr($plugin, 10)); 480 } 481 482 if ($plugin = unserialize($plugin)) { 483 if (is_array($plugin)) { 484 extract($plugin); 485 486 $type = empty($type) ? 0 : min(max(intval($type), 0), 5); 487 $order = empty($order) ? 5 : min(max(intval($order), 1), 9); 488 $flags = empty($flags) ? 0 : intval($flags); 489 $exists = fetch('name', 'txp_plugin', 'name', $name); 490 491 if (isset($help_raw) && empty($plugin['allow_html_help'])) { 492 // Default: help is in Textile format. 493 $textile = new \Textpattern\Textile\Parser(); 494 $help = $textile->textileRestricted($help_raw, 0, 0); 495 } 496 497 if ($exists) { 498 $rs = safe_update( 499 'txp_plugin', 500 "type = $type, 501 author = '".doSlash($author)."', 502 author_uri = '".doSlash($author_uri)."', 503 version = '".doSlash($version)."', 504 description = '".doSlash($description)."', 505 help = '".doSlash($help)."', 506 code = '".doSlash($code)."', 507 code_restore = '".doSlash($code)."', 508 code_md5 = '".doSlash($md5)."', 509 flags = $flags", 510 "name = '".doSlash($name)."'" 511 ); 512 } else { 513 $rs = safe_insert( 514 'txp_plugin', 515 "name = '".doSlash($name)."', 516 status = 0, 517 type = $type, 518 author = '".doSlash($author)."', 519 author_uri = '".doSlash($author_uri)."', 520 version = '".doSlash($version)."', 521 description = '".doSlash($description)."', 522 help = '".doSlash($help)."', 523 code = '".doSlash($code)."', 524 code_restore = '".doSlash($code)."', 525 code_md5 = '".doSlash($md5)."', 526 load_order = '".$order."', 527 flags = $flags" 528 ); 529 } 530 531 if ($rs and $code) { 532 if (!empty($textpack)) { 533 // Plugins tag their Textpack by plugin name. 534 // The ownership may be overridden in the Textpack itself. 535 $textpack = "#@owner {$name}".n.$textpack; 536 install_textpack($textpack, false); 537 } 538 539 if ($flags & PLUGIN_LIFECYCLE_NOTIFY) { 540 load_plugin($name, true); 541 $message = callback_event("plugin_lifecycle.$name", 'installed'); 542 } 543 544 if (empty($message)) { 545 $message = gTxt('plugin_installed', array('{name}' => $name)); 546 } 547 548 plugin_list($message); 549 550 return; 551 } else { 552 $message = array(gTxt('plugin_install_failed', array('{name}' => $name)), E_ERROR); 553 plugin_list($message); 554 555 return; 556 } 557 } 558 } 559 } 560 561 plugin_list(array(gTxt('bad_plugin_code'), E_ERROR)); 562 } 563 564 /** 565 * Renders a plugin installation form. 566 * 567 * @return string HTML 568 * @access private 569 * @see form() 570 */ 571 572 function plugin_form() 573 { 574 return form( 575 tag(gTxt('install_plugin'), 'label', ' for="plugin-install"').popHelp('install_plugin'). 576 '<textarea class="code" id="plugin-install" name="plugin" cols="'.INPUT_LARGE.'" rows="'.TEXTAREA_HEIGHT_SMALL.'" dir="ltr"></textarea>'. 577 fInput('submit', 'install_new', gTxt('upload')). 578 eInput('plugin'). 579 sInput('plugin_verify'), '', '', 'post', 'plugin-data', '', 'plugin_install_form'); 580 } 581 582 /** 583 * Renders a multi-edit form widget for plugins. 584 * 585 * @param int $page The current page 586 * @param string $sort The sort criteria 587 * @param string $dir The sort direction 588 * @param string $crit The search term 589 * @param string $search_method The search method 590 * @return string HTML 591 */ 592 593 function plugin_multiedit_form($page, $sort, $dir, $crit, $search_method) 594 { 595 $orders = selectInput('order', array( 596 1 => 1, 597 2 => 2, 598 3 => 3, 599 4 => 4, 600 5 => 5, 601 6 => 6, 602 7 => 7, 603 8 => 8, 604 9 => 9, 605 ), 5, false); 606 607 $methods = array( 608 'changestatus' => gTxt('changestatus'), 609 'changeorder' => array('label' => gTxt('changeorder'), 'html' => $orders), 610 'delete' => gTxt('delete'), 611 ); 612 613 return multi_edit($methods, 'plugin', 'plugin_multi_edit', $page, $sort, $dir, $crit, $search_method); 614 } 615 616 /** 617 * Processes multi-edit actions. 618 */ 619 620 function plugin_multi_edit() 621 { 622 $selected = ps('selected'); 623 $method = assert_string(ps('edit_method')); 624 625 if (!$selected or !is_array($selected)) { 626 return plugin_list(); 627 } 628 629 $where = "name IN ('".join("','", doSlash($selected))."')"; 630 631 switch ($method) { 632 case 'delete': 633 foreach ($selected as $name) { 634 if (safe_field("flags", 'txp_plugin', "name = '".doSlash($name)."'") & PLUGIN_LIFECYCLE_NOTIFY) { 635 load_plugin($name, true); 636 callback_event("plugin_lifecycle.$name", 'disabled'); 637 callback_event("plugin_lifecycle.$name", 'deleted'); 638 } 639 } 640 // Remove plugins. 641 safe_delete('txp_plugin', $where); 642 // Remove plugin's l10n strings. 643 safe_delete('txp_lang', "owner IN ('".join("','", doSlash($selected))."')"); 644 break; 645 case 'changestatus': 646 foreach ($selected as $name) { 647 if (safe_field("flags", 'txp_plugin', "name = '".doSlash($name)."'") & PLUGIN_LIFECYCLE_NOTIFY) { 648 $status = safe_field("status", 'txp_plugin', "name = '".doSlash($name)."'"); 649 load_plugin($name, true); 650 // Note: won't show returned messages anywhere due to 651 // potentially overwhelming verbiage. 652 callback_event("plugin_lifecycle.$name", $status ? 'disabled' : 'enabled'); 653 } 654 } 655 safe_update('txp_plugin', "status = (1 - status)", $where); 656 break; 657 case 'changeorder': 658 $order = min(max(intval(ps('order')), 1), 9); 659 safe_update('txp_plugin', "load_order = $order", $where); 660 break; 661 } 662 663 $message = gTxt('plugin_'.($method == 'delete' ? 'deleted' : 'updated'), array('{name}' => join(', ', $selected))); 664 665 plugin_list($message); 666 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title