Changeset 3699:77a12236e993 for inc/admin
- Timestamp:
- 02/14/18 10:14:33 (7 years ago)
- Branch:
- default
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
inc/admin/lib.moduleslist.php
r3640 r3699 10 10 # 11 11 # -- END LICENSE BLOCK ----------------------------------------- 12 if (!defined('DC_ADMIN_CONTEXT')) { return;}12 if (!defined('DC_ADMIN_CONTEXT')) {return;} 13 13 14 14 /** … … 21 21 class adminModulesList 22 22 { 23 public $core; /**< @var object dcCore instance */ 24 public $modules; /**< @var object dcModules instance */ 25 public $store; /**< @var object dcStore instance */ 26 27 public static $allow_multi_install = false; /**< @var boolean Work with multiple root directories */ 28 public static $distributed_modules = array(); /**< @var array List of modules distributed with Dotclear */ 29 30 protected $list_id = 'unknow'; /**< @var string Current list ID */ 31 protected $data = array(); /**< @var array Current modules */ 32 33 protected $config_module = ''; /**< @var string Module ID to configure */ 34 protected $config_file = ''; /**< @var string Module path to configure */ 35 protected $config_content = ''; /**< @var string Module configuration page content */ 36 37 protected $path = false; /**< @var string Modules root directory */ 38 protected $path_writable = false; /**< @var boolean Indicate if modules root directory is writable */ 39 protected $path_pattern = false; /**< @var string Directory pattern to work on */ 40 41 protected $page_url = ''; /**< @var string Page URL */ 42 protected $page_qs = '?'; /**< @var string Page query string */ 43 protected $page_tab = ''; /**< @var string Page tab */ 44 protected $page_redir = ''; /**< @var string Page redirection */ 45 46 public static $nav_indexes = 'abcdefghijklmnopqrstuvwxyz0123456789'; /**< @var string Index list */ 47 protected $nav_list = array(); /**< @var array Index list with special index */ 48 protected $nav_special = 'other'; /**< @var string Text for other special index */ 49 50 protected $sort_field = 'sname'; /**< @var string Field used to sort modules */ 51 protected $sort_asc = true; /**< @var boolean Sort order asc */ 52 53 /** 54 * Constructor. 55 * 56 * Note that this creates dcStore instance. 57 * 58 * @param object $modules dcModules instance 59 * @param string $modules_root Modules root directories 60 * @param string $xml_url URL of modules feed from repository 61 */ 62 public function __construct(dcModules $modules, $modules_root, $xml_url) 63 { 64 $this->core = $modules->core; 65 $this->modules = $modules; 66 $this->store = new dcStore($modules, $xml_url); 67 68 $this->page_url = $this->core->adminurl->get('admin.plugins'); 69 70 $this->setPath($modules_root); 71 $this->setIndex(__('other')); 72 } 73 74 /** 75 * Begin a new list. 76 * 77 * @param string $id New list ID 78 * @return adminModulesList self instance 79 */ 80 public function setList($id) 81 { 82 $this->data = array(); 83 $this->page_tab = ''; 84 $this->list_id = $id; 85 86 return $this; 87 } 88 89 /** 90 * Get list ID. 91 * 92 * @return List ID 93 */ 94 public function getList() 95 { 96 return $this->list_id; 97 } 98 99 /// @name Modules root directory methods 100 //@{ 101 /** 102 * Set path info. 103 * 104 * @param string $root Modules root directories 105 * @return adminModulesList self instance 106 */ 107 protected function setPath($root) 108 { 109 $paths = explode(PATH_SEPARATOR, $root); 110 $path = array_pop($paths); 111 unset($paths); 112 113 $this->path = $path; 114 if (is_dir($path) && is_writeable($path)) { 115 $this->path_writable = true; 116 $this->path_pattern = preg_quote($path,'!'); 117 } 118 119 return $this; 120 } 121 122 /** 123 * Get modules root directory. 124 * 125 * @return Path to work on 126 */ 127 public function getPath() 128 { 129 return $this->path; 130 } 131 132 /** 133 * Check if modules root directory is writable. 134 * 135 * @return True if directory is writable 136 */ 137 public function isWritablePath() 138 { 139 return $this->path_writable; 140 } 141 142 /** 143 * Check if root directory of a module is deletable. 144 * 145 * @param string $root Module root directory 146 * @return True if directory is delatable 147 */ 148 public function isDeletablePath($root) 149 { 150 return $this->path_writable 151 && (preg_match('!^'.$this->path_pattern.'!', $root) || defined('DC_DEV') && DC_DEV) 152 && $this->core->auth->isSuperAdmin(); 153 } 154 //@} 155 156 /// @name Page methods 157 //@{ 158 /** 159 * Set page base URL. 160 * 161 * @param string $url Page base URL 162 * @return adminModulesList self instance 163 */ 164 public function setURL($url) 165 { 166 $this->page_qs = strpos('?', $url) ? '&' : '?'; 167 $this->page_url = $url; 168 169 return $this; 170 } 171 172 /** 173 * Get page URL. 174 * 175 * @param string|array $queries Additionnal query string 176 * @param booleany $with_tab Add current tab to URL end 177 * @return Clean page URL 178 */ 179 public function getURL($queries='', $with_tab=true) 180 { 181 return $this->page_url. 182 (!empty($queries) ? $this->page_qs : ''). 183 (is_array($queries) ? http_build_query($queries) : $queries). 184 ($with_tab && !empty($this->page_tab) ? '#'.$this->page_tab : ''); 185 } 186 187 /** 188 * Set page tab. 189 * 190 * @param string $tab Page tab 191 * @return adminModulesList self instance 192 */ 193 public function setTab($tab) 194 { 195 $this->page_tab = $tab; 196 197 return $this; 198 } 199 200 /** 201 * Get page tab. 202 * 203 * @return Page tab 204 */ 205 public function getTab() 206 { 207 return $this->page_tab; 208 } 209 210 /** 211 * Set page redirection. 212 * 213 * @param string $default Default redirection 214 * @return adminModulesList self instance 215 */ 216 public function setRedir($default='') 217 { 218 $this->page_redir = empty($_REQUEST['redir']) ? $default : $_REQUEST['redir']; 219 220 return $this; 221 } 222 223 /** 224 * Get page redirection. 225 * 226 * @return Page redirection 227 */ 228 public function getRedir() 229 { 230 return empty($this->page_redir) ? $this->getURL() : $this->page_redir; 231 } 232 //@} 233 234 /// @name Search methods 235 //@{ 236 /** 237 * Get search query. 238 * 239 * @return Search query 240 */ 241 public function getSearch() 242 { 243 $query = !empty($_REQUEST['m_search']) ? trim($_REQUEST['m_search']) : null; 244 return strlen($query) > 2 ? $query : null; 245 } 246 247 /** 248 * Display searh form. 249 * 250 * @return adminModulesList self instance 251 */ 252 public function displaySearch() 253 { 254 $query = $this->getSearch(); 255 256 if (empty($this->data) && $query === null) { 257 return $this; 258 } 259 260 echo 261 '<div class="modules-search">'. 262 '<form action="'.$this->getURL().'" method="get">'. 263 '<p><label for="m_search" class="classic">'.__('Search in repository:').' </label><br />'. 264 form::field(array('m_search','m_search'), 30, 255, html::escapeHTML($query)). 265 '<input type="submit" value="'.__('OK').'" /> '; 266 267 if ($query) { 268 echo 269 ' <a href="'.$this->getURL().'" class="button">'.__('Reset search').'</a>'; 270 } 271 272 echo 273 '</p>'. 274 '<p class="form-note">'. 275 __('Search is allowed on multiple terms longer than 2 chars, terms must be separated by space.'). 276 '</p>'. 277 '</form>'; 278 279 if ($query) { 280 echo 281 '<p class="message">'.sprintf( 282 __('Found %d result for search "%s":', 'Found %d results for search "%s":', count($this->data)), 283 count($this->data), html::escapeHTML($query) 284 ). 285 '</p>'; 286 } 287 echo '</div>'; 288 289 return $this; 290 } 291 //@} 292 293 /// @name Navigation menu methods 294 //@{ 295 /** 296 * Set navigation special index. 297 * 298 * @return adminModulesList self instance 299 */ 300 public function setIndex($str) 301 { 302 $this->nav_special = (string) $str; 303 $this->nav_list = array_merge(str_split(self::$nav_indexes), array($this->nav_special)); 304 305 return $this; 306 } 307 308 /** 309 * Get index from query. 310 * 311 * @return Query index or default one 312 */ 313 public function getIndex() 314 { 315 return isset($_REQUEST['m_nav']) && in_array($_REQUEST['m_nav'], $this->nav_list) ? $_REQUEST['m_nav'] : $this->nav_list[0]; 316 } 317 318 /** 319 * Display navigation by index menu. 320 * 321 * @return adminModulesList self instance 322 */ 323 public function displayIndex() 324 { 325 if (empty($this->data) || $this->getSearch() !== null) { 326 return $this; 327 } 328 329 # Fetch modules required field 330 $indexes = array(); 331 foreach ($this->data as $id => $module) { 332 if (!isset($module[$this->sort_field])) { 333 continue; 334 } 335 $char = substr($module[$this->sort_field], 0, 1); 336 if (!in_array($char, $this->nav_list)) { 337 $char = $this->nav_special; 338 } 339 if (!isset($indexes[$char])) { 340 $indexes[$char] = 0; 341 } 342 $indexes[$char]++; 343 } 344 345 $buttons = array(); 346 foreach($this->nav_list as $char) { 347 # Selected letter 348 if ($this->getIndex() == $char) { 349 $buttons[] = '<li class="active" title="'.__('current selection').'"><strong> '.$char.' </strong></li>'; 350 } 351 # Letter having modules 352 elseif (!empty($indexes[$char])) { 353 $title = sprintf(__('%d result', '%d results', $indexes[$char]), $indexes[$char]); 354 $buttons[] = '<li class="btn" title="'.$title.'"><a href="'.$this->getURL('m_nav='.$char).'" title="'.$title.'"> '.$char.' </a></li>'; 355 } 356 # Letter without modules 357 else { 358 $buttons[] = '<li class="btn no-link" title="'.__('no results').'"> '.$char.' </li>'; 359 } 360 } 361 # Parse navigation menu 362 echo '<div class="pager">'.__('Browse index:').' <ul class="index">'.implode('',$buttons).'</ul></div>'; 363 364 return $this; 365 } 366 //@} 367 368 /// @name Sort methods 369 //@{ 370 /** 371 * Set default sort field. 372 * 373 * @return adminModulesList self instance 374 */ 375 public function setSort($field, $asc=true) 376 { 377 $this->sort_field = $field; 378 $this->sort_asc = (boolean) $asc; 379 380 return $this; 381 } 382 383 /** 384 * Get sort field from query. 385 * 386 * @return Query sort field or default one 387 */ 388 public function getSort() 389 { 390 return !empty($_REQUEST['m_sort']) ? $_REQUEST['m_sort'] : $this->sort_field; 391 } 392 393 /** 394 * Display sort field form. 395 * 396 * @note This method is not implemented yet 397 * @return adminModulesList self instance 398 */ 399 public function displaySort() 400 { 401 // 402 403 return $this; 404 } 405 //@} 406 407 /// @name Modules methods 408 //@{ 409 /** 410 * Set modules and sanitize them. 411 * 412 * @return adminModulesList self instance 413 */ 414 public function setModules($modules) 415 { 416 $this->data = array(); 417 if (!empty($modules) && is_array($modules)) { 418 foreach($modules as $id => $module) { 419 $this->data[$id] = self::sanitizeModule($id, $module); 420 } 421 } 422 return $this; 423 } 424 425 /** 426 * Get modules currently set. 427 * 428 * @return Array of modules 429 */ 430 public function getModules() 431 { 432 return $this->data; 433 } 434 435 /** 436 * Sanitize a module. 437 * 438 * This clean infos of a module by adding default keys 439 * and clean some of them, sanitize module can safely 440 * be used in lists. 441 * 442 * @return Array of the module informations 443 */ 444 public static function sanitizeModule($id, $module) 445 { 446 $label = empty($module['label']) ? $id : $module['label']; 447 $name = __(empty($module['name']) ? $label : $module['name']); 448 449 return array_merge( 450 # Default values 451 array( 452 'desc' => '', 453 'author' => '', 454 'version' => 0, 455 'current_version' => 0, 456 'root' => '', 457 'root_writable' => false, 458 'permissions' => null, 459 'parent' => null, 460 'priority' => 1000, 461 'standalone_config' => false, 462 'support' => '', 463 'section' => '', 464 'tags' => '', 465 'details' => '', 466 'sshot' => '', 467 'score' => 0, 468 'type' => null, 469 'require' => array(), 470 'settings' => array() 471 ), 472 # Module's values 473 $module, 474 # Clean up values 475 array( 476 'id' => $id, 477 'sid' => self::sanitizeString($id), 478 'label' => $label, 479 'name' => $name, 480 'sname' => self::sanitizeString($name) 481 ) 482 ); 483 } 484 485 /** 486 * Check if a module is part of the distribution. 487 * 488 * @param string $id Module root directory 489 * @return True if module is part of the distribution 490 */ 491 public static function isDistributedModule($id) 492 { 493 $distributed_modules = self::$distributed_modules; 494 495 return is_array($distributed_modules) && in_array($id, $distributed_modules); 496 } 497 498 /** 499 * Sort modules list by specific field. 500 * 501 * @param string $module Array of modules 502 * @param string $field Field to sort from 503 * @param bollean $asc Sort asc if true, else decs 504 * @return Array of sorted modules 505 */ 506 public static function sortModules($modules, $field, $asc=true) 507 { 508 $origin = $sorter = array(); 509 510 foreach($modules as $id => $module) { 511 $origin[] = $module; 512 $sorter[] = isset($module[$field]) ? $module[$field] : $field; 513 } 514 515 array_multisort($sorter, $asc ? SORT_ASC : SORT_DESC, $origin); 516 517 foreach($origin as $module) { 518 $final[$module['id']] = $module; 519 } 520 521 return $final; 522 } 523 524 /** 525 * Display list of modules. 526 * 527 * @param array $cols List of colones (module field) to display 528 * @param array $actions List of predefined actions to show on form 529 * @param boolean $nav_limit Limit list to previously selected index 530 * @return adminModulesList self instance 531 */ 532 public function displayModules($cols=array('name', 'version', 'desc'), $actions=array(), $nav_limit=false) 533 { 534 echo 535 '<form action="'.$this->getURL().'" method="post" class="modules-form-actions">'. 536 '<div class="table-outer">'. 537 '<table id="'.html::escapeHTML($this->list_id).'" class="modules'.(in_array('expander', $cols) ? ' expandable' : '').'">'. 538 '<caption class="hidden">'.html::escapeHTML(__('Plugins list')).'</caption><tr>'; 539 540 if (in_array('name', $cols)) { 541 $colspan = 1; 542 if (in_array('checkbox', $cols)) { 543 $colspan++; 544 } 545 if (in_array('icon', $cols)) { 546 $colspan++; 547 } 548 echo 549 '<th class="first nowrap"'.($colspan > 1 ? ' colspan="'.$colspan.'"' : '').'>'.__('Name').'</th>'; 550 } 551 552 if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) { 553 echo 554 '<th class="nowrap">'.__('Score').'</th>'; 555 } 556 557 if (in_array('version', $cols)) { 558 echo 559 '<th class="nowrap count" scope="col">'.__('Version').'</th>'; 560 } 561 562 if (in_array('current_version', $cols)) { 563 echo 564 '<th class="nowrap count" scope="col">'.__('Current version').'</th>'; 565 } 566 567 if (in_array('desc', $cols)) { 568 echo 569 '<th class="nowrap" scope="col">'.__('Details').'</th>'; 570 } 571 572 if (in_array('distrib', $cols)) { 573 echo 574 '<th'.(in_array('desc', $cols) ? '' : ' class="maximal"').'></th>'; 575 } 576 577 if (!empty($actions) && $this->core->auth->isSuperAdmin()) { 578 echo 579 '<th class="minimal nowrap">'.__('Action').'</th>'; 580 } 581 582 echo 583 '</tr>'; 584 585 $sort_field = $this->getSort(); 586 587 # Sort modules by $sort_field (default sname) 588 $modules = $this->getSearch() === null ? 589 self::sortModules($this->data, $sort_field, $this->sort_asc) : 590 $this->data; 591 592 $count = 0; 593 foreach ($modules as $id => $module) 594 { 595 # Show only requested modules 596 if ($nav_limit && $this->getSearch() === null) { 597 $char = substr($module[$sort_field], 0, 1); 598 if (!in_array($char, $this->nav_list)) { 599 $char = $this->nav_special; 600 } 601 if ($this->getIndex() != $char) { 602 continue; 603 } 604 } 605 606 echo 607 '<tr class="line" id="'.html::escapeHTML($this->list_id).'_m_'.html::escapeHTML($id).'">'; 608 609 $tds = 0; 610 611 if (in_array('checkbox', $cols)) { 612 $tds++; 613 echo 614 '<td class="module-icon nowrap">'. 615 form::checkbox(array('modules['.$count.']', html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id)), html::escapeHTML($id)). 616 '</td>'; 617 } 618 619 if (in_array('icon', $cols)) { 620 $tds++; 621 echo 622 '<td class="module-icon nowrap">'.sprintf( 623 '<img alt="%1$s" title="%1$s" src="%2$s" />', 624 html::escapeHTML($id), file_exists($module['root'].'/icon.png') ? 625 dcPage::getPF($id.'/icon.png') : 'images/module.png' 626 ).'</td>'; 627 } 628 629 $tds++; 630 echo 631 '<td class="module-name nowrap" scope="row">'; 632 if (in_array('checkbox', $cols)) { 633 if (in_array('expander', $cols)) { 634 echo 635 html::escapeHTML($module['name']).($id != $module['name'] ? sprintf(__(' (%s)'),$id) : ''); 636 } 637 else { 638 echo 639 '<label for="'.html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id).'">'. 640 html::escapeHTML($module['name']).($id != $module['name'] ? sprintf(__(' (%s)'),$id) : ''). 641 '</label>'; 642 } 643 } 644 else { 645 echo 646 html::escapeHTML($module['name']).($id != $module['name'] ? sprintf(__(' (%s)'),$id) : ''). 647 form::hidden(array('modules['.$count.']'), html::escapeHTML($id)); 648 } 649 echo 650 $this->core->formNonce(). 651 '</td>'; 652 653 # Display score only for debug purpose 654 if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) { 655 $tds++; 656 echo 657 '<td class="module-version nowrap count"><span class="debug">'.$module['score'].'</span></td>'; 658 } 659 660 if (in_array('version', $cols)) { 661 $tds++; 662 echo 663 '<td class="module-version nowrap count">'.html::escapeHTML($module['version']).'</td>'; 664 } 665 666 if (in_array('current_version', $cols)) { 667 $tds++; 668 echo 669 '<td class="module-current-version nowrap count">'.html::escapeHTML($module['current_version']).'</td>'; 670 } 671 672 if (in_array('desc', $cols)) { 673 $tds++; 674 echo 675 '<td class="module-desc maximal">'.html::escapeHTML(__($module['desc'])); 676 if (isset($module['cannot_disable']) && $module['enabled']) { 677 echo 678 '<br/><span class="info">'. 679 sprintf(__('This module cannot be disabled nor deleted, since the following modules are also enabled : %s'), 680 join(',',$module['cannot_disable'])). 681 '</span>'; 682 } 683 if (isset($module['cannot_enable']) && !$module['enabled']) { 684 echo 685 '<br/><span class="info">'. 686 __('This module cannot be enabled, because of the following reasons :'). 687 '<ul>'; 688 foreach ($module['cannot_enable'] as $m=>$reason) { 689 echo '<li>'.$reason.'</li>'; 690 } 691 echo '</ul>'. 692 '</span>'; 693 } 694 echo '</td>'; 695 696 } 697 698 if (in_array('distrib', $cols)) { 699 $tds++; 700 echo 701 '<td class="module-distrib">'.(self::isDistributedModule($id) ? 702 '<img src="images/dotclear_pw.png" alt="'. 703 __('Plugin from official distribution').'" title="'. 704 __('Plugin from official distribution').'" />' 705 : '').'</td>'; 706 } 707 708 if (!empty($actions) && $this->core->auth->isSuperAdmin()) { 709 $buttons = $this->getActions($id, $module, $actions); 710 711 $tds++; 712 echo 713 '<td class="module-actions nowrap">'. 714 715 '<div>'.implode(' ', $buttons).'</div>'. 716 717 '</td>'; 718 } 719 720 echo 721 '</tr>'; 722 723 # Other informations 724 if (in_array('expander', $cols)) { 725 echo 726 '<tr class="module-more"><td colspan="'.$tds.'" class="expand">'; 727 728 if (!empty($module['author']) || !empty($module['details']) || !empty($module['support'])) { 729 echo 730 '<div><ul class="mod-more">'; 731 732 if (!empty($module['author'])) { 733 echo 734 '<li class="module-author">'.__('Author:').' '.html::escapeHTML($module['author']).'</li>'; 735 } 736 737 $more = array(); 738 if (!empty($module['details'])) { 739 $more[] = '<a class="module-details" href="'.$module['details'].'">'.__('Details').'</a>'; 740 } 741 742 if (!empty($module['support'])) { 743 $more[] = '<a class="module-support" href="'.$module['support'].'">'.__('Support').'</a>'; 744 } 745 746 if (!empty($more)) { 747 echo 748 '<li>'.implode(' - ',$more).'</li>'; 749 } 750 751 echo 752 '</ul></div>'; 753 } 754 755 $config = !empty($module['root']) && file_exists(path::real($module['root'].'/_config.php')); 756 $index = !empty($module['root']) && file_exists(path::real($module['root'].'/index.php')); 757 758 if ($config || $index || !empty($module['section']) || !empty($module['tags']) || !empty($module['settings'])) { 759 echo 760 '<div><ul class="mod-more">'; 761 762 if ($index && $module['enabled']) { 763 echo '<li><a href="'.$this->core->adminurl->get('admin.plugin.'.$id).'">'.__('Manage plugin').'</a></li>'; 764 } 765 766 $settings = $this->getSettingsUrls($this->core,$id); 767 if (!empty($settings) && $module['enabled']) { 768 echo '<li>'.implode(' - ',$settings).'</li>'; 769 } 770 771 if (!empty($module['section'])) { 772 echo 773 '<li class="module-section">'.__('Section:').' '.html::escapeHTML($module['section']).'</li>'; 774 } 775 776 if (!empty($module['tags'])) { 777 echo 778 '<li class="module-tags">'.__('Tags:').' '.html::escapeHTML($module['tags']).'</li>'; 779 } 780 781 echo 782 '</ul></div>'; 783 } 784 785 echo 786 '</td></tr>'; 787 } 788 789 $count++; 790 } 791 echo 792 '</table></div>'; 793 794 if(!$count && $this->getSearch() === null) { 795 echo 796 '<p class="message">'.__('No plugins matched your search.').'</p>'; 797 } 798 799 elseif ((in_array('checkbox', $cols) || $count > 1) && !empty($actions) && $this->core->auth->isSuperAdmin()) { 800 $buttons = $this->getGlobalActions($actions, in_array('checkbox', $cols)); 801 802 if (!empty($buttons)) { 803 if (in_array('checkbox', $cols)) { 804 echo 805 '<p class="checkboxes-helpers"></p>'; 806 } 807 echo 808 '<div>'.implode(' ', $buttons).'</div>'; 809 } 810 } 811 echo 812 '</form>'; 813 814 return $this; 815 } 816 817 /** 818 * Get settings URLs if any 819 * 820 * @param object $core 821 * @param string $id module ID 822 * @param boolean $check check permission 823 * @param boolean $self include self URL (→ plugin index.php URL) 824 * @return Array of settings URLs 825 */ 826 public static function getSettingsUrls($core,$id,$check=false,$self=true) 827 { 828 $st = array(); 829 830 $mr = $core->plugins->moduleRoot($id); 831 $config = !empty($mr) && file_exists(path::real($mr.'/_config.php')); 832 $settings = $core->plugins->moduleInfo($id,'settings'); 833 if ($config || !empty($settings)) { 834 if ($config) { 835 if (!$check || 836 $core->auth->isSuperAdmin() || 837 $core->auth->check($core->plugins->moduleInfo($id,'permissions'),$core->blog->id)) 838 { 839 $params = array('module' => $id,'conf' => '1'); 840 if (!$core->plugins->moduleInfo($id,'standalone_config') && !$self) { 841 $params['redir'] = $core->adminurl->get('admin.plugin.'.$id); 842 } 843 $st[] = '<a class="module-config" href="'. 844 $core->adminurl->get('admin.plugins',$params). 845 '">'.__('Configure plugin').'</a>'; 846 } 847 } 848 if (is_array($settings)) { 849 foreach ($settings as $sk => $sv) { 850 switch ($sk) { 851 case 'blog': 852 if (!$check || 853 $core->auth->isSuperAdmin() || 854 $core->auth->check('admin',$core->blog->id)) 855 { 856 $st[] = '<a class="module-config" href="'. 857 $core->adminurl->get('admin.blog.pref').$sv. 858 '">'.__('Plugin settings (in blog parameters)').'</a>'; 859 } 860 break; 861 case 'pref': 862 if (!$check || 863 $core->auth->isSuperAdmin() || 864 $core->auth->check('usage,contentadmin',$core->blog->id)) 865 { 866 $st[] = '<a class="module-config" href="'. 867 $core->adminurl->get('admin.user.preferences').$sv. 868 '">'.__('Plugin settings (in user preferences)').'</a>'; 869 } 870 break; 871 case 'self': 872 if ($self) { 873 if (!$check || 874 $core->auth->isSuperAdmin() || 875 $core->auth->check($core->plugins->moduleInfo($id,'permissions'),$core->blog->id)) 876 { 877 $st[] = '<a class="module-config" href="'. 878 $core->adminurl->get('admin.plugin.'.$id).$sv. 879 '">'.__('Plugin settings').'</a>'; 880 } 881 } 882 break; 883 } 884 } 885 } 886 } 887 888 return $st; 889 } 890 891 /** 892 * Get action buttons to add to modules list. 893 * 894 * @param string $id Module ID 895 * @param array $module Module info 896 * @param array $actions Actions keys 897 * @return Array of actions buttons 898 */ 899 protected function getActions($id, $module, $actions) 900 { 901 $submits = array(); 902 903 # Use loop to keep requested order 904 foreach($actions as $action) { 905 switch($action) { 906 907 # Deactivate 908 case 'activate': if ($this->core->auth->isSuperAdmin() && $module['root_writable'] && !isset($module['cannot_enable'])) { 909 $submits[] = 910 '<input type="submit" name="activate['.html::escapeHTML($id).']" value="'.__('Activate').'" />'; 911 } break; 912 913 # Activate 914 case 'deactivate': if ($this->core->auth->isSuperAdmin() && $module['root_writable'] && !isset($module['cannot_disable'])) { 915 $submits[] = 916 '<input type="submit" name="deactivate['.html::escapeHTML($id).']" value="'.__('Deactivate').'" class="reset" />'; 917 } break; 918 919 # Delete 920 case 'delete': if ($this->core->auth->isSuperAdmin() && $this->isDeletablePath($module['root'])&& !isset($module['cannot_disable'])) { 921 $dev = !preg_match('!^'.$this->path_pattern.'!', $module['root']) && defined('DC_DEV') && DC_DEV ? ' debug' : ''; 922 $submits[] = 923 '<input type="submit" class="delete '.$dev.'" name="delete['.html::escapeHTML($id).']" value="'.__('Delete').'" />'; 924 } break; 925 926 # Install (from store) 927 case 'install': if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 928 $submits[] = 929 '<input type="submit" name="install['.html::escapeHTML($id).']" value="'.__('Install').'" />'; 930 } break; 931 932 # Update (from store) 933 case 'update': if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 934 $submits[] = 935 '<input type="submit" name="update['.html::escapeHTML($id).']" value="'.__('Update').'" />'; 936 } break; 937 938 # Behavior 939 case 'behavior': 940 941 # --BEHAVIOR-- adminModulesListGetActions 942 $tmp = $this->core->callBehavior('adminModulesListGetActions', $this, $id, $module); 943 944 if (!empty($tmp)) { 945 $submits[] = $tmp; 946 } 947 break; 948 } 949 } 950 951 return $submits; 952 } 953 954 /** 955 * Get global action buttons to add to modules list. 956 * 957 * @param array $actions Actions keys 958 * @param boolean $with_selection Limit action to selected modules 959 * @return Array of actions buttons 960 */ 961 protected function getGlobalActions($actions, $with_selection=false) 962 { 963 $submits = array(); 964 965 # Use loop to keep requested order 966 foreach($actions as $action) { 967 switch($action) { 968 969 # Deactivate 970 case 'activate': if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 971 $submits[] = 972 '<input type="submit" name="activate" value="'.($with_selection ? 973 __('Activate selected plugins') : 974 __('Activate all plugins from this list') 975 ).'" />'; 976 } break; 977 978 # Activate 979 case 'deactivate': if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 980 $submits[] = 981 '<input type="submit" name="deactivate" value="'.($with_selection ? 982 __('Deactivate selected plugins') : 983 __('Deactivate all plugins from this list') 984 ).'" />'; 985 } break; 986 987 # Update (from store) 988 case 'update': if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 989 $submits[] = 990 '<input type="submit" name="update" value="'.($with_selection ? 991 __('Update selected plugins') : 992 __('Update all plugins from this list') 993 ).'" />'; 994 } break; 995 996 # Behavior 997 case 'behavior': 998 999 # --BEHAVIOR-- adminModulesListGetGlobalActions 1000 $tmp = $this->core->callBehavior('adminModulesListGetGlobalActions', $this, $with_selection); 1001 1002 if (!empty($tmp)) { 1003 $submits[] = $tmp; 1004 } 1005 break; 1006 } 1007 } 1008 1009 return $submits; 1010 } 1011 1012 /** 1013 * Execute POST action. 1014 * 1015 * @note Set a notice on success through dcPage::addSuccessNotice 1016 * @throw Exception Module not find or command failed 1017 * @return Null 1018 */ 1019 public function doActions() 1020 { 1021 if (empty($_POST) || !empty($_REQUEST['conf']) 1022 || !$this->isWritablePath()) { 1023 return null; 1024 } 1025 1026 $modules = !empty($_POST['modules']) && is_array($_POST['modules']) ? array_values($_POST['modules']) : array(); 1027 1028 if ($this->core->auth->isSuperAdmin() && !empty($_POST['delete'])) { 1029 1030 if (is_array($_POST['delete'])) { 1031 $modules = array_keys($_POST['delete']); 1032 } 1033 1034 $list = $this->modules->getDisabledModules(); 1035 1036 $failed = false; 1037 $count = 0; 1038 foreach($modules as $id) 1039 { 1040 if (!isset($list[$id])) { 1041 1042 if (!$this->modules->moduleExists($id)) { 1043 throw new Exception(__('No such plugin.')); 1044 } 1045 1046 $module = $this->modules->getModules($id); 1047 $module['id'] = $id; 1048 1049 if (!$this->isDeletablePath($module['root'])) { 1050 $failed = true; 1051 continue; 1052 } 1053 1054 # --BEHAVIOR-- moduleBeforeDelete 1055 $this->core->callBehavior('pluginBeforeDelete', $module); 1056 1057 $this->modules->deleteModule($id); 1058 1059 # --BEHAVIOR-- moduleAfterDelete 1060 $this->core->callBehavior('pluginAfterDelete', $module); 1061 } 1062 else { 1063 $this->modules->deleteModule($id, true); 1064 } 1065 1066 $count++; 1067 } 1068 1069 if (!$count && $failed) { 1070 throw new Exception(__("You don't have permissions to delete this plugin.")); 1071 } 1072 elseif ($failed) { 1073 dcPage::addWarningNotice(__('Some plugins have not been delete.')); 1074 } 1075 else { 1076 dcPage::addSuccessNotice( 1077 __('Plugin has been successfully deleted.', 'Plugins have been successuflly deleted.', $count) 1078 ); 1079 } 1080 http::redirect($this->getURL()); 1081 } 1082 1083 elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['install'])) { 1084 1085 if (is_array($_POST['install'])) { 1086 $modules = array_keys($_POST['install']); 1087 } 1088 1089 $list = $this->store->get(); 1090 1091 if (empty($list)) { 1092 throw new Exception(__('No such plugin.')); 1093 } 1094 1095 $count = 0; 1096 foreach($list as $id => $module) { 1097 1098 if (!in_array($id, $modules)) { 1099 continue; 1100 } 1101 1102 $dest = $this->getPath().'/'.basename($module['file']); 1103 1104 # --BEHAVIOR-- moduleBeforeAdd 1105 $this->core->callBehavior('pluginBeforeAdd', $module); 1106 1107 $this->store->process($module['file'], $dest); 1108 1109 # --BEHAVIOR-- moduleAfterAdd 1110 $this->core->callBehavior('pluginAfterAdd', $module); 1111 1112 $count++; 1113 } 1114 1115 dcPage::addSuccessNotice( 1116 __('Plugin has been successfully installed.', 'Plugins have been successuflly installed.', $count) 1117 ); 1118 http::redirect($this->getURL()); 1119 } 1120 1121 elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['activate'])) { 1122 1123 if (is_array($_POST['activate'])) { 1124 $modules = array_keys($_POST['activate']); 1125 } 1126 1127 $list = $this->modules->getDisabledModules(); 1128 if (empty($list)) { 1129 throw new Exception(__('No such plugin.')); 1130 } 1131 1132 $count = 0; 1133 foreach($list as $id => $module) { 1134 1135 if (!in_array($id, $modules)) { 1136 continue; 1137 } 1138 1139 # --BEHAVIOR-- moduleBeforeActivate 1140 $this->core->callBehavior('pluginBeforeActivate', $id); 1141 1142 $this->modules->activateModule($id); 1143 1144 # --BEHAVIOR-- moduleAfterActivate 1145 $this->core->callBehavior('pluginAfterActivate', $id); 1146 1147 $count++; 1148 } 1149 1150 dcPage::addSuccessNotice( 1151 __('Plugin has been successfully activated.', 'Plugins have been successuflly activated.', $count) 1152 ); 1153 http::redirect($this->getURL()); 1154 } 1155 1156 elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['deactivate'])) { 1157 1158 if (is_array($_POST['deactivate'])) { 1159 $modules = array_keys($_POST['deactivate']); 1160 } 1161 1162 $list = $this->modules->getModules(); 1163 if (empty($list)) { 1164 throw new Exception(__('No such plugin.')); 1165 } 1166 1167 $failed = false; 1168 $count = 0; 1169 foreach($list as $id => $module) { 1170 1171 if (!in_array($id, $modules)) { 1172 continue; 1173 } 1174 1175 if (!$module['root_writable']) { 1176 $failed = true; 1177 continue; 1178 } 1179 1180 $module[$id] = $id; 1181 1182 # --BEHAVIOR-- moduleBeforeDeactivate 1183 $this->core->callBehavior('pluginBeforeDeactivate', $module); 1184 1185 $this->modules->deactivateModule($id); 1186 1187 # --BEHAVIOR-- moduleAfterDeactivate 1188 $this->core->callBehavior('pluginAfterDeactivate', $module); 1189 1190 $count++; 1191 } 1192 1193 if ($failed) { 1194 dcPage::addWarningNotice(__('Some plugins have not been deactivated.')); 1195 } 1196 else { 1197 dcPage::addSuccessNotice( 1198 __('Plugin has been successfully deactivated.', 'Plugins have been successuflly deactivated.', $count) 1199 ); 1200 } 1201 http::redirect($this->getURL()); 1202 } 1203 1204 elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['update'])) { 1205 1206 if (is_array($_POST['update'])) { 1207 $modules = array_keys($_POST['update']); 1208 } 1209 1210 $list = $this->store->get(true); 1211 if (empty($list)) { 1212 throw new Exception(__('No such plugin.')); 1213 } 1214 1215 $count = 0; 1216 foreach($list as $module) { 1217 1218 if (!in_array($module['id'], $modules)) { 1219 continue; 1220 } 1221 1222 if (!self::$allow_multi_install) { 1223 $dest = $module['root'].'/../'.basename($module['file']); 1224 } 1225 else { 1226 $dest = $this->getPath().'/'.basename($module['file']); 1227 if ($module['root'] != $dest) { 1228 @file_put_contents($module['root'].'/_disabled', ''); 1229 } 1230 } 1231 1232 # --BEHAVIOR-- moduleBeforeUpdate 1233 $this->core->callBehavior('pluginBeforeUpdate', $module); 1234 1235 $this->store->process($module['file'], $dest); 1236 1237 # --BEHAVIOR-- moduleAfterUpdate 1238 $this->core->callBehavior('pluginAfterUpdate', $module); 1239 1240 $count++; 1241 } 1242 1243 $tab = $count && $count == count($list) ? '#plugins' : '#update'; 1244 1245 dcPage::addSuccessNotice( 1246 __('Plugin has been successfully updated.', 'Plugins have been successuflly updated.', $count) 1247 ); 1248 http::redirect($this->getURL().$tab); 1249 } 1250 1251 # Manual actions 1252 elseif (!empty($_POST['upload_pkg']) && !empty($_FILES['pkg_file']) 1253 || !empty($_POST['fetch_pkg']) && !empty($_POST['pkg_url'])) 1254 { 1255 if (empty($_POST['your_pwd']) || !$this->core->auth->checkPassword($_POST['your_pwd'])) { 1256 throw new Exception(__('Password verification failed')); 1257 } 1258 1259 if (!empty($_POST['upload_pkg'])) { 1260 files::uploadStatus($_FILES['pkg_file']); 1261 1262 $dest = $this->getPath().'/'.$_FILES['pkg_file']['name']; 1263 if (!move_uploaded_file($_FILES['pkg_file']['tmp_name'], $dest)) { 1264 throw new Exception(__('Unable to move uploaded file.')); 1265 } 1266 } 1267 else { 1268 $url = urldecode($_POST['pkg_url']); 1269 $dest = $this->getPath().'/'.basename($url); 1270 $this->store->download($url, $dest); 1271 } 1272 1273 # --BEHAVIOR-- moduleBeforeAdd 1274 $this->core->callBehavior('pluginBeforeAdd', null); 1275 1276 $ret_code = $this->store->install($dest); 1277 1278 # --BEHAVIOR-- moduleAfterAdd 1279 $this->core->callBehavior('pluginAfterAdd', null); 1280 1281 dcPage::addSuccessNotice($ret_code == 2 ? 1282 __('Plugin has been successfully updated.') : 1283 __('Plugin has been successfully installed.') 1284 ); 1285 http::redirect($this->getURL().'#plugins'); 1286 } 1287 1288 else { 1289 1290 # --BEHAVIOR-- adminModulesListDoActions 1291 $this->core->callBehavior('adminModulesListDoActions', $this, $modules, 'plugin'); 1292 1293 } 1294 1295 return null; 1296 } 1297 1298 /** 1299 * Display tab for manual installation. 1300 * 1301 * @return adminModulesList self instance 1302 */ 1303 public function displayManualForm() 1304 { 1305 if (!$this->core->auth->isSuperAdmin() || !$this->isWritablePath()) { 1306 return null; 1307 } 1308 1309 # 'Upload module' form 1310 echo 1311 '<form method="post" action="'.$this->getURL().'" id="uploadpkg" enctype="multipart/form-data" class="fieldset">'. 1312 '<h4>'.__('Upload a zip file').'</h4>'. 1313 '<p class="field"><label for="pkg_file" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Zip file path:').'</label> '. 1314 '<input type="file" name="pkg_file" id="pkg_file" required /></p>'. 1315 '<p class="field"><label for="your_pwd1" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Your password:').'</label> '. 1316 form::password(array('your_pwd','your_pwd1'),20,255,'','','',false,'required placeholder="'.__('Password').'"').'</p>'. 1317 '<p><input type="submit" name="upload_pkg" value="'.__('Upload').'" />'. 1318 $this->core->formNonce().'</p>'. 1319 '</form>'; 1320 1321 # 'Fetch module' form 1322 echo 1323 '<form method="post" action="'.$this->getURL().'" id="fetchpkg" class="fieldset">'. 1324 '<h4>'.__('Download a zip file').'</h4>'. 1325 '<p class="field"><label for="pkg_url" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Zip file URL:').'</label> '. 1326 form::field(array('pkg_url','pkg_url'),40,255,'','','',false,'required placeholder="'.__('URL').'"').'</p>'. 1327 '<p class="field"><label for="your_pwd2" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Your password:').'</label> '. 1328 form::password(array('your_pwd','your_pwd2'),20,255,'','','',false,'required placeholder="'.__('Password').'"').'</p>'. 1329 '<p><input type="submit" name="fetch_pkg" value="'.__('Download').'" />'. 1330 $this->core->formNonce().'</p>'. 1331 '</form>'; 1332 1333 return $this; 1334 } 1335 //@} 1336 1337 /// @name Module configuration methods 1338 //@{ 1339 /** 1340 * Prepare module configuration. 1341 * 1342 * We need to get configuration content in three steps 1343 * and out of this class to keep backward compatibility. 1344 * 1345 * if ($xxx->setConfiguration()) { 1346 * include $xxx->includeConfiguration(); 1347 * } 1348 * $xxx->getConfiguration(); 1349 * ... [put here page headers and other stuff] 1350 * $xxx->displayConfiguration(); 1351 * 1352 * @param string $id Module to work on or it gather through REQUEST 1353 * @return True if config set 1354 */ 1355 public function setConfiguration($id=null) 1356 { 1357 if (empty($_REQUEST['conf']) || empty($_REQUEST['module']) && !$id) { 1358 return false; 1359 } 1360 1361 if (!empty($_REQUEST['module']) && empty($id)) { 1362 $id = $_REQUEST['module']; 1363 } 1364 1365 if (!$this->modules->moduleExists($id)) { 1366 $this->core->error->add(__('Unknow plugin ID')); 1367 return false; 1368 } 1369 1370 $module = $this->modules->getModules($id); 1371 $module = self::sanitizeModule($id, $module); 1372 $file = path::real($module['root'].'/_config.php'); 1373 1374 if (!file_exists($file)) { 1375 $this->core->error->add(__('This plugin has no configuration file.')); 1376 return false; 1377 } 1378 1379 $this->config_module = $module; 1380 $this->config_file = $file; 1381 $this->config_content = ''; 1382 1383 if (!defined('DC_CONTEXT_MODULE')) { 1384 define('DC_CONTEXT_MODULE', true); 1385 } 1386 1387 return true; 1388 } 1389 1390 /** 1391 * Get path of module configuration file. 1392 * 1393 * @note Required previously set file info 1394 * @return Full path of config file or null 1395 */ 1396 public function includeConfiguration() 1397 { 1398 if (!$this->config_file) { 1399 return null; 1400 } 1401 $this->setRedir($this->getURL().'#plugins'); 1402 1403 ob_start(); 1404 1405 return $this->config_file; 1406 } 1407 1408 /** 1409 * Gather module configuration file content. 1410 * 1411 * @note Required previously file inclusion 1412 * @return True if content has been captured 1413 */ 1414 public function getConfiguration() 1415 { 1416 if ($this->config_file) { 1417 $this->config_content = ob_get_contents(); 1418 } 1419 1420 ob_end_clean(); 1421 1422 return !empty($this->file_content); 1423 } 1424 1425 /** 1426 * Display module configuration form. 1427 * 1428 * @note Required previously gathered content 1429 * @return adminModulesList self instance 1430 */ 1431 public function displayConfiguration() 1432 { 1433 if ($this->config_file) { 1434 1435 if (!$this->config_module['standalone_config']) { 1436 echo 1437 '<form id="module_config" action="'.$this->getURL('conf=1').'" method="post" enctype="multipart/form-data">'. 1438 '<h3>'.sprintf(__('Configure "%s"'), html::escapeHTML($this->config_module['name'])).'</h3>'. 1439 '<p><a class="back" href="'.$this->getRedir().'">'.__('Back').'</a></p>'; 1440 } 1441 1442 echo $this->config_content; 1443 1444 if (!$this->config_module['standalone_config']) { 1445 echo 1446 '<p class="clear"><input type="submit" name="save" value="'.__('Save').'" />'. 1447 form::hidden('module', $this->config_module['id']). 1448 form::hidden('redir', $this->getRedir()). 1449 $this->core->formNonce().'</p>'. 1450 '</form>'; 1451 } 1452 } 1453 1454 return $this; 1455 } 1456 //@} 1457 1458 /** 1459 * Helper to sanitize a string. 1460 * 1461 * Used for search or id. 1462 * 1463 * @param string $str String to sanitize 1464 * @return Sanitized string 1465 */ 1466 public static function sanitizeString($str) 1467 { 1468 return preg_replace('/[^A-Za-z0-9\@\#+_-]/', '', strtolower($str)); 1469 } 23 public $core; /**< @var object dcCore instance */ 24 public $modules; /**< @var object dcModules instance */ 25 public $store; /**< @var object dcStore instance */ 26 27 public static $allow_multi_install = false; /**< @var boolean Work with multiple root directories */ 28 public static $distributed_modules = array(); /**< @var array List of modules distributed with Dotclear */ 29 30 protected $list_id = 'unknow'; /**< @var string Current list ID */ 31 protected $data = array(); /**< @var array Current modules */ 32 33 protected $config_module = ''; /**< @var string Module ID to configure */ 34 protected $config_file = ''; /**< @var string Module path to configure */ 35 protected $config_content = ''; /**< @var string Module configuration page content */ 36 37 protected $path = false; /**< @var string Modules root directory */ 38 protected $path_writable = false; /**< @var boolean Indicate if modules root directory is writable */ 39 protected $path_pattern = false; /**< @var string Directory pattern to work on */ 40 41 protected $page_url = ''; /**< @var string Page URL */ 42 protected $page_qs = '?'; /**< @var string Page query string */ 43 protected $page_tab = ''; /**< @var string Page tab */ 44 protected $page_redir = ''; /**< @var string Page redirection */ 45 46 public static $nav_indexes = 'abcdefghijklmnopqrstuvwxyz0123456789'; /**< @var string Index list */ 47 protected $nav_list = array(); /**< @var array Index list with special index */ 48 protected $nav_special = 'other'; /**< @var string Text for other special index */ 49 50 protected $sort_field = 'sname'; /**< @var string Field used to sort modules */ 51 protected $sort_asc = true; /**< @var boolean Sort order asc */ 52 53 /** 54 * Constructor. 55 * 56 * Note that this creates dcStore instance. 57 * 58 * @param object $modules dcModules instance 59 * @param string $modules_root Modules root directories 60 * @param string $xml_url URL of modules feed from repository 61 */ 62 public function __construct(dcModules $modules, $modules_root, $xml_url) 63 { 64 $this->core = $modules->core; 65 $this->modules = $modules; 66 $this->store = new dcStore($modules, $xml_url); 67 68 $this->page_url = $this->core->adminurl->get('admin.plugins'); 69 70 $this->setPath($modules_root); 71 $this->setIndex(__('other')); 72 } 73 74 /** 75 * Begin a new list. 76 * 77 * @param string $id New list ID 78 * @return adminModulesList self instance 79 */ 80 public function setList($id) 81 { 82 $this->data = array(); 83 $this->page_tab = ''; 84 $this->list_id = $id; 85 86 return $this; 87 } 88 89 /** 90 * Get list ID. 91 * 92 * @return List ID 93 */ 94 public function getList() 95 { 96 return $this->list_id; 97 } 98 99 /// @name Modules root directory methods 100 //@{ 101 /** 102 * Set path info. 103 * 104 * @param string $root Modules root directories 105 * @return adminModulesList self instance 106 */ 107 protected function setPath($root) 108 { 109 $paths = explode(PATH_SEPARATOR, $root); 110 $path = array_pop($paths); 111 unset($paths); 112 113 $this->path = $path; 114 if (is_dir($path) && is_writeable($path)) { 115 $this->path_writable = true; 116 $this->path_pattern = preg_quote($path, '!'); 117 } 118 119 return $this; 120 } 121 122 /** 123 * Get modules root directory. 124 * 125 * @return Path to work on 126 */ 127 public function getPath() 128 { 129 return $this->path; 130 } 131 132 /** 133 * Check if modules root directory is writable. 134 * 135 * @return True if directory is writable 136 */ 137 public function isWritablePath() 138 { 139 return $this->path_writable; 140 } 141 142 /** 143 * Check if root directory of a module is deletable. 144 * 145 * @param string $root Module root directory 146 * @return True if directory is delatable 147 */ 148 public function isDeletablePath($root) 149 { 150 return $this->path_writable 151 && (preg_match('!^' . $this->path_pattern . '!', $root) || defined('DC_DEV') && DC_DEV) 152 && $this->core->auth->isSuperAdmin(); 153 } 154 //@} 155 156 /// @name Page methods 157 //@{ 158 /** 159 * Set page base URL. 160 * 161 * @param string $url Page base URL 162 * @return adminModulesList self instance 163 */ 164 public function setURL($url) 165 { 166 $this->page_qs = strpos('?', $url) ? '&' : '?'; 167 $this->page_url = $url; 168 169 return $this; 170 } 171 172 /** 173 * Get page URL. 174 * 175 * @param string|array $queries Additionnal query string 176 * @param booleany $with_tab Add current tab to URL end 177 * @return Clean page URL 178 */ 179 public function getURL($queries = '', $with_tab = true) 180 { 181 return $this->page_url . 182 (!empty($queries) ? $this->page_qs : '') . 183 (is_array($queries) ? http_build_query($queries) : $queries) . 184 ($with_tab && !empty($this->page_tab) ? '#' . $this->page_tab : ''); 185 } 186 187 /** 188 * Set page tab. 189 * 190 * @param string $tab Page tab 191 * @return adminModulesList self instance 192 */ 193 public function setTab($tab) 194 { 195 $this->page_tab = $tab; 196 197 return $this; 198 } 199 200 /** 201 * Get page tab. 202 * 203 * @return Page tab 204 */ 205 public function getTab() 206 { 207 return $this->page_tab; 208 } 209 210 /** 211 * Set page redirection. 212 * 213 * @param string $default Default redirection 214 * @return adminModulesList self instance 215 */ 216 public function setRedir($default = '') 217 { 218 $this->page_redir = empty($_REQUEST['redir']) ? $default : $_REQUEST['redir']; 219 220 return $this; 221 } 222 223 /** 224 * Get page redirection. 225 * 226 * @return Page redirection 227 */ 228 public function getRedir() 229 { 230 return empty($this->page_redir) ? $this->getURL() : $this->page_redir; 231 } 232 //@} 233 234 /// @name Search methods 235 //@{ 236 /** 237 * Get search query. 238 * 239 * @return Search query 240 */ 241 public function getSearch() 242 { 243 $query = !empty($_REQUEST['m_search']) ? trim($_REQUEST['m_search']) : null; 244 return strlen($query) > 2 ? $query : null; 245 } 246 247 /** 248 * Display searh form. 249 * 250 * @return adminModulesList self instance 251 */ 252 public function displaySearch() 253 { 254 $query = $this->getSearch(); 255 256 if (empty($this->data) && $query === null) { 257 return $this; 258 } 259 260 echo 261 '<div class="modules-search">' . 262 '<form action="' . $this->getURL() . '" method="get">' . 263 '<p><label for="m_search" class="classic">' . __('Search in repository:') . ' </label><br />' . 264 form::field(array('m_search', 'm_search'), 30, 255, html::escapeHTML($query)) . 265 '<input type="submit" value="' . __('OK') . '" /> '; 266 267 if ($query) { 268 echo 269 ' <a href="' . $this->getURL() . '" class="button">' . __('Reset search') . '</a>'; 270 } 271 272 echo 273 '</p>' . 274 '<p class="form-note">' . 275 __('Search is allowed on multiple terms longer than 2 chars, terms must be separated by space.') . 276 '</p>' . 277 '</form>'; 278 279 if ($query) { 280 echo 281 '<p class="message">' . sprintf( 282 __('Found %d result for search "%s":', 'Found %d results for search "%s":', count($this->data)), 283 count($this->data), html::escapeHTML($query) 284 ) . 285 '</p>'; 286 } 287 echo '</div>'; 288 289 return $this; 290 } 291 //@} 292 293 /// @name Navigation menu methods 294 //@{ 295 /** 296 * Set navigation special index. 297 * 298 * @return adminModulesList self instance 299 */ 300 public function setIndex($str) 301 { 302 $this->nav_special = (string) $str; 303 $this->nav_list = array_merge(str_split(self::$nav_indexes), array($this->nav_special)); 304 305 return $this; 306 } 307 308 /** 309 * Get index from query. 310 * 311 * @return Query index or default one 312 */ 313 public function getIndex() 314 { 315 return isset($_REQUEST['m_nav']) && in_array($_REQUEST['m_nav'], $this->nav_list) ? $_REQUEST['m_nav'] : $this->nav_list[0]; 316 } 317 318 /** 319 * Display navigation by index menu. 320 * 321 * @return adminModulesList self instance 322 */ 323 public function displayIndex() 324 { 325 if (empty($this->data) || $this->getSearch() !== null) { 326 return $this; 327 } 328 329 # Fetch modules required field 330 $indexes = array(); 331 foreach ($this->data as $id => $module) { 332 if (!isset($module[$this->sort_field])) { 333 continue; 334 } 335 $char = substr($module[$this->sort_field], 0, 1); 336 if (!in_array($char, $this->nav_list)) { 337 $char = $this->nav_special; 338 } 339 if (!isset($indexes[$char])) { 340 $indexes[$char] = 0; 341 } 342 $indexes[$char]++; 343 } 344 345 $buttons = array(); 346 foreach ($this->nav_list as $char) { 347 # Selected letter 348 if ($this->getIndex() == $char) { 349 $buttons[] = '<li class="active" title="' . __('current selection') . '"><strong> ' . $char . ' </strong></li>'; 350 } 351 # Letter having modules 352 elseif (!empty($indexes[$char])) { 353 $title = sprintf(__('%d result', '%d results', $indexes[$char]), $indexes[$char]); 354 $buttons[] = '<li class="btn" title="' . $title . '"><a href="' . $this->getURL('m_nav=' . $char) . '" title="' . $title . '"> ' . $char . ' </a></li>'; 355 } 356 # Letter without modules 357 else { 358 $buttons[] = '<li class="btn no-link" title="' . __('no results') . '"> ' . $char . ' </li>'; 359 } 360 } 361 # Parse navigation menu 362 echo '<div class="pager">' . __('Browse index:') . ' <ul class="index">' . implode('', $buttons) . '</ul></div>'; 363 364 return $this; 365 } 366 //@} 367 368 /// @name Sort methods 369 //@{ 370 /** 371 * Set default sort field. 372 * 373 * @return adminModulesList self instance 374 */ 375 public function setSort($field, $asc = true) 376 { 377 $this->sort_field = $field; 378 $this->sort_asc = (boolean) $asc; 379 380 return $this; 381 } 382 383 /** 384 * Get sort field from query. 385 * 386 * @return Query sort field or default one 387 */ 388 public function getSort() 389 { 390 return !empty($_REQUEST['m_sort']) ? $_REQUEST['m_sort'] : $this->sort_field; 391 } 392 393 /** 394 * Display sort field form. 395 * 396 * @note This method is not implemented yet 397 * @return adminModulesList self instance 398 */ 399 public function displaySort() 400 { 401 // 402 403 return $this; 404 } 405 //@} 406 407 /// @name Modules methods 408 //@{ 409 /** 410 * Set modules and sanitize them. 411 * 412 * @return adminModulesList self instance 413 */ 414 public function setModules($modules) 415 { 416 $this->data = array(); 417 if (!empty($modules) && is_array($modules)) { 418 foreach ($modules as $id => $module) { 419 $this->data[$id] = self::sanitizeModule($id, $module); 420 } 421 } 422 return $this; 423 } 424 425 /** 426 * Get modules currently set. 427 * 428 * @return Array of modules 429 */ 430 public function getModules() 431 { 432 return $this->data; 433 } 434 435 /** 436 * Sanitize a module. 437 * 438 * This clean infos of a module by adding default keys 439 * and clean some of them, sanitize module can safely 440 * be used in lists. 441 * 442 * @return Array of the module informations 443 */ 444 public static function sanitizeModule($id, $module) 445 { 446 $label = empty($module['label']) ? $id : $module['label']; 447 $name = __(empty($module['name']) ? $label : $module['name']); 448 449 return array_merge( 450 # Default values 451 array( 452 'desc' => '', 453 'author' => '', 454 'version' => 0, 455 'current_version' => 0, 456 'root' => '', 457 'root_writable' => false, 458 'permissions' => null, 459 'parent' => null, 460 'priority' => 1000, 461 'standalone_config' => false, 462 'support' => '', 463 'section' => '', 464 'tags' => '', 465 'details' => '', 466 'sshot' => '', 467 'score' => 0, 468 'type' => null, 469 'require' => array(), 470 'settings' => array() 471 ), 472 # Module's values 473 $module, 474 # Clean up values 475 array( 476 'id' => $id, 477 'sid' => self::sanitizeString($id), 478 'label' => $label, 479 'name' => $name, 480 'sname' => self::sanitizeString($name) 481 ) 482 ); 483 } 484 485 /** 486 * Check if a module is part of the distribution. 487 * 488 * @param string $id Module root directory 489 * @return True if module is part of the distribution 490 */ 491 public static function isDistributedModule($id) 492 { 493 $distributed_modules = self::$distributed_modules; 494 495 return is_array($distributed_modules) && in_array($id, $distributed_modules); 496 } 497 498 /** 499 * Sort modules list by specific field. 500 * 501 * @param string $module Array of modules 502 * @param string $field Field to sort from 503 * @param bollean $asc Sort asc if true, else decs 504 * @return Array of sorted modules 505 */ 506 public static function sortModules($modules, $field, $asc = true) 507 { 508 $origin = $sorter = array(); 509 510 foreach ($modules as $id => $module) { 511 $origin[] = $module; 512 $sorter[] = isset($module[$field]) ? $module[$field] : $field; 513 } 514 515 array_multisort($sorter, $asc ? SORT_ASC : SORT_DESC, $origin); 516 517 foreach ($origin as $module) { 518 $final[$module['id']] = $module; 519 } 520 521 return $final; 522 } 523 524 /** 525 * Display list of modules. 526 * 527 * @param array $cols List of colones (module field) to display 528 * @param array $actions List of predefined actions to show on form 529 * @param boolean $nav_limit Limit list to previously selected index 530 * @return adminModulesList self instance 531 */ 532 public function displayModules($cols = array('name', 'version', 'desc'), $actions = array(), $nav_limit = false) 533 { 534 echo 535 '<form action="' . $this->getURL() . '" method="post" class="modules-form-actions">' . 536 '<div class="table-outer">' . 537 '<table id="' . html::escapeHTML($this->list_id) . '" class="modules' . (in_array('expander', $cols) ? ' expandable' : '') . '">' . 538 '<caption class="hidden">' . html::escapeHTML(__('Plugins list')) . '</caption><tr>'; 539 540 if (in_array('name', $cols)) { 541 $colspan = 1; 542 if (in_array('checkbox', $cols)) { 543 $colspan++; 544 } 545 if (in_array('icon', $cols)) { 546 $colspan++; 547 } 548 echo 549 '<th class="first nowrap"' . ($colspan > 1 ? ' colspan="' . $colspan . '"' : '') . '>' . __('Name') . '</th>'; 550 } 551 552 if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) { 553 echo 554 '<th class="nowrap">' . __('Score') . '</th>'; 555 } 556 557 if (in_array('version', $cols)) { 558 echo 559 '<th class="nowrap count" scope="col">' . __('Version') . '</th>'; 560 } 561 562 if (in_array('current_version', $cols)) { 563 echo 564 '<th class="nowrap count" scope="col">' . __('Current version') . '</th>'; 565 } 566 567 if (in_array('desc', $cols)) { 568 echo 569 '<th class="nowrap" scope="col">' . __('Details') . '</th>'; 570 } 571 572 if (in_array('distrib', $cols)) { 573 echo 574 '<th' . (in_array('desc', $cols) ? '' : ' class="maximal"') . '></th>'; 575 } 576 577 if (!empty($actions) && $this->core->auth->isSuperAdmin()) { 578 echo 579 '<th class="minimal nowrap">' . __('Action') . '</th>'; 580 } 581 582 echo 583 '</tr>'; 584 585 $sort_field = $this->getSort(); 586 587 # Sort modules by $sort_field (default sname) 588 $modules = $this->getSearch() === null ? 589 self::sortModules($this->data, $sort_field, $this->sort_asc) : 590 $this->data; 591 592 $count = 0; 593 foreach ($modules as $id => $module) { 594 # Show only requested modules 595 if ($nav_limit && $this->getSearch() === null) { 596 $char = substr($module[$sort_field], 0, 1); 597 if (!in_array($char, $this->nav_list)) { 598 $char = $this->nav_special; 599 } 600 if ($this->getIndex() != $char) { 601 continue; 602 } 603 } 604 605 echo 606 '<tr class="line" id="' . html::escapeHTML($this->list_id) . '_m_' . html::escapeHTML($id) . '">'; 607 608 $tds = 0; 609 610 if (in_array('checkbox', $cols)) { 611 $tds++; 612 echo 613 '<td class="module-icon nowrap">' . 614 form::checkbox(array('modules[' . $count . ']', html::escapeHTML($this->list_id) . '_modules_' . html::escapeHTML($id)), html::escapeHTML($id)) . 615 '</td>'; 616 } 617 618 if (in_array('icon', $cols)) { 619 $tds++; 620 echo 621 '<td class="module-icon nowrap">' . sprintf( 622 '<img alt="%1$s" title="%1$s" src="%2$s" />', 623 html::escapeHTML($id), file_exists($module['root'] . '/icon.png') ? 624 dcPage::getPF($id . '/icon.png') : 'images/module.png' 625 ) . '</td>'; 626 } 627 628 $tds++; 629 echo 630 '<td class="module-name nowrap" scope="row">'; 631 if (in_array('checkbox', $cols)) { 632 if (in_array('expander', $cols)) { 633 echo 634 html::escapeHTML($module['name']) . ($id != $module['name'] ? sprintf(__(' (%s)'), $id) : ''); 635 } else { 636 echo 637 '<label for="' . html::escapeHTML($this->list_id) . '_modules_' . html::escapeHTML($id) . '">' . 638 html::escapeHTML($module['name']) . ($id != $module['name'] ? sprintf(__(' (%s)'), $id) : '') . 639 '</label>'; 640 } 641 } else { 642 echo 643 html::escapeHTML($module['name']) . ($id != $module['name'] ? sprintf(__(' (%s)'), $id) : '') . 644 form::hidden(array('modules[' . $count . ']'), html::escapeHTML($id)); 645 } 646 echo 647 $this->core->formNonce() . 648 '</td>'; 649 650 # Display score only for debug purpose 651 if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) { 652 $tds++; 653 echo 654 '<td class="module-version nowrap count"><span class="debug">' . $module['score'] . '</span></td>'; 655 } 656 657 if (in_array('version', $cols)) { 658 $tds++; 659 echo 660 '<td class="module-version nowrap count">' . html::escapeHTML($module['version']) . '</td>'; 661 } 662 663 if (in_array('current_version', $cols)) { 664 $tds++; 665 echo 666 '<td class="module-current-version nowrap count">' . html::escapeHTML($module['current_version']) . '</td>'; 667 } 668 669 if (in_array('desc', $cols)) { 670 $tds++; 671 echo 672 '<td class="module-desc maximal">' . html::escapeHTML(__($module['desc'])); 673 if (isset($module['cannot_disable']) && $module['enabled']) { 674 echo 675 '<br/><span class="info">' . 676 sprintf(__('This module cannot be disabled nor deleted, since the following modules are also enabled : %s'), 677 join(',', $module['cannot_disable'])) . 678 '</span>'; 679 } 680 if (isset($module['cannot_enable']) && !$module['enabled']) { 681 echo 682 '<br/><span class="info">' . 683 __('This module cannot be enabled, because of the following reasons :') . 684 '<ul>'; 685 foreach ($module['cannot_enable'] as $m => $reason) { 686 echo '<li>' . $reason . '</li>'; 687 } 688 echo '</ul>' . 689 '</span>'; 690 } 691 echo '</td>'; 692 693 } 694 695 if (in_array('distrib', $cols)) { 696 $tds++; 697 echo 698 '<td class="module-distrib">' . (self::isDistributedModule($id) ? 699 '<img src="images/dotclear_pw.png" alt="' . 700 __('Plugin from official distribution') . '" title="' . 701 __('Plugin from official distribution') . '" />' 702 : '') . '</td>'; 703 } 704 705 if (!empty($actions) && $this->core->auth->isSuperAdmin()) { 706 $buttons = $this->getActions($id, $module, $actions); 707 708 $tds++; 709 echo 710 '<td class="module-actions nowrap">' . 711 712 '<div>' . implode(' ', $buttons) . '</div>' . 713 714 '</td>'; 715 } 716 717 echo 718 '</tr>'; 719 720 # Other informations 721 if (in_array('expander', $cols)) { 722 echo 723 '<tr class="module-more"><td colspan="' . $tds . '" class="expand">'; 724 725 if (!empty($module['author']) || !empty($module['details']) || !empty($module['support'])) { 726 echo 727 '<div><ul class="mod-more">'; 728 729 if (!empty($module['author'])) { 730 echo 731 '<li class="module-author">' . __('Author:') . ' ' . html::escapeHTML($module['author']) . '</li>'; 732 } 733 734 $more = array(); 735 if (!empty($module['details'])) { 736 $more[] = '<a class="module-details" href="' . $module['details'] . '">' . __('Details') . '</a>'; 737 } 738 739 if (!empty($module['support'])) { 740 $more[] = '<a class="module-support" href="' . $module['support'] . '">' . __('Support') . '</a>'; 741 } 742 743 if (!empty($more)) { 744 echo 745 '<li>' . implode(' - ', $more) . '</li>'; 746 } 747 748 echo 749 '</ul></div>'; 750 } 751 752 $config = !empty($module['root']) && file_exists(path::real($module['root'] . '/_config.php')); 753 $index = !empty($module['root']) && file_exists(path::real($module['root'] . '/index.php')); 754 755 if ($config || $index || !empty($module['section']) || !empty($module['tags']) || !empty($module['settings'])) { 756 echo 757 '<div><ul class="mod-more">'; 758 759 if ($index && $module['enabled']) { 760 echo '<li><a href="' . $this->core->adminurl->get('admin.plugin.' . $id) . '">' . __('Manage plugin') . '</a></li>'; 761 } 762 763 $settings = $this->getSettingsUrls($this->core, $id); 764 if (!empty($settings) && $module['enabled']) { 765 echo '<li>' . implode(' - ', $settings) . '</li>'; 766 } 767 768 if (!empty($module['section'])) { 769 echo 770 '<li class="module-section">' . __('Section:') . ' ' . html::escapeHTML($module['section']) . '</li>'; 771 } 772 773 if (!empty($module['tags'])) { 774 echo 775 '<li class="module-tags">' . __('Tags:') . ' ' . html::escapeHTML($module['tags']) . '</li>'; 776 } 777 778 echo 779 '</ul></div>'; 780 } 781 782 echo 783 '</td></tr>'; 784 } 785 786 $count++; 787 } 788 echo 789 '</table></div>'; 790 791 if (!$count && $this->getSearch() === null) { 792 echo 793 '<p class="message">' . __('No plugins matched your search.') . '</p>'; 794 } elseif ((in_array('checkbox', $cols) || $count > 1) && !empty($actions) && $this->core->auth->isSuperAdmin()) { 795 $buttons = $this->getGlobalActions($actions, in_array('checkbox', $cols)); 796 797 if (!empty($buttons)) { 798 if (in_array('checkbox', $cols)) { 799 echo 800 '<p class="checkboxes-helpers"></p>'; 801 } 802 echo 803 '<div>' . implode(' ', $buttons) . '</div>'; 804 } 805 } 806 echo 807 '</form>'; 808 809 return $this; 810 } 811 812 /** 813 * Get settings URLs if any 814 * 815 * @param object $core 816 * @param string $id module ID 817 * @param boolean $check check permission 818 * @param boolean $self include self URL (→ plugin index.php URL) 819 * @return Array of settings URLs 820 */ 821 public static function getSettingsUrls($core, $id, $check = false, $self = true) 822 { 823 $st = array(); 824 825 $mr = $core->plugins->moduleRoot($id); 826 $config = !empty($mr) && file_exists(path::real($mr . '/_config.php')); 827 $settings = $core->plugins->moduleInfo($id, 'settings'); 828 if ($config || !empty($settings)) { 829 if ($config) { 830 if (!$check || 831 $core->auth->isSuperAdmin() || 832 $core->auth->check($core->plugins->moduleInfo($id, 'permissions'), $core->blog->id)) { 833 $params = array('module' => $id, 'conf' => '1'); 834 if (!$core->plugins->moduleInfo($id, 'standalone_config') && !$self) { 835 $params['redir'] = $core->adminurl->get('admin.plugin.' . $id); 836 } 837 $st[] = '<a class="module-config" href="' . 838 $core->adminurl->get('admin.plugins', $params) . 839 '">' . __('Configure plugin') . '</a>'; 840 } 841 } 842 if (is_array($settings)) { 843 foreach ($settings as $sk => $sv) { 844 switch ($sk) { 845 case 'blog': 846 if (!$check || 847 $core->auth->isSuperAdmin() || 848 $core->auth->check('admin', $core->blog->id)) { 849 $st[] = '<a class="module-config" href="' . 850 $core->adminurl->get('admin.blog.pref') . $sv . 851 '">' . __('Plugin settings (in blog parameters)') . '</a>'; 852 } 853 break; 854 case 'pref': 855 if (!$check || 856 $core->auth->isSuperAdmin() || 857 $core->auth->check('usage,contentadmin', $core->blog->id)) { 858 $st[] = '<a class="module-config" href="' . 859 $core->adminurl->get('admin.user.preferences') . $sv . 860 '">' . __('Plugin settings (in user preferences)') . '</a>'; 861 } 862 break; 863 case 'self': 864 if ($self) { 865 if (!$check || 866 $core->auth->isSuperAdmin() || 867 $core->auth->check($core->plugins->moduleInfo($id, 'permissions'), $core->blog->id)) { 868 $st[] = '<a class="module-config" href="' . 869 $core->adminurl->get('admin.plugin.' . $id) . $sv . 870 '">' . __('Plugin settings') . '</a>'; 871 } 872 } 873 break; 874 } 875 } 876 } 877 } 878 879 return $st; 880 } 881 882 /** 883 * Get action buttons to add to modules list. 884 * 885 * @param string $id Module ID 886 * @param array $module Module info 887 * @param array $actions Actions keys 888 * @return Array of actions buttons 889 */ 890 protected function getActions($id, $module, $actions) 891 { 892 $submits = array(); 893 894 # Use loop to keep requested order 895 foreach ($actions as $action) { 896 switch ($action) { 897 898 # Deactivate 899 case 'activate':if ($this->core->auth->isSuperAdmin() && $module['root_writable'] && !isset($module['cannot_enable'])) { 900 $submits[] = 901 '<input type="submit" name="activate[' . html::escapeHTML($id) . ']" value="' . __('Activate') . '" />'; 902 }break; 903 904 # Activate 905 case 'deactivate':if ($this->core->auth->isSuperAdmin() && $module['root_writable'] && !isset($module['cannot_disable'])) { 906 $submits[] = 907 '<input type="submit" name="deactivate[' . html::escapeHTML($id) . ']" value="' . __('Deactivate') . '" class="reset" />'; 908 }break; 909 910 # Delete 911 case 'delete':if ($this->core->auth->isSuperAdmin() && $this->isDeletablePath($module['root']) && !isset($module['cannot_disable'])) { 912 $dev = !preg_match('!^' . $this->path_pattern . '!', $module['root']) && defined('DC_DEV') && DC_DEV ? ' debug' : ''; 913 $submits[] = 914 '<input type="submit" class="delete ' . $dev . '" name="delete[' . html::escapeHTML($id) . ']" value="' . __('Delete') . '" />'; 915 }break; 916 917 # Install (from store) 918 case 'install':if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 919 $submits[] = 920 '<input type="submit" name="install[' . html::escapeHTML($id) . ']" value="' . __('Install') . '" />'; 921 }break; 922 923 # Update (from store) 924 case 'update':if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 925 $submits[] = 926 '<input type="submit" name="update[' . html::escapeHTML($id) . ']" value="' . __('Update') . '" />'; 927 }break; 928 929 # Behavior 930 case 'behavior': 931 932 # --BEHAVIOR-- adminModulesListGetActions 933 $tmp = $this->core->callBehavior('adminModulesListGetActions', $this, $id, $module); 934 935 if (!empty($tmp)) { 936 $submits[] = $tmp; 937 } 938 break; 939 } 940 } 941 942 return $submits; 943 } 944 945 /** 946 * Get global action buttons to add to modules list. 947 * 948 * @param array $actions Actions keys 949 * @param boolean $with_selection Limit action to selected modules 950 * @return Array of actions buttons 951 */ 952 protected function getGlobalActions($actions, $with_selection = false) 953 { 954 $submits = array(); 955 956 # Use loop to keep requested order 957 foreach ($actions as $action) { 958 switch ($action) { 959 960 # Deactivate 961 case 'activate':if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 962 $submits[] = 963 '<input type="submit" name="activate" value="' . ($with_selection ? 964 __('Activate selected plugins') : 965 __('Activate all plugins from this list') 966 ) . '" />'; 967 }break; 968 969 # Activate 970 case 'deactivate':if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 971 $submits[] = 972 '<input type="submit" name="deactivate" value="' . ($with_selection ? 973 __('Deactivate selected plugins') : 974 __('Deactivate all plugins from this list') 975 ) . '" />'; 976 }break; 977 978 # Update (from store) 979 case 'update':if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 980 $submits[] = 981 '<input type="submit" name="update" value="' . ($with_selection ? 982 __('Update selected plugins') : 983 __('Update all plugins from this list') 984 ) . '" />'; 985 }break; 986 987 # Behavior 988 case 'behavior': 989 990 # --BEHAVIOR-- adminModulesListGetGlobalActions 991 $tmp = $this->core->callBehavior('adminModulesListGetGlobalActions', $this, $with_selection); 992 993 if (!empty($tmp)) { 994 $submits[] = $tmp; 995 } 996 break; 997 } 998 } 999 1000 return $submits; 1001 } 1002 1003 /** 1004 * Execute POST action. 1005 * 1006 * @note Set a notice on success through dcPage::addSuccessNotice 1007 * @throw Exception Module not find or command failed 1008 * @return Null 1009 */ 1010 public function doActions() 1011 { 1012 if (empty($_POST) || !empty($_REQUEST['conf']) 1013 || !$this->isWritablePath()) { 1014 return; 1015 } 1016 1017 $modules = !empty($_POST['modules']) && is_array($_POST['modules']) ? array_values($_POST['modules']) : array(); 1018 1019 if ($this->core->auth->isSuperAdmin() && !empty($_POST['delete'])) { 1020 1021 if (is_array($_POST['delete'])) { 1022 $modules = array_keys($_POST['delete']); 1023 } 1024 1025 $list = $this->modules->getDisabledModules(); 1026 1027 $failed = false; 1028 $count = 0; 1029 foreach ($modules as $id) { 1030 if (!isset($list[$id])) { 1031 1032 if (!$this->modules->moduleExists($id)) { 1033 throw new Exception(__('No such plugin.')); 1034 } 1035 1036 $module = $this->modules->getModules($id); 1037 $module['id'] = $id; 1038 1039 if (!$this->isDeletablePath($module['root'])) { 1040 $failed = true; 1041 continue; 1042 } 1043 1044 # --BEHAVIOR-- moduleBeforeDelete 1045 $this->core->callBehavior('pluginBeforeDelete', $module); 1046 1047 $this->modules->deleteModule($id); 1048 1049 # --BEHAVIOR-- moduleAfterDelete 1050 $this->core->callBehavior('pluginAfterDelete', $module); 1051 } else { 1052 $this->modules->deleteModule($id, true); 1053 } 1054 1055 $count++; 1056 } 1057 1058 if (!$count && $failed) { 1059 throw new Exception(__("You don't have permissions to delete this plugin.")); 1060 } elseif ($failed) { 1061 dcPage::addWarningNotice(__('Some plugins have not been delete.')); 1062 } else { 1063 dcPage::addSuccessNotice( 1064 __('Plugin has been successfully deleted.', 'Plugins have been successuflly deleted.', $count) 1065 ); 1066 } 1067 http::redirect($this->getURL()); 1068 } elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['install'])) { 1069 1070 if (is_array($_POST['install'])) { 1071 $modules = array_keys($_POST['install']); 1072 } 1073 1074 $list = $this->store->get(); 1075 1076 if (empty($list)) { 1077 throw new Exception(__('No such plugin.')); 1078 } 1079 1080 $count = 0; 1081 foreach ($list as $id => $module) { 1082 1083 if (!in_array($id, $modules)) { 1084 continue; 1085 } 1086 1087 $dest = $this->getPath() . '/' . basename($module['file']); 1088 1089 # --BEHAVIOR-- moduleBeforeAdd 1090 $this->core->callBehavior('pluginBeforeAdd', $module); 1091 1092 $this->store->process($module['file'], $dest); 1093 1094 # --BEHAVIOR-- moduleAfterAdd 1095 $this->core->callBehavior('pluginAfterAdd', $module); 1096 1097 $count++; 1098 } 1099 1100 dcPage::addSuccessNotice( 1101 __('Plugin has been successfully installed.', 'Plugins have been successuflly installed.', $count) 1102 ); 1103 http::redirect($this->getURL()); 1104 } elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['activate'])) { 1105 1106 if (is_array($_POST['activate'])) { 1107 $modules = array_keys($_POST['activate']); 1108 } 1109 1110 $list = $this->modules->getDisabledModules(); 1111 if (empty($list)) { 1112 throw new Exception(__('No such plugin.')); 1113 } 1114 1115 $count = 0; 1116 foreach ($list as $id => $module) { 1117 1118 if (!in_array($id, $modules)) { 1119 continue; 1120 } 1121 1122 # --BEHAVIOR-- moduleBeforeActivate 1123 $this->core->callBehavior('pluginBeforeActivate', $id); 1124 1125 $this->modules->activateModule($id); 1126 1127 # --BEHAVIOR-- moduleAfterActivate 1128 $this->core->callBehavior('pluginAfterActivate', $id); 1129 1130 $count++; 1131 } 1132 1133 dcPage::addSuccessNotice( 1134 __('Plugin has been successfully activated.', 'Plugins have been successuflly activated.', $count) 1135 ); 1136 http::redirect($this->getURL()); 1137 } elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['deactivate'])) { 1138 1139 if (is_array($_POST['deactivate'])) { 1140 $modules = array_keys($_POST['deactivate']); 1141 } 1142 1143 $list = $this->modules->getModules(); 1144 if (empty($list)) { 1145 throw new Exception(__('No such plugin.')); 1146 } 1147 1148 $failed = false; 1149 $count = 0; 1150 foreach ($list as $id => $module) { 1151 1152 if (!in_array($id, $modules)) { 1153 continue; 1154 } 1155 1156 if (!$module['root_writable']) { 1157 $failed = true; 1158 continue; 1159 } 1160 1161 $module[$id] = $id; 1162 1163 # --BEHAVIOR-- moduleBeforeDeactivate 1164 $this->core->callBehavior('pluginBeforeDeactivate', $module); 1165 1166 $this->modules->deactivateModule($id); 1167 1168 # --BEHAVIOR-- moduleAfterDeactivate 1169 $this->core->callBehavior('pluginAfterDeactivate', $module); 1170 1171 $count++; 1172 } 1173 1174 if ($failed) { 1175 dcPage::addWarningNotice(__('Some plugins have not been deactivated.')); 1176 } else { 1177 dcPage::addSuccessNotice( 1178 __('Plugin has been successfully deactivated.', 'Plugins have been successuflly deactivated.', $count) 1179 ); 1180 } 1181 http::redirect($this->getURL()); 1182 } elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['update'])) { 1183 1184 if (is_array($_POST['update'])) { 1185 $modules = array_keys($_POST['update']); 1186 } 1187 1188 $list = $this->store->get(true); 1189 if (empty($list)) { 1190 throw new Exception(__('No such plugin.')); 1191 } 1192 1193 $count = 0; 1194 foreach ($list as $module) { 1195 1196 if (!in_array($module['id'], $modules)) { 1197 continue; 1198 } 1199 1200 if (!self::$allow_multi_install) { 1201 $dest = $module['root'] . '/../' . basename($module['file']); 1202 } else { 1203 $dest = $this->getPath() . '/' . basename($module['file']); 1204 if ($module['root'] != $dest) { 1205 @file_put_contents($module['root'] . '/_disabled', ''); 1206 } 1207 } 1208 1209 # --BEHAVIOR-- moduleBeforeUpdate 1210 $this->core->callBehavior('pluginBeforeUpdate', $module); 1211 1212 $this->store->process($module['file'], $dest); 1213 1214 # --BEHAVIOR-- moduleAfterUpdate 1215 $this->core->callBehavior('pluginAfterUpdate', $module); 1216 1217 $count++; 1218 } 1219 1220 $tab = $count && $count == count($list) ? '#plugins' : '#update'; 1221 1222 dcPage::addSuccessNotice( 1223 __('Plugin has been successfully updated.', 'Plugins have been successuflly updated.', $count) 1224 ); 1225 http::redirect($this->getURL() . $tab); 1226 } 1227 1228 # Manual actions 1229 elseif (!empty($_POST['upload_pkg']) && !empty($_FILES['pkg_file']) 1230 || !empty($_POST['fetch_pkg']) && !empty($_POST['pkg_url'])) { 1231 if (empty($_POST['your_pwd']) || !$this->core->auth->checkPassword($_POST['your_pwd'])) { 1232 throw new Exception(__('Password verification failed')); 1233 } 1234 1235 if (!empty($_POST['upload_pkg'])) { 1236 files::uploadStatus($_FILES['pkg_file']); 1237 1238 $dest = $this->getPath() . '/' . $_FILES['pkg_file']['name']; 1239 if (!move_uploaded_file($_FILES['pkg_file']['tmp_name'], $dest)) { 1240 throw new Exception(__('Unable to move uploaded file.')); 1241 } 1242 } else { 1243 $url = urldecode($_POST['pkg_url']); 1244 $dest = $this->getPath() . '/' . basename($url); 1245 $this->store->download($url, $dest); 1246 } 1247 1248 # --BEHAVIOR-- moduleBeforeAdd 1249 $this->core->callBehavior('pluginBeforeAdd', null); 1250 1251 $ret_code = $this->store->install($dest); 1252 1253 # --BEHAVIOR-- moduleAfterAdd 1254 $this->core->callBehavior('pluginAfterAdd', null); 1255 1256 dcPage::addSuccessNotice($ret_code == 2 ? 1257 __('Plugin has been successfully updated.') : 1258 __('Plugin has been successfully installed.') 1259 ); 1260 http::redirect($this->getURL() . '#plugins'); 1261 } else { 1262 1263 # --BEHAVIOR-- adminModulesListDoActions 1264 $this->core->callBehavior('adminModulesListDoActions', $this, $modules, 'plugin'); 1265 1266 } 1267 1268 return; 1269 } 1270 1271 /** 1272 * Display tab for manual installation. 1273 * 1274 * @return adminModulesList self instance 1275 */ 1276 public function displayManualForm() 1277 { 1278 if (!$this->core->auth->isSuperAdmin() || !$this->isWritablePath()) { 1279 return; 1280 } 1281 1282 # 'Upload module' form 1283 echo 1284 '<form method="post" action="' . $this->getURL() . '" id="uploadpkg" enctype="multipart/form-data" class="fieldset">' . 1285 '<h4>' . __('Upload a zip file') . '</h4>' . 1286 '<p class="field"><label for="pkg_file" class="classic required"><abbr title="' . __('Required field') . '">*</abbr> ' . __('Zip file path:') . '</label> ' . 1287 '<input type="file" name="pkg_file" id="pkg_file" required /></p>' . 1288 '<p class="field"><label for="your_pwd1" class="classic required"><abbr title="' . __('Required field') . '">*</abbr> ' . __('Your password:') . '</label> ' . 1289 form::password(array('your_pwd', 'your_pwd1'), 20, 255, 1290 array( 1291 'extra_html' => 'required placeholder="' . __('Password') . '"', 1292 'autocomplete' => 'current-password' 1293 ) 1294 ) . '</p>' . 1295 '<p><input type="submit" name="upload_pkg" value="' . __('Upload') . '" />' . 1296 $this->core->formNonce() . '</p>' . 1297 '</form>'; 1298 1299 # 'Fetch module' form 1300 echo 1301 '<form method="post" action="' . $this->getURL() . '" id="fetchpkg" class="fieldset">' . 1302 '<h4>' . __('Download a zip file') . '</h4>' . 1303 '<p class="field"><label for="pkg_url" class="classic required"><abbr title="' . __('Required field') . '">*</abbr> ' . __('Zip file URL:') . '</label> ' . 1304 form::field(array('pkg_url', 'pkg_url'), 40, 255, '', '', '', false, 'required placeholder="' . __('URL') . '"') . '</p>' . 1305 '<p class="field"><label for="your_pwd2" class="classic required"><abbr title="' . __('Required field') . '">*</abbr> ' . __('Your password:') . '</label> ' . 1306 form::password(array('your_pwd', 'your_pwd2'), 20, 255, 1307 array( 1308 'extra_html' => 'required placeholder="' . __('Password') . '"', 1309 'autocomplete' => 'current-password' 1310 ) 1311 ) . '</p>' . 1312 '<p><input type="submit" name="fetch_pkg" value="' . __('Download') . '" />' . 1313 $this->core->formNonce() . '</p>' . 1314 '</form>'; 1315 1316 return $this; 1317 } 1318 //@} 1319 1320 /// @name Module configuration methods 1321 //@{ 1322 /** 1323 * Prepare module configuration. 1324 * 1325 * We need to get configuration content in three steps 1326 * and out of this class to keep backward compatibility. 1327 * 1328 * if ($xxx->setConfiguration()) { 1329 * include $xxx->includeConfiguration(); 1330 * } 1331 * $xxx->getConfiguration(); 1332 * ... [put here page headers and other stuff] 1333 * $xxx->displayConfiguration(); 1334 * 1335 * @param string $id Module to work on or it gather through REQUEST 1336 * @return True if config set 1337 */ 1338 public function setConfiguration($id = null) 1339 { 1340 if (empty($_REQUEST['conf']) || empty($_REQUEST['module']) && !$id) { 1341 return false; 1342 } 1343 1344 if (!empty($_REQUEST['module']) && empty($id)) { 1345 $id = $_REQUEST['module']; 1346 } 1347 1348 if (!$this->modules->moduleExists($id)) { 1349 $this->core->error->add(__('Unknow plugin ID')); 1350 return false; 1351 } 1352 1353 $module = $this->modules->getModules($id); 1354 $module = self::sanitizeModule($id, $module); 1355 $file = path::real($module['root'] . '/_config.php'); 1356 1357 if (!file_exists($file)) { 1358 $this->core->error->add(__('This plugin has no configuration file.')); 1359 return false; 1360 } 1361 1362 $this->config_module = $module; 1363 $this->config_file = $file; 1364 $this->config_content = ''; 1365 1366 if (!defined('DC_CONTEXT_MODULE')) { 1367 define('DC_CONTEXT_MODULE', true); 1368 } 1369 1370 return true; 1371 } 1372 1373 /** 1374 * Get path of module configuration file. 1375 * 1376 * @note Required previously set file info 1377 * @return Full path of config file or null 1378 */ 1379 public function includeConfiguration() 1380 { 1381 if (!$this->config_file) { 1382 return; 1383 } 1384 $this->setRedir($this->getURL() . '#plugins'); 1385 1386 ob_start(); 1387 1388 return $this->config_file; 1389 } 1390 1391 /** 1392 * Gather module configuration file content. 1393 * 1394 * @note Required previously file inclusion 1395 * @return True if content has been captured 1396 */ 1397 public function getConfiguration() 1398 { 1399 if ($this->config_file) { 1400 $this->config_content = ob_get_contents(); 1401 } 1402 1403 ob_end_clean(); 1404 1405 return !empty($this->file_content); 1406 } 1407 1408 /** 1409 * Display module configuration form. 1410 * 1411 * @note Required previously gathered content 1412 * @return adminModulesList self instance 1413 */ 1414 public function displayConfiguration() 1415 { 1416 if ($this->config_file) { 1417 1418 if (!$this->config_module['standalone_config']) { 1419 echo 1420 '<form id="module_config" action="' . $this->getURL('conf=1') . '" method="post" enctype="multipart/form-data">' . 1421 '<h3>' . sprintf(__('Configure "%s"'), html::escapeHTML($this->config_module['name'])) . '</h3>' . 1422 '<p><a class="back" href="' . $this->getRedir() . '">' . __('Back') . '</a></p>'; 1423 } 1424 1425 echo $this->config_content; 1426 1427 if (!$this->config_module['standalone_config']) { 1428 echo 1429 '<p class="clear"><input type="submit" name="save" value="' . __('Save') . '" />' . 1430 form::hidden('module', $this->config_module['id']) . 1431 form::hidden('redir', $this->getRedir()) . 1432 $this->core->formNonce() . '</p>' . 1433 '</form>'; 1434 } 1435 } 1436 1437 return $this; 1438 } 1439 //@} 1440 1441 /** 1442 * Helper to sanitize a string. 1443 * 1444 * Used for search or id. 1445 * 1446 * @param string $str String to sanitize 1447 * @return Sanitized string 1448 */ 1449 public static function sanitizeString($str) 1450 { 1451 return preg_replace('/[^A-Za-z0-9\@\#+_-]/', '', strtolower($str)); 1452 } 1470 1453 } 1471 1454 … … 1477 1460 class adminThemesList extends adminModulesList 1478 1461 { 1479 /** 1480 * Constructor. 1481 * 1482 * Note that this creates dcStore instance. 1483 * 1484 * @param object $modules dcModules instance 1485 * @param string $modules_root Modules root directories 1486 * @param string $xml_url URL of modules feed from repository 1487 */ 1488 public function __construct(dcModules $modules, $modules_root, $xml_url) 1489 { 1490 parent::__construct($modules, $modules_root, $xml_url); 1491 $this->page_url = $this->core->adminurl->get('admin.blog.theme'); 1492 } 1493 1494 public function displayModules($cols=array('name', 'config', 'version', 'desc'), $actions=array(), $nav_limit=false) 1495 { 1496 echo 1497 '<form action="'.$this->getURL().'" method="post" class="modules-form-actions">'. 1498 '<div id="'.html::escapeHTML($this->list_id).'" class="modules'.(in_array('expander', $cols) ? ' expandable' : '').' one-box">'; 1499 1500 $sort_field = $this->getSort(); 1501 1502 # Sort modules by id 1503 $modules = $this->getSearch() === null ? 1504 self::sortModules($this->data, $sort_field, $this->sort_asc) : 1505 $this->data; 1506 1507 $res = ''; 1508 $count = 0; 1509 foreach ($modules as $id => $module) 1510 { 1511 # Show only requested modules 1512 if ($nav_limit && $this->getSearch() === null) { 1513 $char = substr($module[$sort_field], 0, 1); 1514 if (!in_array($char, $this->nav_list)) { 1515 $char = $this->nav_special; 1516 } 1517 if ($this->getIndex() != $char) { 1518 continue; 1519 } 1520 } 1521 1522 $current = $this->core->blog->settings->system->theme == $id && $this->modules->moduleExists($id); 1523 $distrib = self::isDistributedModule($id) ? ' dc-box' : ''; 1524 1525 $line = 1526 '<div class="box '.($current ? 'medium current-theme' : 'theme').$distrib.'">'; 1527 1528 if (in_array('name', $cols) && !$current) { 1529 $line .= 1530 '<h4 class="module-name">'; 1531 1532 if (in_array('checkbox', $cols)) { 1533 $line .= 1534 '<label for="'.html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id).'">'. 1535 form::checkbox(array('modules['.$count.']', html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id)), html::escapeHTML($id)). 1536 html::escapeHTML($module['name']). 1537 '</label>'; 1538 1539 } 1540 else { 1541 $line .= 1542 form::hidden(array('modules['.$count.']'), html::escapeHTML($id)). 1543 html::escapeHTML($module['name']); 1544 } 1545 1546 $line .= 1547 $this->core->formNonce(). 1548 '</h4>'; 1549 } 1550 1551 # Display score only for debug purpose 1552 if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) { 1553 $line .= 1554 '<p class="module-score debug">'.sprintf(__('Score: %s'), $module['score']).'</p>'; 1555 } 1556 1557 if (in_array('sshot', $cols)) { 1558 # Screenshot from url 1559 if (preg_match('#^http(s)?://#', $module['sshot'])) { 1560 $sshot = $module['sshot']; 1561 } 1562 # Screenshot from installed module 1563 elseif (file_exists($this->core->blog->themes_path.'/'.$id.'/screenshot.jpg')) { 1564 $sshot = $this->getURL('shot='.rawurlencode($id)); 1565 } 1566 # Default screenshot 1567 else { 1568 $sshot = 'images/noscreenshot.png'; 1569 } 1570 1571 $line .= 1572 '<div class="module-sshot"><img src="'.$sshot.'" alt="'. 1573 sprintf(__('%s screenshot.'), html::escapeHTML($module['name'])).'" /></div>'; 1574 } 1575 1576 $line .= 1577 '<div class="module-infos toggle-bloc">'; 1578 1579 if (in_array('name', $cols) && $current) { 1580 $line .= 1581 '<h4 class="module-name">'; 1582 1583 if (in_array('checkbox', $cols)) { 1584 $line .= 1585 '<label for="'.html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id).'">'. 1586 form::checkbox(array('modules['.$count.']', html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id)), html::escapeHTML($id)). 1587 html::escapeHTML($module['name']). 1588 '</label>'; 1589 } 1590 else { 1591 $line .= 1592 form::hidden(array('modules['.$count.']'), html::escapeHTML($id)). 1593 html::escapeHTML($module['name']); 1594 } 1595 1596 $line .= 1597 '</h4>'; 1598 } 1599 1600 $line .= 1601 '<p>'; 1602 1603 if (in_array('desc', $cols)) { 1604 $line .= 1605 '<span class="module-desc">'.html::escapeHTML(__($module['desc'])).'</span> '; 1606 } 1607 1608 if (in_array('author', $cols)) { 1609 $line .= 1610 '<span class="module-author">'.sprintf(__('by %s'),html::escapeHTML($module['author'])).'</span> '; 1611 } 1612 1613 if (in_array('version', $cols)) { 1614 $line .= 1615 '<span class="module-version">'.sprintf(__('version %s'),html::escapeHTML($module['version'])).'</span> '; 1616 } 1617 1618 if (in_array('current_version', $cols)) { 1619 $line .= 1620 '<span class="module-current-version">'.sprintf(__('(current version %s)'),html::escapeHTML($module['current_version'])).'</span> '; 1621 } 1622 1623 if (in_array('parent', $cols) && !empty($module['parent'])) { 1624 if ($this->modules->moduleExists($module['parent'])) { 1625 $line .= 1626 '<span class="module-parent-ok">'.sprintf(__('(built on "%s")'),html::escapeHTML($module['parent'])).'</span> '; 1627 } 1628 else { 1629 $line .= 1630 '<span class="module-parent-missing">'.sprintf(__('(requires "%s")'),html::escapeHTML($module['parent'])).'</span> '; 1631 } 1632 } 1633 1634 $has_details = in_array('details', $cols) && !empty($module['details']); 1635 $has_support = in_array('support', $cols) && !empty($module['support']); 1636 if ($has_details || $has_support) { 1637 $line .= 1638 '<span class="mod-more">'; 1639 1640 if ($has_details) { 1641 $line .= 1642 '<a class="module-details" href="'.$module['details'].'">'.__('Details').'</a>'; 1643 } 1644 1645 if ($has_support) { 1646 $line .= 1647 ' - <a class="module-support" href="'.$module['support'].'">'.__('Support').'</a>'; 1648 } 1649 1650 $line .= 1651 '</span>'; 1652 } 1653 1654 $line .= 1655 '</p>'. 1656 '</div>'; 1657 1658 $line .= 1659 '<div class="module-actions toggle-bloc">'; 1660 1661 # Plugins actions 1662 if ($current) { 1663 1664 # _GET actions 1665 if (file_exists(path::real($this->core->blog->themes_path.'/'.$id).'/style.css')) { 1666 $theme_url = preg_match('#^http(s)?://#', $this->core->blog->settings->system->themes_url) ? 1667 http::concatURL($this->core->blog->settings->system->themes_url, '/'.$id) : 1668 http::concatURL($this->core->blog->url, $this->core->blog->settings->system->themes_url.'/'.$id); 1669 $line .= 1670 '<p><a href="'.$theme_url.'/style.css">'.__('View stylesheet').'</a></p>'; 1671 } 1672 1673 $line .= '<div class="current-actions">'; 1674 1675 if (file_exists(path::real($this->core->blog->themes_path.'/'.$id).'/_config.php')) { 1676 $line .= 1677 '<p><a href="'.$this->getURL('module='.$id.'&conf=1', false).'" class="button submit">'.__('Configure theme').'</a></p>'; 1678 } 1679 1680 # --BEHAVIOR-- adminCurrentThemeDetails 1681 $line .= 1682 $this->core->callBehavior('adminCurrentThemeDetails', $this->core, $id, $module); 1683 1684 $line .= '</div>'; 1685 } 1686 1687 # _POST actions 1688 if (!empty($actions)) { 1689 $line .= 1690 '<p>'.implode(' ', $this->getActions($id, $module, $actions)).'</p>'; 1691 } 1692 1693 $line .= 1694 '</div>'; 1695 1696 $line .= 1697 '</div>'; 1698 1699 $count++; 1700 1701 $res = $current ? $line.$res : $res.$line; 1702 } 1703 1704 echo 1705 $res. 1706 '</div>'; 1707 1708 if(!$count && $this->getSearch() === null) { 1709 echo 1710 '<p class="message">'.__('No themes matched your search.').'</p>'; 1711 } 1712 1713 elseif ((in_array('checkbox', $cols) || $count > 1) && !empty($actions) && $this->core->auth->isSuperAdmin()) { 1714 $buttons = $this->getGlobalActions($actions, in_array('checkbox', $cols)); 1715 1716 if (!empty($buttons)) { 1717 if (in_array('checkbox', $cols)) { 1718 echo 1719 '<p class="checkboxes-helpers"></p>'; 1720 } 1721 echo '<div>'.implode(' ', $buttons).'</div>'; 1722 } 1723 } 1724 1725 echo 1726 '</form>'; 1727 1728 return $this; 1729 } 1730 1731 protected function getActions($id, $module, $actions) 1732 { 1733 $submits = array(); 1734 1735 $this->core->blog->settings->addNamespace('system'); 1736 if ($id != $this->core->blog->settings->system->theme) { 1737 1738 # Select theme to use on curent blog 1739 if (in_array('select', $actions)) { 1740 $submits[] = 1741 '<input type="submit" name="select['.html::escapeHTML($id).']" value="'.__('Use this one').'" />'; 1742 } 1743 } 1744 1745 return array_merge( 1746 $submits, 1747 parent::getActions($id, $module, $actions) 1748 ); 1749 } 1750 1751 protected function getGlobalActions($actions, $with_selection=false) 1752 { 1753 $submits = array(); 1754 1755 foreach($actions as $action) { 1756 switch($action) { 1757 1758 # Update (from store) 1759 case 'update': if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 1760 $submits[] = 1761 '<input type="submit" name="update" value="'.($with_selection ? 1762 __('Update selected themes') : 1763 __('Update all themes from this list') 1764 ).'" />'.$this->core->formNonce(); 1765 } break; 1766 1767 # Behavior 1768 case 'behavior': 1769 1770 # --BEHAVIOR-- adminModulesListGetGlobalActions 1771 $tmp = $this->core->callBehavior('adminModulesListGetGlobalActions', $this); 1772 1773 if (!empty($tmp)) { 1774 $submits[] = $tmp; 1775 } 1776 break; 1777 } 1778 } 1779 1780 return $submits; 1781 } 1782 1783 public function doActions() 1784 { 1785 if (empty($_POST) || !empty($_REQUEST['conf'])) { 1786 return null; 1787 } 1788 1789 $modules = !empty($_POST['modules']) && is_array($_POST['modules']) ? array_values($_POST['modules']) : array(); 1790 1791 if (!empty($_POST['select'])) { 1792 1793 # Can select only one theme at a time! 1794 if (is_array($_POST['select'])) { 1795 $modules = array_keys($_POST['select']); 1796 $id = $modules[0]; 1797 1798 if (!$this->modules->moduleExists($id)) { 1799 throw new Exception(__('No such theme.')); 1800 } 1801 1802 $this->core->blog->settings->addNamespace('system'); 1803 $this->core->blog->settings->system->put('theme',$id); 1804 $this->core->blog->triggerBlog(); 1805 1806 dcPage::addSuccessNotice(__('Theme has been successfully selected.')); 1807 http::redirect($this->getURL().'#themes'); 1808 } 1809 } 1810 1811 else { 1812 if (!$this->isWritablePath()) { 1813 return null; 1814 } 1815 1816 if ($this->core->auth->isSuperAdmin() && !empty($_POST['activate'])) { 1817 1818 if (is_array($_POST['activate'])) { 1819 $modules = array_keys($_POST['activate']); 1820 } 1821 1822 $list = $this->modules->getDisabledModules(); 1823 if (empty($list)) { 1824 throw new Exception(__('No such theme.')); 1825 } 1826 1827 $count = 0; 1828 foreach($list as $id => $module) { 1829 1830 if (!in_array($id, $modules)) { 1831 continue; 1832 } 1833 1834 # --BEHAVIOR-- themeBeforeActivate 1835 $this->core->callBehavior('themeBeforeActivate', $id); 1836 1837 $this->modules->activateModule($id); 1838 1839 # --BEHAVIOR-- themeAfterActivate 1840 $this->core->callBehavior('themeAfterActivate', $id); 1841 1842 $count++; 1843 } 1844 1845 dcPage::addSuccessNotice( 1846 __('Theme has been successfully activated.', 'Themes have been successuflly activated.', $count) 1847 ); 1848 http::redirect($this->getURL()); 1849 } 1850 1851 elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['deactivate'])) { 1852 1853 if (is_array($_POST['deactivate'])) { 1854 $modules = array_keys($_POST['deactivate']); 1855 } 1856 1857 $list = $this->modules->getModules(); 1858 if (empty($list)) { 1859 throw new Exception(__('No such theme.')); 1860 } 1861 1862 $failed = false; 1863 $count = 0; 1864 foreach($list as $id => $module) { 1865 1866 if (!in_array($id, $modules)) { 1867 continue; 1868 } 1869 1870 if (!$module['root_writable']) { 1871 $failed = true; 1872 continue; 1873 } 1874 1875 $module[$id] = $id; 1876 1877 # --BEHAVIOR-- themeBeforeDeactivate 1878 $this->core->callBehavior('themeBeforeDeactivate', $module); 1879 1880 $this->modules->deactivateModule($id); 1881 1882 # --BEHAVIOR-- themeAfterDeactivate 1883 $this->core->callBehavior('themeAfterDeactivate', $module); 1884 1885 $count++; 1886 } 1887 1888 if ($failed) { 1889 dcPage::addWarningNotice(__('Some themes have not been deactivated.')); 1890 } 1891 else { 1892 dcPage::addSuccessNotice( 1893 __('Theme has been successfully deactivated.', 'Themes have been successuflly deactivated.', $count) 1894 ); 1895 } 1896 http::redirect($this->getURL()); 1897 } 1898 1899 elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['delete'])) { 1900 1901 if (is_array($_POST['delete'])) { 1902 $modules = array_keys($_POST['delete']); 1903 } 1904 1905 $list = $this->modules->getDisabledModules(); 1906 1907 $failed = false; 1908 $count = 0; 1909 foreach($modules as $id) 1910 { 1911 if (!isset($list[$id])) { 1912 1913 if (!$this->modules->moduleExists($id)) { 1914 throw new Exception(__('No such theme.')); 1915 } 1916 1917 $module = $this->modules->getModules($id); 1918 $module['id'] = $id; 1919 1920 if (!$this->isDeletablePath($module['root'])) { 1921 $failed = true; 1922 continue; 1923 } 1924 1925 # --BEHAVIOR-- themeBeforeDelete 1926 $this->core->callBehavior('themeBeforeDelete', $module); 1927 1928 $this->modules->deleteModule($id); 1929 1930 # --BEHAVIOR-- themeAfterDelete 1931 $this->core->callBehavior('themeAfterDelete', $module); 1932 } 1933 else { 1934 $this->modules->deleteModule($id, true); 1935 } 1936 1937 $count++; 1938 } 1939 1940 if (!$count && $failed) { 1941 throw new Exception(__("You don't have permissions to delete this theme.")); 1942 } 1943 elseif ($failed) { 1944 dcPage::addWarningNotice(__('Some themes have not been delete.')); 1945 } 1946 else { 1947 dcPage::addSuccessNotice( 1948 __('Theme has been successfully deleted.', 'Themes have been successuflly deleted.', $count) 1949 ); 1950 } 1951 http::redirect($this->getURL()); 1952 } 1953 1954 elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['install'])) { 1955 1956 if (is_array($_POST['install'])) { 1957 $modules = array_keys($_POST['install']); 1958 } 1959 1960 $list = $this->store->get(); 1961 1962 if (empty($list)) { 1963 throw new Exception(__('No such theme.')); 1964 } 1965 1966 $count = 0; 1967 foreach($list as $id => $module) { 1968 1969 if (!in_array($id, $modules)) { 1970 continue; 1971 } 1972 1973 $dest = $this->getPath().'/'.basename($module['file']); 1974 1975 # --BEHAVIOR-- themeBeforeAdd 1976 $this->core->callBehavior('themeBeforeAdd', $module); 1977 1978 $this->store->process($module['file'], $dest); 1979 1980 # --BEHAVIOR-- themeAfterAdd 1981 $this->core->callBehavior('themeAfterAdd', $module); 1982 1983 $count++; 1984 } 1985 1986 dcPage::addSuccessNotice( 1987 __('Theme has been successfully installed.', 'Themes have been successuflly installed.', $count) 1988 ); 1989 http::redirect($this->getURL()); 1990 } 1991 1992 elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['update'])) { 1993 1994 if (is_array($_POST['update'])) { 1995 $modules = array_keys($_POST['update']); 1996 } 1997 1998 $list = $this->store->get(true); 1999 if (empty($list)) { 2000 throw new Exception(__('No such theme.')); 2001 } 2002 2003 $count = 0; 2004 foreach($list as $module) { 2005 2006 if (!in_array($module['id'], $modules)) { 2007 continue; 2008 } 2009 2010 $dest = $module['root'].'/../'.basename($module['file']); 2011 2012 # --BEHAVIOR-- themeBeforeUpdate 2013 $this->core->callBehavior('themeBeforeUpdate', $module); 2014 2015 $this->store->process($module['file'], $dest); 2016 2017 # --BEHAVIOR-- themeAfterUpdate 2018 $this->core->callBehavior('themeAfterUpdate', $module); 2019 2020 $count++; 2021 } 2022 2023 $tab = $count && $count == count($list) ? '#themes' : '#update'; 2024 2025 dcPage::addSuccessNotice( 2026 __('Theme has been successfully updated.', 'Themes have been successuflly updated.', $count) 2027 ); 2028 http::redirect($this->getURL().$tab); 2029 } 2030 2031 # Manual actions 2032 elseif (!empty($_POST['upload_pkg']) && !empty($_FILES['pkg_file']) 2033 || !empty($_POST['fetch_pkg']) && !empty($_POST['pkg_url'])) 2034 { 2035 if (empty($_POST['your_pwd']) || !$this->core->auth->checkPassword($_POST['your_pwd'])) { 2036 throw new Exception(__('Password verification failed')); 2037 } 2038 2039 if (!empty($_POST['upload_pkg'])) { 2040 files::uploadStatus($_FILES['pkg_file']); 2041 2042 $dest = $this->getPath().'/'.$_FILES['pkg_file']['name']; 2043 if (!move_uploaded_file($_FILES['pkg_file']['tmp_name'], $dest)) { 2044 throw new Exception(__('Unable to move uploaded file.')); 2045 } 2046 } 2047 else { 2048 $url = urldecode($_POST['pkg_url']); 2049 $dest = $this->getPath().'/'.basename($url); 2050 $this->store->download($url, $dest); 2051 } 2052 2053 # --BEHAVIOR-- themeBeforeAdd 2054 $this->core->callBehavior('themeBeforeAdd', null); 2055 2056 $ret_code = $this->store->install($dest); 2057 2058 # --BEHAVIOR-- themeAfterAdd 2059 $this->core->callBehavior('themeAfterAdd', null); 2060 2061 dcPage::addSuccessNotice($ret_code == 2 ? 2062 __('Theme has been successfully updated.') : 2063 __('Theme has been successfully installed.') 2064 ); 2065 http::redirect($this->getURL().'#themes'); 2066 } 2067 2068 else { 2069 2070 # --BEHAVIOR-- adminModulesListDoActions 2071 $this->core->callBehavior('adminModulesListDoActions', $this, $modules, 'theme'); 2072 2073 } 2074 } 2075 2076 return null; 2077 } 1462 /** 1463 * Constructor. 1464 * 1465 * Note that this creates dcStore instance. 1466 * 1467 * @param object $modules dcModules instance 1468 * @param string $modules_root Modules root directories 1469 * @param string $xml_url URL of modules feed from repository 1470 */ 1471 public function __construct(dcModules $modules, $modules_root, $xml_url) 1472 { 1473 parent::__construct($modules, $modules_root, $xml_url); 1474 $this->page_url = $this->core->adminurl->get('admin.blog.theme'); 1475 } 1476 1477 public function displayModules($cols = array('name', 'config', 'version', 'desc'), $actions = array(), $nav_limit = false) 1478 { 1479 echo 1480 '<form action="' . $this->getURL() . '" method="post" class="modules-form-actions">' . 1481 '<div id="' . html::escapeHTML($this->list_id) . '" class="modules' . (in_array('expander', $cols) ? ' expandable' : '') . ' one-box">'; 1482 1483 $sort_field = $this->getSort(); 1484 1485 # Sort modules by id 1486 $modules = $this->getSearch() === null ? 1487 self::sortModules($this->data, $sort_field, $this->sort_asc) : 1488 $this->data; 1489 1490 $res = ''; 1491 $count = 0; 1492 foreach ($modules as $id => $module) { 1493 # Show only requested modules 1494 if ($nav_limit && $this->getSearch() === null) { 1495 $char = substr($module[$sort_field], 0, 1); 1496 if (!in_array($char, $this->nav_list)) { 1497 $char = $this->nav_special; 1498 } 1499 if ($this->getIndex() != $char) { 1500 continue; 1501 } 1502 } 1503 1504 $current = $this->core->blog->settings->system->theme == $id && $this->modules->moduleExists($id); 1505 $distrib = self::isDistributedModule($id) ? ' dc-box' : ''; 1506 1507 $line = 1508 '<div class="box ' . ($current ? 'medium current-theme' : 'theme') . $distrib . '">'; 1509 1510 if (in_array('name', $cols) && !$current) { 1511 $line .= 1512 '<h4 class="module-name">'; 1513 1514 if (in_array('checkbox', $cols)) { 1515 $line .= 1516 '<label for="' . html::escapeHTML($this->list_id) . '_modules_' . html::escapeHTML($id) . '">' . 1517 form::checkbox(array('modules[' . $count . ']', html::escapeHTML($this->list_id) . '_modules_' . html::escapeHTML($id)), html::escapeHTML($id)) . 1518 html::escapeHTML($module['name']) . 1519 '</label>'; 1520 1521 } else { 1522 $line .= 1523 form::hidden(array('modules[' . $count . ']'), html::escapeHTML($id)) . 1524 html::escapeHTML($module['name']); 1525 } 1526 1527 $line .= 1528 $this->core->formNonce() . 1529 '</h4>'; 1530 } 1531 1532 # Display score only for debug purpose 1533 if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) { 1534 $line .= 1535 '<p class="module-score debug">' . sprintf(__('Score: %s'), $module['score']) . '</p>'; 1536 } 1537 1538 if (in_array('sshot', $cols)) { 1539 # Screenshot from url 1540 if (preg_match('#^http(s)?://#', $module['sshot'])) { 1541 $sshot = $module['sshot']; 1542 } 1543 # Screenshot from installed module 1544 elseif (file_exists($this->core->blog->themes_path . '/' . $id . '/screenshot.jpg')) { 1545 $sshot = $this->getURL('shot=' . rawurlencode($id)); 1546 } 1547 # Default screenshot 1548 else { 1549 $sshot = 'images/noscreenshot.png'; 1550 } 1551 1552 $line .= 1553 '<div class="module-sshot"><img src="' . $sshot . '" alt="' . 1554 sprintf(__('%s screenshot.'), html::escapeHTML($module['name'])) . '" /></div>'; 1555 } 1556 1557 $line .= 1558 '<div class="module-infos toggle-bloc">'; 1559 1560 if (in_array('name', $cols) && $current) { 1561 $line .= 1562 '<h4 class="module-name">'; 1563 1564 if (in_array('checkbox', $cols)) { 1565 $line .= 1566 '<label for="' . html::escapeHTML($this->list_id) . '_modules_' . html::escapeHTML($id) . '">' . 1567 form::checkbox(array('modules[' . $count . ']', html::escapeHTML($this->list_id) . '_modules_' . html::escapeHTML($id)), html::escapeHTML($id)) . 1568 html::escapeHTML($module['name']) . 1569 '</label>'; 1570 } else { 1571 $line .= 1572 form::hidden(array('modules[' . $count . ']'), html::escapeHTML($id)) . 1573 html::escapeHTML($module['name']); 1574 } 1575 1576 $line .= 1577 '</h4>'; 1578 } 1579 1580 $line .= 1581 '<p>'; 1582 1583 if (in_array('desc', $cols)) { 1584 $line .= 1585 '<span class="module-desc">' . html::escapeHTML(__($module['desc'])) . '</span> '; 1586 } 1587 1588 if (in_array('author', $cols)) { 1589 $line .= 1590 '<span class="module-author">' . sprintf(__('by %s'), html::escapeHTML($module['author'])) . '</span> '; 1591 } 1592 1593 if (in_array('version', $cols)) { 1594 $line .= 1595 '<span class="module-version">' . sprintf(__('version %s'), html::escapeHTML($module['version'])) . '</span> '; 1596 } 1597 1598 if (in_array('current_version', $cols)) { 1599 $line .= 1600 '<span class="module-current-version">' . sprintf(__('(current version %s)'), html::escapeHTML($module['current_version'])) . '</span> '; 1601 } 1602 1603 if (in_array('parent', $cols) && !empty($module['parent'])) { 1604 if ($this->modules->moduleExists($module['parent'])) { 1605 $line .= 1606 '<span class="module-parent-ok">' . sprintf(__('(built on "%s")'), html::escapeHTML($module['parent'])) . '</span> '; 1607 } else { 1608 $line .= 1609 '<span class="module-parent-missing">' . sprintf(__('(requires "%s")'), html::escapeHTML($module['parent'])) . '</span> '; 1610 } 1611 } 1612 1613 $has_details = in_array('details', $cols) && !empty($module['details']); 1614 $has_support = in_array('support', $cols) && !empty($module['support']); 1615 if ($has_details || $has_support) { 1616 $line .= 1617 '<span class="mod-more">'; 1618 1619 if ($has_details) { 1620 $line .= 1621 '<a class="module-details" href="' . $module['details'] . '">' . __('Details') . '</a>'; 1622 } 1623 1624 if ($has_support) { 1625 $line .= 1626 ' - <a class="module-support" href="' . $module['support'] . '">' . __('Support') . '</a>'; 1627 } 1628 1629 $line .= 1630 '</span>'; 1631 } 1632 1633 $line .= 1634 '</p>' . 1635 '</div>'; 1636 1637 $line .= 1638 '<div class="module-actions toggle-bloc">'; 1639 1640 # Plugins actions 1641 if ($current) { 1642 1643 # _GET actions 1644 if (file_exists(path::real($this->core->blog->themes_path . '/' . $id) . '/style.css')) { 1645 $theme_url = preg_match('#^http(s)?://#', $this->core->blog->settings->system->themes_url) ? 1646 http::concatURL($this->core->blog->settings->system->themes_url, '/' . $id) : 1647 http::concatURL($this->core->blog->url, $this->core->blog->settings->system->themes_url . '/' . $id); 1648 $line .= 1649 '<p><a href="' . $theme_url . '/style.css">' . __('View stylesheet') . '</a></p>'; 1650 } 1651 1652 $line .= '<div class="current-actions">'; 1653 1654 if (file_exists(path::real($this->core->blog->themes_path . '/' . $id) . '/_config.php')) { 1655 $line .= 1656 '<p><a href="' . $this->getURL('module=' . $id . '&conf=1', false) . '" class="button submit">' . __('Configure theme') . '</a></p>'; 1657 } 1658 1659 # --BEHAVIOR-- adminCurrentThemeDetails 1660 $line .= 1661 $this->core->callBehavior('adminCurrentThemeDetails', $this->core, $id, $module); 1662 1663 $line .= '</div>'; 1664 } 1665 1666 # _POST actions 1667 if (!empty($actions)) { 1668 $line .= 1669 '<p>' . implode(' ', $this->getActions($id, $module, $actions)) . '</p>'; 1670 } 1671 1672 $line .= 1673 '</div>'; 1674 1675 $line .= 1676 '</div>'; 1677 1678 $count++; 1679 1680 $res = $current ? $line . $res : $res . $line; 1681 } 1682 1683 echo 1684 $res . 1685 '</div>'; 1686 1687 if (!$count && $this->getSearch() === null) { 1688 echo 1689 '<p class="message">' . __('No themes matched your search.') . '</p>'; 1690 } elseif ((in_array('checkbox', $cols) || $count > 1) && !empty($actions) && $this->core->auth->isSuperAdmin()) { 1691 $buttons = $this->getGlobalActions($actions, in_array('checkbox', $cols)); 1692 1693 if (!empty($buttons)) { 1694 if (in_array('checkbox', $cols)) { 1695 echo 1696 '<p class="checkboxes-helpers"></p>'; 1697 } 1698 echo '<div>' . implode(' ', $buttons) . '</div>'; 1699 } 1700 } 1701 1702 echo 1703 '</form>'; 1704 1705 return $this; 1706 } 1707 1708 protected function getActions($id, $module, $actions) 1709 { 1710 $submits = array(); 1711 1712 $this->core->blog->settings->addNamespace('system'); 1713 if ($id != $this->core->blog->settings->system->theme) { 1714 1715 # Select theme to use on curent blog 1716 if (in_array('select', $actions)) { 1717 $submits[] = 1718 '<input type="submit" name="select[' . html::escapeHTML($id) . ']" value="' . __('Use this one') . '" />'; 1719 } 1720 } 1721 1722 return array_merge( 1723 $submits, 1724 parent::getActions($id, $module, $actions) 1725 ); 1726 } 1727 1728 protected function getGlobalActions($actions, $with_selection = false) 1729 { 1730 $submits = array(); 1731 1732 foreach ($actions as $action) { 1733 switch ($action) { 1734 1735 # Update (from store) 1736 case 'update':if ($this->core->auth->isSuperAdmin() && $this->path_writable) { 1737 $submits[] = 1738 '<input type="submit" name="update" value="' . ($with_selection ? 1739 __('Update selected themes') : 1740 __('Update all themes from this list') 1741 ) . '" />' . $this->core->formNonce(); 1742 }break; 1743 1744 # Behavior 1745 case 'behavior': 1746 1747 # --BEHAVIOR-- adminModulesListGetGlobalActions 1748 $tmp = $this->core->callBehavior('adminModulesListGetGlobalActions', $this); 1749 1750 if (!empty($tmp)) { 1751 $submits[] = $tmp; 1752 } 1753 break; 1754 } 1755 } 1756 1757 return $submits; 1758 } 1759 1760 public function doActions() 1761 { 1762 if (empty($_POST) || !empty($_REQUEST['conf'])) { 1763 return; 1764 } 1765 1766 $modules = !empty($_POST['modules']) && is_array($_POST['modules']) ? array_values($_POST['modules']) : array(); 1767 1768 if (!empty($_POST['select'])) { 1769 1770 # Can select only one theme at a time! 1771 if (is_array($_POST['select'])) { 1772 $modules = array_keys($_POST['select']); 1773 $id = $modules[0]; 1774 1775 if (!$this->modules->moduleExists($id)) { 1776 throw new Exception(__('No such theme.')); 1777 } 1778 1779 $this->core->blog->settings->addNamespace('system'); 1780 $this->core->blog->settings->system->put('theme', $id); 1781 $this->core->blog->triggerBlog(); 1782 1783 dcPage::addSuccessNotice(__('Theme has been successfully selected.')); 1784 http::redirect($this->getURL() . '#themes'); 1785 } 1786 } else { 1787 if (!$this->isWritablePath()) { 1788 return; 1789 } 1790 1791 if ($this->core->auth->isSuperAdmin() && !empty($_POST['activate'])) { 1792 1793 if (is_array($_POST['activate'])) { 1794 $modules = array_keys($_POST['activate']); 1795 } 1796 1797 $list = $this->modules->getDisabledModules(); 1798 if (empty($list)) { 1799 throw new Exception(__('No such theme.')); 1800 } 1801 1802 $count = 0; 1803 foreach ($list as $id => $module) { 1804 1805 if (!in_array($id, $modules)) { 1806 continue; 1807 } 1808 1809 # --BEHAVIOR-- themeBeforeActivate 1810 $this->core->callBehavior('themeBeforeActivate', $id); 1811 1812 $this->modules->activateModule($id); 1813 1814 # --BEHAVIOR-- themeAfterActivate 1815 $this->core->callBehavior('themeAfterActivate', $id); 1816 1817 $count++; 1818 } 1819 1820 dcPage::addSuccessNotice( 1821 __('Theme has been successfully activated.', 'Themes have been successuflly activated.', $count) 1822 ); 1823 http::redirect($this->getURL()); 1824 } elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['deactivate'])) { 1825 1826 if (is_array($_POST['deactivate'])) { 1827 $modules = array_keys($_POST['deactivate']); 1828 } 1829 1830 $list = $this->modules->getModules(); 1831 if (empty($list)) { 1832 throw new Exception(__('No such theme.')); 1833 } 1834 1835 $failed = false; 1836 $count = 0; 1837 foreach ($list as $id => $module) { 1838 1839 if (!in_array($id, $modules)) { 1840 continue; 1841 } 1842 1843 if (!$module['root_writable']) { 1844 $failed = true; 1845 continue; 1846 } 1847 1848 $module[$id] = $id; 1849 1850 # --BEHAVIOR-- themeBeforeDeactivate 1851 $this->core->callBehavior('themeBeforeDeactivate', $module); 1852 1853 $this->modules->deactivateModule($id); 1854 1855 # --BEHAVIOR-- themeAfterDeactivate 1856 $this->core->callBehavior('themeAfterDeactivate', $module); 1857 1858 $count++; 1859 } 1860 1861 if ($failed) { 1862 dcPage::addWarningNotice(__('Some themes have not been deactivated.')); 1863 } else { 1864 dcPage::addSuccessNotice( 1865 __('Theme has been successfully deactivated.', 'Themes have been successuflly deactivated.', $count) 1866 ); 1867 } 1868 http::redirect($this->getURL()); 1869 } elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['delete'])) { 1870 1871 if (is_array($_POST['delete'])) { 1872 $modules = array_keys($_POST['delete']); 1873 } 1874 1875 $list = $this->modules->getDisabledModules(); 1876 1877 $failed = false; 1878 $count = 0; 1879 foreach ($modules as $id) { 1880 if (!isset($list[$id])) { 1881 1882 if (!$this->modules->moduleExists($id)) { 1883 throw new Exception(__('No such theme.')); 1884 } 1885 1886 $module = $this->modules->getModules($id); 1887 $module['id'] = $id; 1888 1889 if (!$this->isDeletablePath($module['root'])) { 1890 $failed = true; 1891 continue; 1892 } 1893 1894 # --BEHAVIOR-- themeBeforeDelete 1895 $this->core->callBehavior('themeBeforeDelete', $module); 1896 1897 $this->modules->deleteModule($id); 1898 1899 # --BEHAVIOR-- themeAfterDelete 1900 $this->core->callBehavior('themeAfterDelete', $module); 1901 } else { 1902 $this->modules->deleteModule($id, true); 1903 } 1904 1905 $count++; 1906 } 1907 1908 if (!$count && $failed) { 1909 throw new Exception(__("You don't have permissions to delete this theme.")); 1910 } elseif ($failed) { 1911 dcPage::addWarningNotice(__('Some themes have not been delete.')); 1912 } else { 1913 dcPage::addSuccessNotice( 1914 __('Theme has been successfully deleted.', 'Themes have been successuflly deleted.', $count) 1915 ); 1916 } 1917 http::redirect($this->getURL()); 1918 } elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['install'])) { 1919 1920 if (is_array($_POST['install'])) { 1921 $modules = array_keys($_POST['install']); 1922 } 1923 1924 $list = $this->store->get(); 1925 1926 if (empty($list)) { 1927 throw new Exception(__('No such theme.')); 1928 } 1929 1930 $count = 0; 1931 foreach ($list as $id => $module) { 1932 1933 if (!in_array($id, $modules)) { 1934 continue; 1935 } 1936 1937 $dest = $this->getPath() . '/' . basename($module['file']); 1938 1939 # --BEHAVIOR-- themeBeforeAdd 1940 $this->core->callBehavior('themeBeforeAdd', $module); 1941 1942 $this->store->process($module['file'], $dest); 1943 1944 # --BEHAVIOR-- themeAfterAdd 1945 $this->core->callBehavior('themeAfterAdd', $module); 1946 1947 $count++; 1948 } 1949 1950 dcPage::addSuccessNotice( 1951 __('Theme has been successfully installed.', 'Themes have been successuflly installed.', $count) 1952 ); 1953 http::redirect($this->getURL()); 1954 } elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['update'])) { 1955 1956 if (is_array($_POST['update'])) { 1957 $modules = array_keys($_POST['update']); 1958 } 1959 1960 $list = $this->store->get(true); 1961 if (empty($list)) { 1962 throw new Exception(__('No such theme.')); 1963 } 1964 1965 $count = 0; 1966 foreach ($list as $module) { 1967 1968 if (!in_array($module['id'], $modules)) { 1969 continue; 1970 } 1971 1972 $dest = $module['root'] . '/../' . basename($module['file']); 1973 1974 # --BEHAVIOR-- themeBeforeUpdate 1975 $this->core->callBehavior('themeBeforeUpdate', $module); 1976 1977 $this->store->process($module['file'], $dest); 1978 1979 # --BEHAVIOR-- themeAfterUpdate 1980 $this->core->callBehavior('themeAfterUpdate', $module); 1981 1982 $count++; 1983 } 1984 1985 $tab = $count && $count == count($list) ? '#themes' : '#update'; 1986 1987 dcPage::addSuccessNotice( 1988 __('Theme has been successfully updated.', 'Themes have been successuflly updated.', $count) 1989 ); 1990 http::redirect($this->getURL() . $tab); 1991 } 1992 1993 # Manual actions 1994 elseif (!empty($_POST['upload_pkg']) && !empty($_FILES['pkg_file']) 1995 || !empty($_POST['fetch_pkg']) && !empty($_POST['pkg_url'])) { 1996 if (empty($_POST['your_pwd']) || !$this->core->auth->checkPassword($_POST['your_pwd'])) { 1997 throw new Exception(__('Password verification failed')); 1998 } 1999 2000 if (!empty($_POST['upload_pkg'])) { 2001 files::uploadStatus($_FILES['pkg_file']); 2002 2003 $dest = $this->getPath() . '/' . $_FILES['pkg_file']['name']; 2004 if (!move_uploaded_file($_FILES['pkg_file']['tmp_name'], $dest)) { 2005 throw new Exception(__('Unable to move uploaded file.')); 2006 } 2007 } else { 2008 $url = urldecode($_POST['pkg_url']); 2009 $dest = $this->getPath() . '/' . basename($url); 2010 $this->store->download($url, $dest); 2011 } 2012 2013 # --BEHAVIOR-- themeBeforeAdd 2014 $this->core->callBehavior('themeBeforeAdd', null); 2015 2016 $ret_code = $this->store->install($dest); 2017 2018 # --BEHAVIOR-- themeAfterAdd 2019 $this->core->callBehavior('themeAfterAdd', null); 2020 2021 dcPage::addSuccessNotice($ret_code == 2 ? 2022 __('Theme has been successfully updated.') : 2023 __('Theme has been successfully installed.') 2024 ); 2025 http::redirect($this->getURL() . '#themes'); 2026 } else { 2027 2028 # --BEHAVIOR-- adminModulesListDoActions 2029 $this->core->callBehavior('adminModulesListDoActions', $this, $modules, 'theme'); 2030 2031 } 2032 } 2033 2034 return; 2035 } 2078 2036 }
Note: See TracChangeset
for help on using the changeset viewer.