Dotclear

source: inc/admin/lib.moduleslist.php @ 2490:a7398fa0bfeb

Revision 2490:a7398fa0bfeb, 49.6 KB checked in by Denis Jean-Chirstian <contact@…>, 12 years ago (diff)

Fix user perms on plugins and themes list. (plugins that use list behaviors must check their own rights), fixes #1815

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

Sites map