Dotclear

source: inc/admin/lib.moduleslist.php @ 2631:505e3c81524e

Revision 2631:505e3c81524e, 49.7 KB checked in by Denis Jean-Christian <contact@…>, 12 years ago (diff)

array_multisort does not preserve keys and corupts numeric module id, fixes #1909

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          $origin = $sorter = array();
505
506          foreach($modules as $id => $module) {
507               $origin[] = $module;
508               $sorter[] = isset($module[$field]) ? $module[$field] : $field;
509          }
510
511          array_multisort($sorter, $asc ? SORT_ASC : SORT_DESC, $origin);
512
513          foreach($origin as $module) {
514               $final[$module['id']] = $module;
515          }
516
517          return $final;
518     }
519
520     /**
521      * Display list of modules.
522      *
523      * @param array     $cols          List of colones (module field) to display
524      * @param array     $actions  List of predefined actions to show on form
525      * @param boolean   $nav_limit     Limit list to previously selected index
526      * @return     adminModulesList self instance
527      */
528     public function displayModules($cols=array('name', 'version', 'desc'), $actions=array(), $nav_limit=false)
529     {
530          echo
531          '<form action="'.$this->getURL().'" method="post" class="modules-form-actions">'.
532          '<div class="table-outer">'.
533          '<table id="'.html::escapeHTML($this->list_id).'" class="modules'.(in_array('expander', $cols) ? ' expandable' : '').'">'.
534          '<caption class="hidden">'.html::escapeHTML(__('Plugins list')).'</caption><tr>';
535
536          if (in_array('name', $cols)) {
537               $colspan = 1;
538               if (in_array('checkbox', $cols)) {
539                    $colspan++;
540               }
541               if (in_array('icon', $cols)) {
542                    $colspan++;
543               }
544               echo
545               '<th class="first nowrap"'.($colspan > 1 ? ' colspan="'.$colspan.'"' : '').'>'.__('Name').'</th>';
546          }
547
548          if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) {
549               echo
550               '<th class="nowrap">'.__('Score').'</th>';
551          }
552
553          if (in_array('version', $cols)) {
554               echo
555               '<th class="nowrap count" scope="col">'.__('Version').'</th>';
556          }
557
558          if (in_array('current_version', $cols)) {
559               echo
560               '<th class="nowrap count" scope="col">'.__('Current version').'</th>';
561          }
562
563          if (in_array('desc', $cols)) {
564               echo
565               '<th class="nowrap" scope="col">'.__('Details').'</th>';
566          }
567
568          if (in_array('distrib', $cols)) {
569               echo
570               '<th'.(in_array('desc', $cols) ? '' : ' class="maximal"').'></th>';
571          }
572
573          if (!empty($actions) && $this->core->auth->isSuperAdmin()) {
574               echo
575               '<th class="minimal nowrap">'.__('Action').'</th>';
576          }
577
578          echo
579          '</tr>';
580
581          $sort_field = $this->getSort();
582
583          # Sort modules by $sort_field (default sname)
584          $modules = $this->getSearch() === null ?
585               self::sortModules($this->data, $sort_field, $this->sort_asc) :
586               $this->data;
587
588          $count = 0;
589          foreach ($modules as $id => $module)
590          {
591               # Show only requested modules
592               if ($nav_limit && $this->getSearch() === null) {
593                    $char = substr($module[$sort_field], 0, 1);
594                    if (!in_array($char, $this->nav_list)) {
595                         $char = $this->nav_special;
596                    }
597                    if ($this->getIndex() != $char) {
598                         continue;
599                    }
600               }
601
602               echo
603               '<tr class="line" id="'.html::escapeHTML($this->list_id).'_m_'.html::escapeHTML($id).'">';
604
605               $tds = 0;
606
607               if (in_array('checkbox', $cols)) {
608                    $tds++;
609                    echo
610                    '<td class="module-icon nowrap">'.
611                    form::checkbox(array('modules['.$count.']', html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id)), html::escapeHTML($id)).
612                    '</td>';
613               }
614
615               if (in_array('icon', $cols)) {
616                    $tds++;
617                    echo
618                    '<td class="module-icon nowrap">'.sprintf(
619                         '<img alt="%1$s" title="%1$s" src="%2$s" />',
620                         html::escapeHTML($id), file_exists($module['root'].'/icon.png') ? 'index.php?pf='.$id.'/icon.png' : 'images/module.png'
621                    ).'</td>';
622               }
623
624               $tds++;
625               echo
626               '<td class="module-name nowrap" scope="row">';
627               if (in_array('checkbox', $cols)) {
628                    if (in_array('expander', $cols)) {
629                         echo
630                         html::escapeHTML($module['name']);
631                    }
632                    else {
633                         echo
634                         '<label for="'.html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id).'">'.
635                         html::escapeHTML($module['name']).
636                         '</label>';
637                    }
638               }
639               else {
640                    echo
641                    html::escapeHTML($module['name']).
642                    form::hidden(array('modules['.$count.']'), html::escapeHTML($id));
643               }
644               echo
645               $this->core->formNonce().
646               '</td>';
647
648               # Display score only for debug purpose
649               if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) {
650                    $tds++;
651                    echo
652                    '<td class="module-version nowrap count"><span class="debug">'.$module['score'].'</span></td>';
653               }
654
655               if (in_array('version', $cols)) {
656                    $tds++;
657                    echo
658                    '<td class="module-version nowrap count">'.html::escapeHTML($module['version']).'</td>';
659               }
660
661               if (in_array('current_version', $cols)) {
662                    $tds++;
663                    echo
664                    '<td class="module-current-version nowrap count">'.html::escapeHTML($module['current_version']).'</td>';
665               }
666
667               if (in_array('desc', $cols)) {
668                    $tds++;
669                    echo
670                    '<td class="module-desc maximal">'.html::escapeHTML(__($module['desc'])).'</td>';
671               }
672
673               if (in_array('distrib', $cols)) {
674                    $tds++;
675                    echo
676                    '<td class="module-distrib">'.(self::isDistributedModule($id) ?
677                         '<img src="images/dotclear_pw.png" alt="'.
678                         __('Plugin from official distribution').'" title="'.
679                         __('Plugin from official distribution').'" />'
680                    : '').'</td>';
681               }
682
683               if (!empty($actions) && $this->core->auth->isSuperAdmin()) {
684                    $buttons = $this->getActions($id, $module, $actions);
685
686                    $tds++;
687                    echo
688                    '<td class="module-actions nowrap">'.
689
690                    '<div>'.implode(' ', $buttons).'</div>'.
691
692                    '</td>';
693               }
694
695               echo
696               '</tr>';
697
698               # Other informations
699               if (in_array('expander', $cols)) {
700                    echo
701                    '<tr class="module-more"><td colspan="'.$tds.'" class="expand">';
702
703                    if (!empty($module['author']) || !empty($module['details']) || !empty($module['support'])) {
704                         echo
705                         '<div><ul class="mod-more">';
706
707                         if (!empty($module['author'])) {
708                              echo
709                              '<li class="module-author">'.__('Author:').' '.html::escapeHTML($module['author']).'</li>';
710                         }
711
712                         $more = array();
713                         if (!empty($module['details'])) {
714                              $more[] = '<a class="module-details" href="'.$module['details'].'">'.__('Details').'</a>';
715                         }
716
717                         if (!empty($module['support'])) {
718                              $more[] = '<a class="module-support" href="'.$module['support'].'">'.__('Support').'</a>';
719                         }
720
721                         if (!empty($more)) {
722                              echo
723                              '<li>'.implode(' - ', $more).'</li>';
724                         }
725
726                         echo
727                         '</ul></div>';
728                    }
729
730                    $config = !empty($module['root']) && file_exists(path::real($module['root'].'/_config.php'));
731
732                    if ($config || !empty($module['section']) || !empty($module['section'])) {
733                         echo
734                         '<div><ul class="mod-more">';
735
736                         if ($config) {
737                              echo
738                              '<li><a class="module-config" href="'.$this->getURL('module='.$id.'&amp;conf=1').'">'.__('Configure plugin').'</a></li>';
739                         }
740
741                         if (!empty($module['section'])) {
742                              echo
743                              '<li class="module-section">'.__('Section:').' '.html::escapeHTML($module['section']).'</li>';
744                         }
745
746                         if (!empty($module['section'])) {
747                              echo
748                              '<li class="module-tags">'.__('Tags:').' '.html::escapeHTML($module['tags']).'</li>';
749                         }
750
751                         echo
752                         '</ul></div>';
753                    }
754
755                    echo
756                    '</td></tr>';
757               }
758
759               $count++;
760          }
761          echo
762          '</table></div>';
763
764          if(!$count && $this->getSearch() === null) {
765               echo
766               '<p class="message">'.__('No plugins matched your search.').'</p>';
767          }
768
769          elseif ((in_array('checkbox', $cols) || $count > 1) && !empty($actions) && $this->core->auth->isSuperAdmin()) {
770               $buttons = $this->getGlobalActions($actions, in_array('checkbox', $cols));
771
772               if (!empty($buttons)) {
773                    if (in_array('checkbox', $cols)) {
774                         echo
775                         '<p class="checkboxes-helpers"></p>';
776                    }
777                    echo
778                    '<div>'.implode(' ', $buttons).'</div>';
779               }
780          }
781          echo
782          '</form>';
783
784          return $this;
785     }
786
787     /**
788      * Get action buttons to add to modules list.
789      *
790      * @param string    $id            Module ID
791      * @param array     $module        Module info
792      * @param array     $actions  Actions keys
793      * @return     Array of actions buttons
794      */
795     protected function getActions($id, $module, $actions)
796     {
797          $submits = array();
798
799          # Use loop to keep requested order
800          foreach($actions as $action) {
801               switch($action) {
802
803                    # Deactivate
804                    case 'activate': if ($this->core->auth->isSuperAdmin() && $module['root_writable']) {
805                         $submits[] =
806                         '<input type="submit" name="activate['.html::escapeHTML($id).']" value="'.__('Activate').'" />';
807                    } break;
808
809                    # Activate
810                    case 'deactivate': if ($this->core->auth->isSuperAdmin() && $module['root_writable']) {
811                         $submits[] =
812                         '<input type="submit" name="deactivate['.html::escapeHTML($id).']" value="'.__('Deactivate').'" class="reset" />';
813                    } break;
814
815                    # Delete
816                    case 'delete': if ($this->core->auth->isSuperAdmin() && $this->isDeletablePath($module['root'])) {
817                         $dev = !preg_match('!^'.$this->path_pattern.'!', $module['root']) && defined('DC_DEV') && DC_DEV ? ' debug' : '';
818                         $submits[] =
819                         '<input type="submit" class="delete '.$dev.'" name="delete['.html::escapeHTML($id).']" value="'.__('Delete').'" />';
820                    } break;
821
822                    # Install (from store)
823                    case 'install': if ($this->core->auth->isSuperAdmin() && $this->path_writable) {
824                         $submits[] =
825                         '<input type="submit" name="install['.html::escapeHTML($id).']" value="'.__('Install').'" />';
826                    } break;
827
828                    # Update (from store)
829                    case 'update': if ($this->core->auth->isSuperAdmin() && $this->path_writable) {
830                         $submits[] =
831                         '<input type="submit" name="update['.html::escapeHTML($id).']" value="'.__('Update').'" />';
832                    } break;
833
834                    # Behavior
835                    case 'behavior':
836
837                         # --BEHAVIOR-- adminModulesListGetActions
838                         $tmp = $this->core->callBehavior('adminModulesListGetActions', $this, $id, $module);
839
840                         if (!empty($tmp)) {
841                              $submits[] = $tmp;
842                         }
843                    break;
844               }
845          }
846
847          return $submits;
848     }
849
850     /**
851      * Get global action buttons to add to modules list.
852      *
853      * @param array     $actions        Actions keys
854      * @param boolean   $with_selection Limit action to selected modules
855      * @return     Array of actions buttons
856      */
857     protected function getGlobalActions($actions, $with_selection=false)
858     {
859          $submits = array();
860
861          # Use loop to keep requested order
862          foreach($actions as $action) {
863               switch($action) {
864
865                    # Deactivate
866                    case 'activate': if ($this->core->auth->isSuperAdmin() && $this->path_writable) {
867                         $submits[] =
868                         '<input type="submit" name="activate" value="'.($with_selection ?
869                              __('Activate selected plugins') :
870                              __('Activate all plugins from this list')
871                         ).'" />';
872                    } break;
873
874                    # Activate
875                    case 'deactivate': if ($this->core->auth->isSuperAdmin() && $this->path_writable) {
876                         $submits[] =
877                         '<input type="submit" name="deactivate" value="'.($with_selection ?
878                              __('Deactivate selected plugins') :
879                              __('Deactivate all plugins from this list')
880                         ).'" />';
881                    } break;
882
883                    # Update (from store)
884                    case 'update': if ($this->core->auth->isSuperAdmin() && $this->path_writable) {
885                         $submits[] =
886                         '<input type="submit" name="update" value="'.($with_selection ?
887                              __('Update selected plugins') :
888                              __('Update all plugins from this list')
889                         ).'" />';
890                    } break;
891
892                    # Behavior
893                    case 'behavior':
894
895                         # --BEHAVIOR-- adminModulesListGetGlobalActions
896                         $tmp = $this->core->callBehavior('adminModulesListGetGlobalActions', $this, $with_selection);
897
898                         if (!empty($tmp)) {
899                              $submits[] = $tmp;
900                         }
901                    break;
902               }
903          }
904
905          return $submits;
906     }
907
908     /**
909      * Execute POST action.
910      *
911      * @note  Set a notice on success through dcPage::addSuccessNotice
912      * @throw Exception Module not find or command failed
913      * @return     Null
914      */
915     public function doActions()
916     {
917          if (empty($_POST) || !empty($_REQUEST['conf'])
918          || !$this->isWritablePath()) {
919               return null;
920          }
921
922          $modules = !empty($_POST['modules']) && is_array($_POST['modules']) ? array_values($_POST['modules']) : array();
923
924          if ($this->core->auth->isSuperAdmin() && !empty($_POST['delete'])) {
925
926               if (is_array($_POST['delete'])) {
927                    $modules = array_keys($_POST['delete']);
928               }
929
930               $list = $this->modules->getDisabledModules();
931
932               $failed = false;
933               $count = 0;
934               foreach($modules as $id)
935               {
936                    if (!isset($list[$id])) {
937
938                         if (!$this->modules->moduleExists($id)) {
939                              throw new Exception(__('No such plugin.'));
940                         }
941
942                         $module = $this->modules->getModules($id);
943                         $module['id'] = $id;
944
945                         if (!$this->isDeletablePath($module['root'])) {
946                              $failed = true;
947                              continue;
948                         }
949
950                         # --BEHAVIOR-- moduleBeforeDelete
951                         $this->core->callBehavior('pluginBeforeDelete', $module);
952
953                         $this->modules->deleteModule($id);
954
955                         # --BEHAVIOR-- moduleAfterDelete
956                         $this->core->callBehavior('pluginAfterDelete', $module);
957                    }
958                    else {
959                         $this->modules->deleteModule($id, true);
960                    }
961
962                    $count++;
963               }
964
965               if (!$count && $failed) {
966                    throw new Exception(__("You don't have permissions to delete this plugin."));
967               }
968               elseif ($failed) {
969                    dcPage::addWarningNotice(__('Some plugins have not been delete.'));
970               }
971               else {
972                    dcPage::addSuccessNotice(
973                         __('Plugin has been successfully deleted.', 'Plugins have been successuflly deleted.', $count)
974                    );
975               }
976               http::redirect($this->getURL());
977          }
978
979          elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['install'])) {
980
981               if (is_array($_POST['install'])) {
982                    $modules = array_keys($_POST['install']);
983               }
984
985               $list = $this->store->get();
986
987               if (empty($list)) {
988                    throw new Exception(__('No such plugin.'));
989               }
990
991               $count = 0;
992               foreach($list as $id => $module) {
993
994                    if (!in_array($id, $modules)) {
995                         continue;
996                    }
997
998                    $dest = $this->getPath().'/'.basename($module['file']);
999
1000                    # --BEHAVIOR-- moduleBeforeAdd
1001                    $this->core->callBehavior('pluginBeforeAdd', $module);
1002
1003                    $this->store->process($module['file'], $dest);
1004
1005                    # --BEHAVIOR-- moduleAfterAdd
1006                    $this->core->callBehavior('pluginAfterAdd', $module);
1007
1008                    $count++;
1009               }
1010
1011               dcPage::addSuccessNotice(
1012                    __('Plugin has been successfully installed.', 'Plugins have been successuflly installed.', $count)
1013               );
1014               http::redirect($this->getURL());
1015          }
1016
1017          elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['activate'])) {
1018
1019               if (is_array($_POST['activate'])) {
1020                    $modules = array_keys($_POST['activate']);
1021               }
1022
1023               $list = $this->modules->getDisabledModules();
1024               if (empty($list)) {
1025                    throw new Exception(__('No such plugin.'));
1026               }
1027
1028               $count = 0;
1029               foreach($list as $id => $module) {
1030
1031                    if (!in_array($id, $modules)) {
1032                         continue;
1033                    }
1034
1035                    # --BEHAVIOR-- moduleBeforeActivate
1036                    $this->core->callBehavior('pluginBeforeActivate', $id);
1037
1038                    $this->modules->activateModule($id);
1039
1040                    # --BEHAVIOR-- moduleAfterActivate
1041                    $this->core->callBehavior('pluginAfterActivate', $id);
1042
1043                    $count++;
1044               }
1045
1046               dcPage::addSuccessNotice(
1047                    __('Plugin has been successfully activated.', 'Plugins have been successuflly activated.', $count)
1048               );
1049               http::redirect($this->getURL());
1050          }
1051
1052          elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['deactivate'])) {
1053
1054               if (is_array($_POST['deactivate'])) {
1055                    $modules = array_keys($_POST['deactivate']);
1056               }
1057
1058               $list = $this->modules->getModules();
1059               if (empty($list)) {
1060                    throw new Exception(__('No such plugin.'));
1061               }
1062
1063               $failed = false;
1064               $count = 0;
1065               foreach($list as $id => $module) {
1066
1067                    if (!in_array($id, $modules)) {
1068                         continue;
1069                    }
1070
1071                    if (!$module['root_writable']) {
1072                         $failed = true;
1073                         continue;
1074                    }
1075
1076                    $module[$id] = $id;
1077
1078                    # --BEHAVIOR-- moduleBeforeDeactivate
1079                    $this->core->callBehavior('pluginBeforeDeactivate', $module);
1080
1081                    $this->modules->deactivateModule($id);
1082
1083                    # --BEHAVIOR-- moduleAfterDeactivate
1084                    $this->core->callBehavior('pluginAfterDeactivate', $module);
1085
1086                    $count++;
1087               }
1088
1089               if ($failed) {
1090                    dcPage::addWarningNotice(__('Some plugins have not been deactivated.'));
1091               }
1092               else {
1093                    dcPage::addSuccessNotice(
1094                         __('Plugin has been successfully deactivated.', 'Plugins have been successuflly deactivated.', $count)
1095                    );
1096               }
1097               http::redirect($this->getURL());
1098          }
1099
1100          elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['update'])) {
1101
1102               if (is_array($_POST['update'])) {
1103                    $modules = array_keys($_POST['update']);
1104               }
1105
1106               $list = $this->store->get(true);
1107               if (empty($list)) {
1108                    throw new Exception(__('No such plugin.'));
1109               }
1110
1111               $count = 0;
1112               foreach($list as $module) {
1113
1114                    if (!in_array($module['id'], $modules)) {
1115                         continue;
1116                    }
1117
1118                    if (!self::$allow_multi_install) {
1119                         $dest = $module['root'].'/../'.basename($module['file']);
1120                    }
1121                    else {
1122                         $dest = $this->getPath().'/'.basename($module['file']);
1123                         if ($module['root'] != $dest) {
1124                              @file_put_contents($module['root'].'/_disabled', '');
1125                         }
1126                    }
1127
1128                    # --BEHAVIOR-- moduleBeforeUpdate
1129                    $this->core->callBehavior('pluginBeforeUpdate', $module);
1130
1131                    $this->store->process($module['file'], $dest);
1132
1133                    # --BEHAVIOR-- moduleAfterUpdate
1134                    $this->core->callBehavior('pluginAfterUpdate', $module);
1135
1136                    $count++;
1137               }
1138
1139               $tab = $count && $count == count($list) ? '#plugins' : '#update';
1140
1141               dcPage::addSuccessNotice(
1142                    __('Plugin has been successfully updated.', 'Plugins have been successuflly updated.', $count)
1143               );
1144               http::redirect($this->getURL().$tab);
1145          }
1146
1147          # Manual actions
1148          elseif (!empty($_POST['upload_pkg']) && !empty($_FILES['pkg_file'])
1149               || !empty($_POST['fetch_pkg']) && !empty($_POST['pkg_url']))
1150          {
1151               if (empty($_POST['your_pwd']) || !$this->core->auth->checkPassword(crypt::hmac(DC_MASTER_KEY, $_POST['your_pwd']))) {
1152                    throw new Exception(__('Password verification failed'));
1153               }
1154
1155               if (!empty($_POST['upload_pkg'])) {
1156                    files::uploadStatus($_FILES['pkg_file']);
1157
1158                    $dest = $this->getPath().'/'.$_FILES['pkg_file']['name'];
1159                    if (!move_uploaded_file($_FILES['pkg_file']['tmp_name'], $dest)) {
1160                         throw new Exception(__('Unable to move uploaded file.'));
1161                    }
1162               }
1163               else {
1164                    $url = urldecode($_POST['pkg_url']);
1165                    $dest = $this->getPath().'/'.basename($url);
1166                    $this->store->download($url, $dest);
1167               }
1168
1169               # --BEHAVIOR-- moduleBeforeAdd
1170               $this->core->callBehavior('pluginBeforeAdd', null);
1171
1172               $ret_code = $this->store->install($dest);
1173
1174               # --BEHAVIOR-- moduleAfterAdd
1175               $this->core->callBehavior('pluginAfterAdd', null);
1176
1177               dcPage::addSuccessNotice($ret_code == 2 ?
1178                    __('Plugin has been successfully updated.') :
1179                    __('Plugin has been successfully installed.')
1180               );
1181               http::redirect($this->getURL().'#plugins');
1182          }
1183
1184          else {
1185
1186               # --BEHAVIOR-- adminModulesListDoActions
1187               $this->core->callBehavior('adminModulesListDoActions', $this, $modules, 'plugin');
1188
1189          }
1190
1191          return null;
1192     }
1193
1194     /**
1195      * Display tab for manual installation.
1196      *
1197      * @return     adminModulesList self instance
1198      */
1199     public function displayManualForm()
1200     {
1201          if (!$this->core->auth->isSuperAdmin() || !$this->isWritablePath()) {
1202               return null;
1203          }
1204
1205          # 'Upload module' form
1206          echo
1207          '<form method="post" action="'.$this->getURL().'" id="uploadpkg" enctype="multipart/form-data" class="fieldset">'.
1208          '<h4>'.__('Upload a zip file').'</h4>'.
1209          '<p class="field"><label for="pkg_file" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Zip file path:').'</label> '.
1210          '<input type="file" name="pkg_file" id="pkg_file" /></p>'.
1211          '<p class="field"><label for="your_pwd1" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Your password:').'</label> '.
1212          form::password(array('your_pwd','your_pwd1'),20,255).'</p>'.
1213          '<p><input type="submit" name="upload_pkg" value="'.__('Upload').'" />'.
1214          $this->core->formNonce().'</p>'.
1215          '</form>';
1216
1217          # 'Fetch module' form
1218          echo
1219          '<form method="post" action="'.$this->getURL().'" id="fetchpkg" class="fieldset">'.
1220          '<h4>'.__('Download a zip file').'</h4>'.
1221          '<p class="field"><label for="pkg_url" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Zip file URL:').'</label> '.
1222          form::field(array('pkg_url','pkg_url'),40,255).'</p>'.
1223          '<p class="field"><label for="your_pwd2" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Your password:').'</label> '.
1224          form::password(array('your_pwd','your_pwd2'),20,255).'</p>'.
1225          '<p><input type="submit" name="fetch_pkg" value="'.__('Download').'" />'.
1226          $this->core->formNonce().'</p>'.
1227          '</form>';
1228
1229          return $this;
1230     }
1231     //@}
1232
1233     /// @name Module configuration methods
1234     //@{
1235     /**
1236      * Prepare module configuration.
1237      *
1238      * We need to get configuration content in three steps
1239      * and out of this class to keep backward compatibility.
1240      *
1241      * if ($xxx->setConfiguration()) {
1242      *   include $xxx->includeConfiguration();
1243      * }
1244      * $xxx->getConfiguration();
1245      * ... [put here page headers and other stuff]
1246      * $xxx->displayConfiguration();
1247      *
1248      * @param string    $id       Module to work on or it gather through REQUEST
1249      * @return     True if config set
1250      */
1251     public function setConfiguration($id=null)
1252     {
1253          if (empty($_REQUEST['conf']) || empty($_REQUEST['module']) && !$id) {
1254               return false;
1255          }
1256
1257          if (!empty($_REQUEST['module']) && empty($id)) {
1258               $id = $_REQUEST['module'];
1259          }
1260
1261          if (!$this->modules->moduleExists($id)) {
1262               $this->core->error->add(__('Unknow plugin ID'));
1263               return false;
1264          }
1265
1266          $module = $this->modules->getModules($id);
1267          $module = self::sanitizeModule($id, $module);
1268          $file = path::real($module['root'].'/_config.php');
1269
1270          if (!file_exists($file)) {
1271               $this->core->error->add(__('This plugin has no configuration file.'));
1272               return false;
1273          }
1274
1275          $this->config_module = $module;
1276          $this->config_file = $file;
1277          $this->config_content = '';
1278
1279          if (!defined('DC_CONTEXT_MODULE')) {
1280               define('DC_CONTEXT_MODULE', true);
1281          }
1282
1283          return true;
1284     }
1285
1286     /**
1287      * Get path of module configuration file.
1288      *
1289      * @note Required previously set file info
1290      * @return Full path of config file or null
1291      */
1292     public function includeConfiguration()
1293     {
1294          if (!$this->config_file) {
1295               return null;
1296          }
1297          $this->setRedir($this->getURL().'#plugins');
1298
1299          ob_start();
1300
1301          return $this->config_file;
1302     }
1303
1304     /**
1305      * Gather module configuration file content.
1306      *
1307      * @note Required previously file inclusion
1308      * @return True if content has been captured
1309      */
1310     public function getConfiguration()
1311     {
1312          if ($this->config_file) {
1313               $this->config_content = ob_get_contents();
1314          }
1315
1316          ob_end_clean();
1317
1318          return !empty($this->file_content);
1319     }
1320
1321     /**
1322      * Display module configuration form.
1323      *
1324      * @note Required previously gathered content
1325      * @return     adminModulesList self instance
1326      */
1327     public function displayConfiguration()
1328     {
1329          if ($this->config_file) {
1330
1331               if (!$this->config_module['standalone_config']) {
1332                    echo
1333                    '<form id="module_config" action="'.$this->getURL('conf=1').'" method="post" enctype="multipart/form-data">'.
1334                    '<h3>'.sprintf(__('Configure "%s"'), html::escapeHTML($this->config_module['name'])).'</h3>'.
1335                    '<p><a class="back" href="'.$this->getRedir().'">'.__('Back').'</a></p>';
1336               }
1337
1338               echo $this->config_content;
1339
1340               if (!$this->config_module['standalone_config']) {
1341                    echo
1342                    '<p class="clear"><input type="submit" name="save" value="'.__('Save').'" />'.
1343                    form::hidden('module', $this->config_module['id']).
1344                    form::hidden('redir', $this->getRedir()).
1345                    $this->core->formNonce().'</p>'.
1346                    '</form>';
1347               }
1348          }
1349
1350          return $this;
1351     }
1352     //@}
1353
1354     /**
1355      * Helper to sanitize a string.
1356      *
1357      * Used for search or id.
1358      *
1359      * @param string    $str      String to sanitize
1360      * @return     Sanitized string
1361      */
1362     public static function sanitizeString($str)
1363     {
1364          return preg_replace('/[^A-Za-z0-9\@\#+_-]/', '', strtolower($str));
1365     }
1366}
1367
1368/**
1369 * @ingroup DC_CORE
1370 * @brief Helper to manage list of themes.
1371 * @since 2.6
1372 */
1373class adminThemesList extends adminModulesList
1374{
1375     protected $page_url = 'blog_theme.php';
1376
1377     public function displayModules($cols=array('name', 'config', 'version', 'desc'), $actions=array(), $nav_limit=false)
1378     {
1379          echo
1380          '<form action="'.$this->getURL().'" method="post" class="modules-form-actions">'.
1381          '<div id="'.html::escapeHTML($this->list_id).'" class="modules'.(in_array('expander', $cols) ? ' expandable' : '').' one-box">';
1382
1383          $sort_field = $this->getSort();
1384
1385          # Sort modules by id
1386          $modules = $this->getSearch() === null ?
1387               self::sortModules($this->data, $sort_field, $this->sort_asc) :
1388               $this->data;
1389
1390          $res = '';
1391          $count = 0;
1392          foreach ($modules as $id => $module)
1393          {
1394               # Show only requested modules
1395               if ($nav_limit && $this->getSearch() === null) {
1396                    $char = substr($module[$sort_field], 0, 1);
1397                    if (!in_array($char, $this->nav_list)) {
1398                         $char = $this->nav_special;
1399                    }
1400                    if ($this->getIndex() != $char) {
1401                         continue;
1402                    }
1403               }
1404
1405               $current = $this->core->blog->settings->system->theme == $id && $this->modules->moduleExists($id);
1406               $distrib = self::isDistributedModule($id) ? ' dc-box' : '';
1407
1408               $line =
1409               '<div class="box '.($current ? 'medium current-theme' : 'theme').$distrib.'">';
1410
1411               if (in_array('name', $cols) && !$current) {
1412                    $line .=
1413                    '<h4 class="module-name">';
1414
1415                    if (in_array('checkbox', $cols)) {
1416                         $line .=
1417                         '<label for="'.html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id).'">'.
1418                         form::checkbox(array('modules['.$count.']', html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id)), html::escapeHTML($id)).
1419                         html::escapeHTML($module['name']).
1420                         '</label>';
1421
1422                    }
1423                    else {
1424                         $line .=
1425                         form::hidden(array('modules['.$count.']'), html::escapeHTML($id)).
1426                         html::escapeHTML($module['name']);
1427                    }
1428
1429                    $line .=
1430                    $this->core->formNonce().
1431                    '</h4>';
1432               }
1433
1434               # Display score only for debug purpose
1435               if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) {
1436                    $line .=
1437                    '<p class="module-score debug">'.sprintf(__('Score: %s'), $module['score']).'</p>';
1438               }
1439
1440               if (in_array('sshot', $cols)) {
1441                    # Screenshot from url
1442                    if (preg_match('#^http(s)?://#', $module['sshot'])) {
1443                         $sshot = $module['sshot'];
1444                    }
1445                    # Screenshot from installed module
1446                    elseif (file_exists($this->core->blog->themes_path.'/'.$id.'/screenshot.jpg')) {
1447                         $sshot = $this->getURL('shot='.rawurlencode($id));
1448                    }
1449                    # Default screenshot
1450                    else {
1451                         $sshot = 'images/noscreenshot.png';
1452                    }
1453
1454                    $line .=
1455                    '<div class="module-sshot"><img src="'.$sshot.'" alt="'.
1456                    sprintf(__('%s screenshot.'), html::escapeHTML($module['name'])).'" /></div>';
1457               }
1458
1459               $line .=
1460               '<div class="module-infos toggle-bloc">';
1461
1462               if (in_array('name', $cols) && $current) {
1463                    $line .=
1464                    '<h4 class="module-name">';
1465
1466                    if (in_array('checkbox', $cols)) {
1467                         $line .=
1468                         '<label for="'.html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id).'">'.
1469                         form::checkbox(array('modules['.$count.']', html::escapeHTML($this->list_id).'_modules_'.html::escapeHTML($id)), html::escapeHTML($id)).
1470                         html::escapeHTML($module['name']).
1471                         '</label>';
1472                    }
1473                    else {
1474                         $line .=
1475                         form::hidden(array('modules['.$count.']'), html::escapeHTML($id)).
1476                         html::escapeHTML($module['name']);
1477                    }
1478
1479                    $line .=
1480                    '</h4>';
1481               }
1482
1483               $line .=
1484               '<p>';
1485
1486               if (in_array('desc', $cols)) {
1487                    $line .=
1488                    '<span class="module-desc">'.html::escapeHTML(__($module['desc'])).'</span> ';
1489               }
1490
1491               if (in_array('author', $cols)) {
1492                    $line .=
1493                    '<span class="module-author">'.sprintf(__('by %s'),html::escapeHTML($module['author'])).'</span> ';
1494               }
1495
1496               if (in_array('version', $cols)) {
1497                    $line .=
1498                    '<span class="module-version">'.sprintf(__('version %s'),html::escapeHTML($module['version'])).'</span> ';
1499               }
1500
1501               if (in_array('current_version', $cols)) {
1502                    $line .=
1503                    '<span class="module-current-version">'.sprintf(__('(current version %s)'),html::escapeHTML($module['current_version'])).'</span> ';
1504               }
1505
1506               if (in_array('parent', $cols) && !empty($module['parent'])) {
1507                    if ($this->modules->moduleExists($module['parent'])) {
1508                         $line .=
1509                         '<span class="module-parent-ok">'.sprintf(__('(built on "%s")'),html::escapeHTML($module['parent'])).'</span> ';
1510                    }
1511                    else {
1512                         $line .=
1513                         '<span class="module-parent-missing">'.sprintf(__('(requires "%s")'),html::escapeHTML($module['parent'])).'</span> ';
1514                    }
1515               }
1516
1517               $has_details = in_array('details', $cols) && !empty($module['details']);
1518               $has_support = in_array('support', $cols) && !empty($module['support']);
1519               if ($has_details || $has_support) {
1520                    $line .=
1521                    '<span class="mod-more">';
1522
1523                    if ($has_details) {
1524                         $line .=
1525                         '<a class="module-details" href="'.$module['details'].'">'.__('Details').'</a>';
1526                    }
1527
1528                    if ($has_support) {
1529                         $line .=
1530                         ' - <a class="module-support" href="'.$module['support'].'">'.__('Support').'</a>';
1531                    }
1532
1533                    $line .=
1534                    '</span>';
1535               }
1536
1537               $line .=
1538               '</p>'.
1539               '</div>';
1540
1541               $line .=
1542               '<div class="module-actions toggle-bloc">';
1543
1544               # Plugins actions
1545               if ($current) {
1546
1547                    # _GET actions
1548                    if (file_exists(path::real($this->core->blog->themes_path.'/'.$id).'/style.css')) {
1549                         $theme_url = preg_match('#^http(s)?://#', $this->core->blog->settings->system->themes_url) ?
1550                              http::concatURL($this->core->blog->settings->system->themes_url, '/'.$id) :
1551                              http::concatURL($this->core->blog->url, $this->core->blog->settings->system->themes_url.'/'.$id);
1552                         $line .=
1553                         '<p><a href="'.$theme_url.'/style.css">'.__('View stylesheet').'</a></p>';
1554                    }
1555
1556                    $line .= '<div class="current-actions">';
1557
1558                    if (file_exists(path::real($this->core->blog->themes_path.'/'.$id).'/_config.php')) {
1559                         $line .=
1560                         '<p><a href="'.$this->getURL('module='.$id.'&amp;conf=1', false).'" class="button submit">'.__('Configure theme').'</a></p>';
1561                    }
1562
1563                    # --BEHAVIOR-- adminCurrentThemeDetails
1564                    $line .=
1565                    $this->core->callBehavior('adminCurrentThemeDetails', $this->core, $id, $module);
1566
1567                    $line .= '</div>';
1568               }
1569
1570               # _POST actions
1571               if (!empty($actions)) {
1572                    $line .=
1573                    '<p>'.implode(' ', $this->getActions($id, $module, $actions)).'</p>';
1574               }
1575
1576               $line .=
1577               '</div>';
1578
1579               $line .=
1580               '</div>';
1581
1582               $count++;
1583
1584               $res = $current ? $line.$res : $res.$line;
1585          }
1586
1587          echo
1588          $res.
1589          '</div>';
1590
1591          if(!$count && $this->getSearch() === null) {
1592               echo
1593               '<p class="message">'.__('No themes matched your search.').'</p>';
1594          }
1595
1596          elseif ((in_array('checkbox', $cols) || $count > 1) && !empty($actions) && $this->core->auth->isSuperAdmin()) {
1597               $buttons = $this->getGlobalActions($actions, in_array('checkbox', $cols));
1598
1599               if (!empty($buttons)) {
1600                    if (in_array('checkbox', $cols)) {
1601                         echo
1602                         '<p class="checkboxes-helpers"></p>';
1603                    }
1604                    echo '<div>'.implode(' ', $buttons).'</div>';
1605               }
1606          }
1607
1608          echo
1609          '</form>';
1610
1611          return $this;
1612     }
1613
1614     protected function getActions($id, $module, $actions)
1615     {
1616          $submits = array();
1617
1618          $this->core->blog->settings->addNamespace('system');
1619          if ($id != $this->core->blog->settings->system->theme) {
1620
1621               # Select theme to use on curent blog
1622               if (in_array('select', $actions) && $this->path_writable) {
1623                    $submits[] =
1624                    '<input type="submit" name="select['.html::escapeHTML($id).']" value="'.__('Use this one').'" />';
1625               }
1626          }
1627
1628          return array_merge(
1629               $submits,
1630               parent::getActions($id, $module, $actions)
1631          );
1632     }
1633
1634     protected function getGlobalActions($actions, $with_selection=false)
1635     {
1636          $submits = array();
1637
1638          foreach($actions as $action) {
1639               switch($action) {
1640
1641                    # Update (from store)
1642                    case 'update': if ($this->core->auth->isSuperAdmin() && $this->path_writable) {
1643                         $submits[] =
1644                         '<input type="submit" name="update" value="'.($with_selection ?
1645                              __('Update selected themes') :
1646                              __('Update all themes from this list')
1647                         ).'" />';
1648                    } break;
1649
1650                    # Behavior
1651                    case 'behavior':
1652
1653                         # --BEHAVIOR-- adminModulesListGetGlobalActions
1654                         $tmp = $this->core->callBehavior('adminModulesListGetGlobalActions', $this);
1655
1656                         if (!empty($tmp)) {
1657                              $submits[] = $tmp;
1658                         }
1659                    break;
1660               }
1661          }
1662
1663          return $submits;
1664     }
1665
1666     public function doActions()
1667     {
1668          if (empty($_POST) || !empty($_REQUEST['conf']) || !$this->isWritablePath()) {
1669               return null;
1670          }
1671
1672          $modules = !empty($_POST['modules']) && is_array($_POST['modules']) ? array_values($_POST['modules']) : array();
1673
1674          if (!empty($_POST['select'])) {
1675
1676               # Can select only one theme at a time!
1677               if (is_array($_POST['select'])) {
1678                    $modules = array_keys($_POST['select']);
1679                    $id = $modules[0];
1680
1681                    if (!$this->modules->moduleExists($id)) {
1682                         throw new Exception(__('No such theme.'));
1683                    }
1684
1685                    $this->core->blog->settings->addNamespace('system');
1686                    $this->core->blog->settings->system->put('theme',$id);
1687                    $this->core->blog->triggerBlog();
1688
1689                    dcPage::addSuccessNotice(__('Theme has been successfully selected.'));
1690                    http::redirect($this->getURL().'#themes');
1691               }
1692          }
1693
1694          elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['activate'])) {
1695
1696               if (is_array($_POST['activate'])) {
1697                    $modules = array_keys($_POST['activate']);
1698               }
1699
1700               $list = $this->modules->getDisabledModules();
1701               if (empty($list)) {
1702                    throw new Exception(__('No such theme.'));
1703               }
1704
1705               $count = 0;
1706               foreach($list as $id => $module) {
1707
1708                    if (!in_array($id, $modules)) {
1709                         continue;
1710                    }
1711
1712                    # --BEHAVIOR-- themeBeforeActivate
1713                    $this->core->callBehavior('themeBeforeActivate', $id);
1714
1715                    $this->modules->activateModule($id);
1716
1717                    # --BEHAVIOR-- themeAfterActivate
1718                    $this->core->callBehavior('themeAfterActivate', $id);
1719
1720                    $count++;
1721               }
1722
1723               dcPage::addSuccessNotice(
1724                    __('Theme has been successfully activated.', 'Themes have been successuflly activated.', $count)
1725               );
1726               http::redirect($this->getURL());
1727          }
1728
1729          elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['deactivate'])) {
1730
1731               if (is_array($_POST['deactivate'])) {
1732                    $modules = array_keys($_POST['deactivate']);
1733               }
1734
1735               $list = $this->modules->getModules();
1736               if (empty($list)) {
1737                    throw new Exception(__('No such theme.'));
1738               }
1739
1740               $failed = false;
1741               $count = 0;
1742               foreach($list as $id => $module) {
1743
1744                    if (!in_array($id, $modules)) {
1745                         continue;
1746                    }
1747
1748                    if (!$module['root_writable']) {
1749                         $failed = true;
1750                         continue;
1751                    }
1752
1753                    $module[$id] = $id;
1754
1755                    # --BEHAVIOR-- themeBeforeDeactivate
1756                    $this->core->callBehavior('themeBeforeDeactivate', $module);
1757
1758                    $this->modules->deactivateModule($id);
1759
1760                    # --BEHAVIOR-- themeAfterDeactivate
1761                    $this->core->callBehavior('themeAfterDeactivate', $module);
1762
1763                    $count++;
1764               }
1765
1766               if ($failed) {
1767                    dcPage::addWarningNotice(__('Some themes have not been deactivated.'));
1768               }
1769               else {
1770                    dcPage::addSuccessNotice(
1771                         __('Theme has been successfully deactivated.', 'Themes have been successuflly deactivated.', $count)
1772                    );
1773               }
1774               http::redirect($this->getURL());
1775          }
1776
1777          elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['delete'])) {
1778
1779               if (is_array($_POST['delete'])) {
1780                    $modules = array_keys($_POST['delete']);
1781               }
1782
1783               $list = $this->modules->getDisabledModules();
1784
1785               $failed = false;
1786               $count = 0;
1787               foreach($modules as $id)
1788               {
1789                    if (!isset($list[$id])) {
1790
1791                         if (!$this->modules->moduleExists($id)) {
1792                              throw new Exception(__('No such theme.'));
1793                         }
1794
1795                         $module = $this->modules->getModules($id);
1796                         $module['id'] = $id;
1797
1798                         if (!$this->isDeletablePath($module['root'])) {
1799                              $failed = true;
1800                              continue;
1801                         }
1802
1803                         # --BEHAVIOR-- themeBeforeDelete
1804                         $this->core->callBehavior('themeBeforeDelete', $module);
1805
1806                         $this->modules->deleteModule($id);
1807
1808                         # --BEHAVIOR-- themeAfterDelete
1809                         $this->core->callBehavior('themeAfterDelete', $module);
1810                    }
1811                    else {
1812                         $this->modules->deleteModule($id, true);
1813                    }
1814
1815                    $count++;
1816               }
1817
1818               if (!$count && $failed) {
1819                    throw new Exception(__("You don't have permissions to delete this theme."));
1820               }
1821               elseif ($failed) {
1822                    dcPage::addWarningNotice(__('Some themes have not been delete.'));
1823               }
1824               else {
1825                    dcPage::addSuccessNotice(
1826                         __('Theme has been successfully deleted.', 'Themes have been successuflly deleted.', $count)
1827                    );
1828               }
1829               http::redirect($this->getURL());
1830          }
1831
1832          elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['install'])) {
1833
1834               if (is_array($_POST['install'])) {
1835                    $modules = array_keys($_POST['install']);
1836               }
1837
1838               $list = $this->store->get();
1839
1840               if (empty($list)) {
1841                    throw new Exception(__('No such theme.'));
1842               }
1843
1844               $count = 0;
1845               foreach($list as $id => $module) {
1846
1847                    if (!in_array($id, $modules)) {
1848                         continue;
1849                    }
1850
1851                    $dest = $this->getPath().'/'.basename($module['file']);
1852
1853                    # --BEHAVIOR-- themeBeforeAdd
1854                    $this->core->callBehavior('themeBeforeAdd', $module);
1855
1856                    $this->store->process($module['file'], $dest);
1857
1858                    # --BEHAVIOR-- themeAfterAdd
1859                    $this->core->callBehavior('themeAfterAdd', $module);
1860
1861                    $count++;
1862               }
1863
1864               dcPage::addSuccessNotice(
1865                    __('Theme has been successfully installed.', 'Themes have been successuflly installed.', $count)
1866               );
1867               http::redirect($this->getURL());
1868          }
1869
1870          elseif ($this->core->auth->isSuperAdmin() && !empty($_POST['update'])) {
1871
1872               if (is_array($_POST['update'])) {
1873                    $modules = array_keys($_POST['update']);
1874               }
1875
1876               $list = $this->store->get(true);
1877               if (empty($list)) {
1878                    throw new Exception(__('No such theme.'));
1879               }
1880
1881               $count = 0;
1882               foreach($list as $module) {
1883
1884                    if (!in_array($module['id'], $modules)) {
1885                         continue;
1886                    }
1887
1888                    $dest = $module['root'].'/../'.basename($module['file']);
1889
1890                    # --BEHAVIOR-- themeBeforeUpdate
1891                    $this->core->callBehavior('themeBeforeUpdate', $module);
1892
1893                    $this->store->process($module['file'], $dest);
1894
1895                    # --BEHAVIOR-- themeAfterUpdate
1896                    $this->core->callBehavior('themeAfterUpdate', $module);
1897
1898                    $count++;
1899               }
1900
1901               $tab = $count && $count == count($list) ? '#themes' : '#update';
1902
1903               dcPage::addSuccessNotice(
1904                    __('Theme has been successfully updated.', 'Themes have been successuflly updated.', $count)
1905               );
1906               http::redirect($this->getURL().$tab);
1907          }
1908
1909          # Manual actions
1910          elseif (!empty($_POST['upload_pkg']) && !empty($_FILES['pkg_file'])
1911               || !empty($_POST['fetch_pkg']) && !empty($_POST['pkg_url']))
1912          {
1913               if (empty($_POST['your_pwd']) || !$this->core->auth->checkPassword(crypt::hmac(DC_MASTER_KEY, $_POST['your_pwd']))) {
1914                    throw new Exception(__('Password verification failed'));
1915               }
1916
1917               if (!empty($_POST['upload_pkg'])) {
1918                    files::uploadStatus($_FILES['pkg_file']);
1919
1920                    $dest = $this->getPath().'/'.$_FILES['pkg_file']['name'];
1921                    if (!move_uploaded_file($_FILES['pkg_file']['tmp_name'], $dest)) {
1922                         throw new Exception(__('Unable to move uploaded file.'));
1923                    }
1924               }
1925               else {
1926                    $url = urldecode($_POST['pkg_url']);
1927                    $dest = $this->getPath().'/'.basename($url);
1928                    $this->store->download($url, $dest);
1929               }
1930
1931               # --BEHAVIOR-- themeBeforeAdd
1932               $this->core->callBehavior('themeBeforeAdd', null);
1933
1934               $ret_code = $this->store->install($dest);
1935
1936               # --BEHAVIOR-- themeAfterAdd
1937               $this->core->callBehavior('themeAfterAdd', null);
1938
1939               dcPage::addSuccessNotice($ret_code == 2 ?
1940                    __('Theme has been successfully updated.') :
1941                    __('Theme has been successfully installed.')
1942               );
1943               http::redirect($this->getURL().'#themes');
1944          }
1945
1946          else {
1947
1948               # --BEHAVIOR-- adminModulesListDoActions
1949               $this->core->callBehavior('adminModulesListDoActions', $this, $modules, 'theme');
1950
1951          }
1952
1953          return null;
1954     }
1955}
Note: See TracBrowser for help on using the repository browser.

Sites map