Dotclear

source: inc/admin/lib.moduleslist.php @ 2849:97e1b334e54b

Revision 2849:97e1b334e54b, 50.2 KB checked in by Dsls, 11 years ago (diff)

Deprecated $core->adminurl->decode, use $core->adminurl->get instead (with urlencode set to false)

Added dcPage::getPF to shortcut plugin file inclusion

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

Sites map