Dotclear

source: inc/admin/lib.moduleslist.php @ 3036:7ed4286c8013

Revision 3036:7ed4286c8013, 50.9 KB checked in by franck <carnet.franck.paul@…>, 10 years ago (diff)

Centralizes crypt function of pwd in class.Dotclear.auth.php, closes #1923

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

Sites map