Dotclear

source: inc/admin/lib.moduleslist.php @ 2997:8f7c065a8639

Revision 2997:8f7c065a8639, 50.5 KB checked in by Dsls, 10 years ago (diff)

First step in modules dependencies management

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

Sites map