Dotclear

source: inc/admin/lib.moduleslist.php @ 3166:8bf4b2906b75

Revision 3166:8bf4b2906b75, 50.9 KB checked in by franck <carnet.franck.paul@…>, 9 years ago (diff)

Let select another theme even if theme folder is read-only, fixes #2161

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

Sites map