Dotclear

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

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

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

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

Sites map