Dotclear

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

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

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

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

Sites map