Dotclear

source: inc/admin/lib.moduleslist.php @ 2227:bfa7b3467627

Revision 2227:bfa7b3467627, 33.7 KB checked in by Denis Jean-Chirstian <contact@…>, 12 years ago (diff)

Continue clean and document code for plugins and themes admin pages

Line 
1<?php
2# -- BEGIN LICENSE BLOCK ---------------------------------------
3#
4# This file is part of Dotclear 2.
5#
6# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
7# Licensed under the GPL version 2.0 license.
8# See LICENSE file or
9# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10#
11# -- END LICENSE BLOCK -----------------------------------------
12if (!defined('DC_ADMIN_CONTEXT')) { return; }
13
14/**
15 * @ingroup DC_CORE
16 * @brief Helper for admin list of modules.
17 * @since 2.6
18
19 * Provides an object to parse XML feed of modules from a repository.
20 */
21class adminModulesList
22{
23     public $core;       /**< @var object    dcCore instance */
24     public $modules;    /**< @var object    dcModules instance */
25     public $store;      /**< @var object    dcStore instance */
26
27     public static $allow_multi_install = false;       /**< @var boolean   Work with multiple root directories */
28     public static $distributed_modules = array();     /**< @var array     List of modules distributed with Dotclear */
29
30     protected $list_id = 'unknow';     /**< @var string    Current list ID */
31     protected $data = array();         /**< @var array     Current modules */
32
33     protected $config_module = '';     /**< @var string    Module ID to configure */
34     protected $config_file = '';  /**< @var string    Module path to configure */
35     protected $config_content = '';    /**< @var string    Module configuration page content */
36
37     protected $path = false;           /**< @var string    Modules root directory */
38     protected $path_writable = false;  /**< @var boolean   Indicate if modules root directory is writable */
39     protected $path_pattern = false;   /**< @var string    Directory pattern to work on */
40
41     protected $page_url = 'plugins.php';    /**< @var string    Page URL */
42     protected $page_qs = '?';                    /**< @var string    Page query string */
43     protected $page_tab = '';                    /**< @var string    Page tab */
44
45     public static $nav_indexes = 'abcdefghijklmnopqrstuvwxyz0123456789'; /**< @var  string    Index list */
46     protected $nav_list = array();          /**< @var array     Index list with special index */
47     protected $nav_special = 'other';  /**< @var string    Text for other special index */
48
49     protected $sort_field = 'sname';   /**< @var string    Field used to sort modules */
50     protected $sort_asc = true;             /**< @var boolean   Sort order asc */
51
52     /**
53      * Constructor.
54      *
55      * Note that this creates dcStore instance.
56      *
57      * @param object    $modules       dcModules instance
58      * @param string    $modules_root  Modules root directories
59      * @param string    $xml_url       URL of modules feed from repository
60      */
61     public function __construct(dcModules $modules, $modules_root, $xml_url)
62     {
63          $this->core = $modules->core;
64          $this->modules = $modules;
65          $this->store = new dcStore($modules, $xml_url);
66
67          $this->setPath($modules_root);
68          $this->setIndex(__('other'));
69     }
70
71     /**
72      * Begin a new list.
73      *
74      * @param string    $id       New list ID
75      * @return     adminModulesList self instance
76      */
77     public function initList($id)
78     {
79          $this->data = array();
80          $this->page_tab = '';
81          $this->list_id = $id;
82
83          return $this;
84     }
85
86     /// @name Modules root directory methods
87     //@{
88     /**
89      * Set path info.
90      *
91      * @param string    $root          Modules root directories
92      * @return     adminModulesList self instance
93      */
94     protected function setPath($root)
95     {
96          $paths = explode(PATH_SEPARATOR, $root);
97          $path = array_pop($paths);
98          unset($paths);
99
100          $this->path = $path;
101          if (is_dir($path) && is_writeable($path)) {
102               $this->path_writable = true;
103               $this->path_pattern = preg_quote($path,'!');
104          }
105
106          return $this;
107     }
108
109     /**
110      * Get modules root directory.
111      *
112      * @return     Path to work on
113      */
114     public function getPath()
115     {
116          return $this->path;
117     }
118
119     /**
120      * Check if modules root directory is writable.
121      *
122      * @return     True if directory is writable
123      */
124     public function isWritablePath()
125     {
126          return $this->path_writable;
127     }
128
129     /**
130      * Check if root directory of a module is deletable.
131      *
132      * @param string    $root          Module root directory
133      * @return     True if directory is delatable
134      */
135     public function isDeletablePath($root)
136     {
137          return $this->path_writable 
138               && preg_match('!^'.$this->path_pattern.'!', $root) 
139               && $this->core->auth->isSuperAdmin();
140     }
141     //@}
142
143     /// @name Page methods
144     //@{
145     /**
146      * Set page base URL.
147      *
148      * @param string    $url      Page base URL
149      * @return     adminModulesList self instance
150      */
151     public function setURL($url)
152     {
153          $this->page_qs = strpos('?', $url) ? '&' : '?';
154          $this->page_url = $url;
155
156          return $this;
157     }
158
159     /**
160      * Get page URL.
161      *
162      * @param string|array   $queries  Additionnal query string
163      * @param booleany  $with_tab      Add current tab to URL end
164      * @return     Clean page URL
165      */
166     public function getURL($queries='', $with_tab=true)
167     {
168          return $this->page_url.
169               (!empty($queries) ? $this->page_qs : '').
170               (is_array($queries) ? http_build_query($queries) : $queries).
171               ($with_tab && !empty($this->page_tab) ? '#'.$this->page_tab : '');
172     }
173
174     /**
175      * Set page tab.
176      *
177      * @param string    $tab      Page tab
178      * @return     adminModulesList self instance
179      */
180     public function setTab($tab)
181     {
182          $this->page_tab = $tab;
183
184          return $this;
185     }
186
187     /**
188      * Get page tab.
189      *
190      * @return     Page tab
191      */
192     public function getTab()
193     {
194          return $this->page_tab;
195     }
196     //@}
197
198     /// @name Search methods
199     //@{
200     /**
201      * Get search query.
202      *
203      * @return     Search query
204      */
205     public function getSearch()
206     {
207          $query = !empty($_REQUEST['m_search']) ? trim($_REQUEST['m_search']) : null;
208          return strlen($query) > 2 ? $query : null;
209     }
210
211     /**
212      * Display searh form.
213      *
214      * @return     adminModulesList self instance
215      */
216     public function displaySearch()
217     {
218          $query = $this->getSearch();
219
220          if (empty($this->data) && $query === null) {
221               return $this;
222          }
223
224          echo 
225          '<form action="'.$this->getURL().'" method="get" class="fieldset">'.
226          '<p><label for="m_search" class="classic">'.__('Search in repository:').'&nbsp;</label><br />'.
227          form::field(array('m_search','m_search'), 30, 255, html::escapeHTML($query)).
228          '<input type="submit" value="'.__('Search').'" /> ';
229          if ($query) { echo ' <a href="'.$this->getURL().'" class="button">'.__('Reset search').'</a>'; }
230          echo '</p>'.
231          '</form>';
232
233          if ($query) {
234               echo 
235               '<p class="message">'.sprintf(
236                    __('Found %d result for search "%s":', 'Found %d results for search "%s":', count($this->data)), 
237                    count($this->data), html::escapeHTML($query)
238                    ).
239               '</p>';
240          }
241          return $this;
242     }
243     //@}
244
245     /// @name Navigation menu methods
246     //@{
247     /**
248      * Set navigation special index.
249      *
250      * @return     adminModulesList self instance
251      */
252     public function setIndex($str)
253     {
254          $this->nav_special = (string) $str;
255          $this->nav_list = array_merge(str_split(self::$nav_indexes), array($this->nav_special));
256
257          return $this;
258     }
259
260     /**
261      * Get index from query.
262      *
263      * @return     Query index or default one
264      */
265     public function getIndex()
266     {
267          return isset($_REQUEST['m_nav']) && in_array($_REQUEST['m_nav'], $this->nav_list) ? $_REQUEST['m_nav'] : $this->nav_list[0];
268     }
269
270     /**
271      * Display navigation by index menu.
272      *
273      * @return     adminModulesList self instance
274      */
275     public function displayIndex()
276     {
277          if (empty($this->data) || $this->getSearch() !== null) {
278               return $this;
279          }
280
281          # Fetch modules required field
282          $indexes = array();
283          foreach ($this->data as $id => $module) {
284               if (!isset($module[$this->sort_field])) {
285                    continue;
286               }
287               $char = substr($module[$this->sort_field], 0, 1);
288               if (!in_array($char, $this->nav_list)) {
289                    $char = $this->nav_special;
290               }
291               if (!isset($indexes[$char])) {
292                    $indexes[$char] = 0;
293               }
294               $indexes[$char]++;
295          }
296
297          $buttons = array();
298          foreach($this->nav_list as $char) {
299               # Selected letter
300               if ($this->getIndex() == $char) {
301                    $buttons[] = '<li class="active" title="'.__('current selection').'"><strong> '.$char.' </strong></li>';
302               }
303               # Letter having modules
304               elseif (!empty($indexes[$char])) {
305                    $title = sprintf(__('%d module', '%d modules', $indexes[$char]), $indexes[$char]);
306                    $buttons[] = '<li class="btn" title="'.$title.'"><a href="'.$this->getURL('m_nav='.$char).'" title="'.$title.'"> '.$char.' </a></li>';
307               }
308               # Letter without modules
309               else {
310                    $buttons[] = '<li class="btn no-link" title="'.__('no module').'"> '.$char.' </li>';
311               }
312          }
313          # Parse navigation menu
314          echo '<div class="pager">'.__('Browse index:').' <ul>'.implode('',$buttons).'</ul></div>';
315
316          return $this;
317     }
318     //@}
319
320     /// @name Sort methods
321     //@{
322     /**
323      * Set default sort field.
324      *
325      * @return     adminModulesList self instance
326      */
327     public function setSort($field, $asc=true)
328     {
329          $this->sort_field = $field;
330          $this->sort_asc = (boolean) $asc;
331
332          return $this;
333     }
334
335     /**
336      * Get sort field from query.
337      *
338      * @return     Query sort field or default one
339      */
340     public function getSort()
341     {
342          return !empty($_REQUEST['m_sort']) ? $_REQUEST['m_sort'] : $this->sort_field;
343     }
344
345     /**
346      * Display sort field form.
347      *
348      * @note  This method is not implemented yet
349      * @return     adminModulesList self instance
350      */
351     public function displaySort()
352     {
353          //
354
355          return $this;
356     }
357     //@}
358
359     /// @name Modules methods
360     //@{
361     /**
362      * Set modules and sanitize them.
363      *
364      * @return     adminModulesList self instance
365      */
366     public function setModules($modules)
367     {
368          $this->data = array();
369          if (!empty($modules) && is_array($modules)) {
370               foreach($modules as $id => $module) {
371                    $this->data[$id] = self::sanitizeModule($id, $module);
372               }
373          }
374          return $this;
375     }
376
377     /**
378      * Get modules currently set.
379      *
380      * @return     Array of modules
381      */
382     public function getModules()
383     {
384          return $this->data;
385     }
386
387     /**
388      * Sanitize a module.
389      *
390      * This clean infos of a module by adding default keys
391      * and clean some of them, sanitize module can safely
392      * be used in lists.
393      *
394      * @return     Array of the module informations
395      */
396     public static function sanitizeModule($id, $module)
397     {
398          $label = empty($module['label']) ? $id : $module['label'];
399          $name = __(empty($module['name']) ? $label : $module['name']);
400         
401          return array_merge(
402               # Default values
403               array(
404                    'desc'                   => '',
405                    'author'            => '',
406                    'version'                => 0,
407                    'current_version'   => 0,
408                    'root'                   => '',
409                    'root_writable'     => false,
410                    'permissions'       => null,
411                    'parent'            => null,
412                    'priority'               => 1000,
413                    'standalone_config' => false,
414                    'support'                => '',
415                    'section'                => '',
416                    'tags'                   => '',
417                    'details'                => '',
418                    'sshot'             => '',
419                    'score'                  => 0
420               ),
421               # Module's values
422               $module,
423               # Clean up values
424               array(
425                    'id'                     => $id,
426                    'sid'                    => self::sanitizeString($id),
427                    'label'             => $label,
428                    'name'                   => $name,
429                    'sname'             => self::sanitizeString($name)
430               )
431          );
432     }
433
434     /**
435      * Check if a module is part of the distribution.
436      *
437      * @param string    $id       Module root directory
438      * @return     True if module is part of the distribution
439      */
440     public static function isDistributedModule($id)
441     {
442          $distributed_modules = self::$distributed_modules;
443
444          return is_array($distributed_modules) && in_array($id, $distributed_modules);
445     }
446
447     /**
448      * Sort modules list by specific field.
449      *
450      * @param string    $module        Array of modules
451      * @param string    $field         Field to sort from
452      * @param bollean   $asc      Sort asc if true, else decs
453      * @return     Array of sorted modules
454      */
455     public static function sortModules($modules, $field, $asc=true)
456     {
457          $sorter = array();
458          foreach($modules as $id => $module) {
459               $sorter[$id] = isset($module[$field]) ? $module[$field] : $field;
460          }
461          array_multisort($sorter, $asc ? SORT_ASC : SORT_DESC, $modules);
462
463          return $modules;
464     }
465
466     /**
467      * Display list of modules.
468      *
469      * @param array     $cols          List of colones (module field) to display
470      * @param array     $actions  List of predefined actions to show on form
471      * @param boolean   $nav_limit     Limit list to previously selected index
472      * @return     adminModulesList self instance
473      */
474     public function displayModules($cols=array('name', 'version', 'desc'), $actions=array(), $nav_limit=false)
475     {
476          echo 
477          '<div class="table-outer">'.
478          '<table id="'.html::escapeHTML($this->list_id).'" class="modules'.(in_array('expander', $cols) ? ' expandable' : '').'">'.
479          '<caption class="hidden">'.html::escapeHTML(__('Modules list')).'</caption><tr>';
480
481          if (in_array('name', $cols)) {
482               echo 
483               '<th class="first nowrap"'.(in_array('icon', $cols) ? ' colspan="2"' : '').'>'.__('Name').'</th>';
484          }
485
486          if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) {
487               echo 
488               '<th class="nowrap">'.__('Score').'</th>';
489          }
490
491          if (in_array('version', $cols)) {
492               echo 
493               '<th class="nowrap count" scope="col">'.__('Version').'</th>';
494          }
495
496          if (in_array('current_version', $cols)) {
497               echo 
498               '<th class="nowrap count" scope="col">'.__('Current version').'</th>';
499          }
500
501          if (in_array('desc', $cols)) {
502               echo 
503               '<th class="nowrap" scope="col">'.__('Details').'</th>';
504          }
505
506          if (in_array('distrib', $cols)) {
507               echo '<th'.(in_array('desc', $cols) ? '' : ' class="maximal"').'></th>';
508          }
509
510          if (!empty($actions) && $this->core->auth->isSuperAdmin()) {
511               echo 
512               '<th class="minimal nowrap">'.__('Action').'</th>';
513          }
514
515          echo 
516          '</tr>';
517
518          $sort_field = $this->getSort();
519
520          # Sort modules by $sort_field (default sname)
521          $modules = $this->getSearch() === null ?
522               self::sortModules($this->data, $sort_field, $this->sort_asc) :
523               $this->data;
524
525          $count = 0;
526          foreach ($modules as $id => $module)
527          {
528               # Show only requested modules
529               if ($nav_limit && $this->getSearch() === null) {
530                    $char = substr($module[$sort_field], 0, 1);
531                    if (!in_array($char, $this->nav_list)) {
532                         $char = $this->nav_special;
533                    }
534                    if ($this->getIndex() != $char) {
535                         continue;
536                    }
537               }
538
539               echo 
540               '<tr class="line" id="'.html::escapeHTML($this->list_id).'_m_'.html::escapeHTML($id).'">';
541
542               if (in_array('icon', $cols)) {
543                    echo 
544                    '<td class="module-icon nowrap">'.sprintf(
545                         '<img alt="%1$s" title="%1$s" src="%2$s" />', 
546                         html::escapeHTML($id), file_exists($module['root'].'/icon.png') ? 'index.php?pf='.$id.'/icon.png' : 'images/module.png'
547                    ).'</td>';
548               }
549
550               # Link to config file
551               $config = in_array('config', $cols) && !empty($module['root']) && file_exists(path::real($module['root'].'/_config.php'));
552
553               echo 
554               '<td class="module-name nowrap" scope="row">'.($config ? 
555                    '<a href="'.$this->getURL('module='.$id.'&conf=1').'" title"'.sprintf(__('Configure module "%s"'), html::escapeHTML($module['name'])).'">'.html::escapeHTML($module['name']).'</a>' : 
556                    html::escapeHTML($module['name'])
557               ).'</td>';
558
559               # Display score only for debug purpose
560               if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) {
561                    echo 
562                    '<td class="module-version nowrap count"><span class="debug">'.$module['score'].'</span></td>';
563               }
564
565               if (in_array('version', $cols)) {
566                    echo 
567                    '<td class="module-version nowrap count">'.html::escapeHTML($module['version']).'</td>';
568               }
569
570               if (in_array('current_version', $cols)) {
571                    echo 
572                    '<td class="module-current-version nowrap count">'.html::escapeHTML($module['current_version']).'</td>';
573               }
574
575               if (in_array('desc', $cols)) {
576                    echo 
577                    '<td class="module-desc maximal">'.html::escapeHTML($module['desc']).'</td>';
578               }
579
580               if (in_array('distrib', $cols)) {
581                    echo 
582                    '<td class="module-distrib">'.(self::isDistributedModule($id) ? 
583                         '<img src="images/dotclear_pw.png" alt="'.
584                         __('Module from official distribution').'" title="'.
585                         __('module from official distribution').'" />' 
586                    : '').'</td>';
587               }
588
589               if (!empty($actions) && $this->core->auth->isSuperAdmin()) {
590                    $buttons = $this->getActions($id, $module, $actions);
591
592                    echo 
593                    '<td class="module-actions nowrap">'.
594
595                    '<form action="'.$this->getURL().'" method="post">'.
596                    '<div>'.
597                    $this->core->formNonce().
598                    form::hidden(array('module'), html::escapeHTML($id)).
599
600                    implode(' ', $buttons).
601
602                    '</div>'.
603                    '</form>'.
604
605                    '</td>';
606               }
607
608               echo 
609               '</tr>';
610
611               $count++;
612          }
613          echo 
614          '</table></div>';
615
616          if(!$count && $this->getSearch() === null) {
617               echo 
618               '<p class="message">'.__('No module matches your search.').'</p>';
619          }
620
621          return $this;
622     }
623
624     /**
625      * Get action buttons to add to modules list.
626      *
627      * @param string    $id            Module ID
628      * @param array     $module        Module info
629      * @param array     $actions  Actions keys
630      * @return     Array of actions buttons
631      */
632     protected function getActions($id, $module, $actions)
633     {
634          $submits = array();
635
636          # Use loop to keep requested order
637          foreach($actions as $action) {
638               switch($action) {
639
640                    # Deactivate
641                    case 'activate': if ($module['root_writable']) {
642                         $submits[] = 
643                         '<input type="submit" name="activate" value="'.__('Activate').'" />';
644                    } break;
645
646                    # Activate
647                    case 'deactivate': if ($module['root_writable']) {
648                         $submits[] = 
649                         '<input type="submit" name="deactivate" value="'.__('Deactivate').'" class="reset" />';
650                    } break;
651
652                    # Delete
653                    case 'delete': if ($this->isDeletablePath($module['root'])) {
654                         $submits[] = 
655                         '<input type="submit" class="delete" name="delete" value="'.__('Delete').'" />';
656                    } break;
657
658                    # Install (from store)
659                    case 'install': if ($this->path_writable) {
660                         $submits[] = 
661                         '<input type="submit" name="install" value="'.__('Install').'" />';
662                    } break;
663
664                    # Update (from store)
665                    case 'update': if ($this->path_writable) {
666                         $submits[] = 
667                         '<input type="submit" name="update" value="'.__('Update').'" />';
668                    } break;
669               }
670          }
671
672          return $submits;
673     }
674
675     /**
676      * Execute POST action.
677      *
678      * @note  Set a notice on success through dcPage::addSuccessNotice
679      * @throw Exception Module not find or command failed
680      * @param string    $prefix        Prefix used on behaviors
681      * @return     Null
682      */
683     public function doActions($prefix)
684     {
685          if (empty($_POST) || !empty($_REQUEST['conf']) 
686          || !$this->core->auth->isSuperAdmin() || !$this->isWritablePath()) {
687               return null;
688          }
689
690          # List actions
691          if (!empty($_POST['module'])) {
692
693               $id = $_POST['module'];
694
695               if (!empty($_POST['activate'])) {
696
697                    $enabled = $this->modules->getDisabledModules();
698                    if (!isset($enabled[$id])) {
699                         throw new Exception(__('No such module.'));
700                    }
701
702                    # --BEHAVIOR-- moduleBeforeActivate
703                    $this->core->callBehavior($prefix.'BeforeActivate', $id);
704
705                    $this->modules->activateModule($id);
706
707                    # --BEHAVIOR-- moduleAfterActivate
708                    $this->core->callBehavior($prefix.'AfterActivate', $id);
709
710                    dcPage::addSuccessNotice(__('Module has been successfully activated.'));
711                    http::redirect($this->getURL());
712               }
713
714               elseif (!empty($_POST['deactivate'])) {
715
716                    if (!$this->modules->moduleExists($id)) {
717                         throw new Exception(__('No such module.'));
718                    }
719
720                    $module = $this->modules->getModules($id);
721                    $module['id'] = $id;
722
723                    if (!$module['root_writable']) {
724                         throw new Exception(__('You don\'t have permissions to deactivate this module.'));
725                    }
726
727                    # --BEHAVIOR-- moduleBeforeDeactivate
728                    $this->core->callBehavior($prefix.'BeforeDeactivate', $module);
729
730                    $this->modules->deactivateModule($id);
731
732                    # --BEHAVIOR-- moduleAfterDeactivate
733                    $this->core->callBehavior($prefix.'AfterDeactivate', $module);
734
735                    dcPage::addSuccessNotice(__('Module has been successfully deactivated.'));
736                    http::redirect($this->getURL());
737               }
738
739               elseif (!empty($_POST['delete'])) {
740
741                    $disabled = $this->modules->getDisabledModules();
742                    if (!isset($disabled[$id])) {
743
744                         if (!$this->modules->moduleExists($id)) {
745                              throw new Exception(__('No such module.'));
746                         }
747
748                         $module = $this->modules->getModules($id);
749                         $module['id'] = $id;
750
751                         if (!$this->isDeletablePath($module['root'])) {
752                              throw new Exception(__("You don't have permissions to delete this module."));
753                         }
754
755                         # --BEHAVIOR-- moduleBeforeDelete
756                         $this->core->callBehavior($prefix.'BeforeDelete', $module);
757
758                         $this->modules->deleteModule($id);
759
760                         # --BEHAVIOR-- moduleAfterDelete
761                         $this->core->callBehavior($prefix.'AfterDelete', $module);
762                    }
763                    else {
764                         $this->modules->deleteModule($id, true);
765                    }
766
767                    dcPage::addSuccessNotice(__('Module has been successfully deleted.'));
768                    http::redirect($this->getURL());
769               }
770
771               elseif (!empty($_POST['install'])) {
772
773                    $updated = $this->store->get();
774                    if (!isset($updated[$id])) {
775                         throw new Exception(__('No such module.'));
776                    }
777
778                    $module = $updated[$id];
779                    $module['id'] = $id;
780
781                    $dest = $this->getPath().'/'.basename($module['file']);
782
783                    # --BEHAVIOR-- moduleBeforeAdd
784                    $this->core->callBehavior($prefix.'BeforeAdd', $module);
785
786                    $ret_code = $this->store->process($module['file'], $dest);
787
788                    # --BEHAVIOR-- moduleAfterAdd
789                    $this->core->callBehavior($prefix.'AfterAdd', $module);
790
791                    dcPage::addSuccessNotice($ret_code == 2 ?
792                         __('Module has been successfully updated.') :
793                         __('Module has been successfully installed.')
794                    );
795                    http::redirect($this->getURL());
796               }
797
798               elseif (!empty($_POST['update'])) {
799
800                    $updated = $store->get();
801                    if (!isset($updated[$id])) {
802                         throw new Exception(__('No such module.'));
803                    }
804
805                    if (!$this->modules->moduleExists($id)) {
806                         throw new Exception(__('No such module.'));
807                    }
808
809                    $module = $updated[$id];
810                    $module['id'] = $id;
811
812                    if (!self::$allow_multi_install) {
813                         $dest = $module['root'].'/../'.basename($module['file']);
814                    }
815                    else {
816                         $dest = $this->getPath().'/'.basename($module['file']);
817                         if ($module['root'] != $dest) {
818                              @file_put_contents($module['root'].'/_disabled', '');
819                         }
820                    }
821
822                    # --BEHAVIOR-- moduleBeforeUpdate
823                    $this->core->callBehavior($prefix.'BeforeUpdate', $module);
824
825                    $this->store->process($module['file'], $dest);
826
827                    # --BEHAVIOR-- moduleAfterUpdate
828                    $this->core->callBehavior($prefix.'AfterUpdate', $module);
829
830                    dcPage::addSuccessNotice(__('Module has been successfully updated.'));
831                    http::redirect($this->getURL());
832               }
833          }
834          # Manual actions
835          elseif (!empty($_POST['upload_pkg']) && !empty($_FILES['pkg_file']) 
836               || !empty($_POST['fetch_pkg']) && !empty($_POST['pkg_url']))
837          {
838               if (empty($_POST['your_pwd']) || !$this->core->auth->checkPassword(crypt::hmac(DC_MASTER_KEY, $_POST['your_pwd']))) {
839                    throw new Exception(__('Password verification failed'));
840               }
841
842               if (!empty($_POST['upload_pkg'])) {
843                    files::uploadStatus($_FILES['pkg_file']);
844                   
845                    $dest = $this->getPath().'/'.$_FILES['pkg_file']['name'];
846                    if (!move_uploaded_file($_FILES['pkg_file']['tmp_name'], $dest)) {
847                         throw new Exception(__('Unable to move uploaded file.'));
848                    }
849               }
850               else {
851                    $url = urldecode($_POST['pkg_url']);
852                    $dest = $this->getPath().'/'.basename($url);
853                    $this->store->download($url, $dest);
854               }
855
856               # --BEHAVIOR-- moduleBeforeAdd
857               $this->core->callBehavior($prefix.'BeforeAdd', null);
858
859               $ret_code = $this->store->install($dest);
860
861               # --BEHAVIOR-- moduleAfterAdd
862               $this->core->callBehavior($prefix.'AfterAdd', null);
863
864               dcPage::addSuccessNotice($ret_code == 2 ?
865                    __('Module has been successfully updated.') :
866                    __('Module has been successfully installed.')
867               );
868               http::redirect($this->getURL().'#'.$prefix);
869          }
870
871          return null;
872     }
873
874     /**
875      * Display tab for manual installation.
876      *
877      * @return     adminModulesList self instance
878      */
879     public function displayManualForm()
880     {
881          if (!$this->core->auth->isSuperAdmin() || !$this->isWritablePath()) {
882               return null;
883          }
884
885          # 'Upload module' form
886          echo
887          '<form method="post" action="'.$this->getURL().'" id="uploadpkg" enctype="multipart/form-data" class="fieldset">'.
888          '<h4>'.__('Upload a zip file').'</h4>'.
889          '<p class="field"><label for="pkg_file" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Zip file path:').'</label> '.
890          '<input type="file" name="pkg_file" id="pkg_file" /></p>'.
891          '<p class="field"><label for="your_pwd1" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Your password:').'</label> '.
892          form::password(array('your_pwd','your_pwd1'),20,255).'</p>'.
893          '<p><input type="submit" name="upload_pkg" value="'.__('Upload').'" />'.
894          $this->core->formNonce().'</p>'.
895          '</form>';
896
897          # 'Fetch module' form
898          echo
899          '<form method="post" action="'.$this->getURL().'" id="fetchpkg" class="fieldset">'.
900          '<h4>'.__('Download a zip file').'</h4>'.
901          '<p class="field"><label for="pkg_url" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Zip file URL:').'</label> '.
902          form::field(array('pkg_url','pkg_url'),40,255).'</p>'.
903          '<p class="field"><label for="your_pwd2" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.__('Your password:').'</label> '.
904          form::password(array('your_pwd','your_pwd2'),20,255).'</p>'.
905          '<p><input type="submit" name="fetch_pkg" value="'.__('Download').'" />'.
906          $this->core->formNonce().'</p>'.
907          '</form>';
908
909          return $this;
910     }
911     //@}
912
913     /// @name Module configuration methods
914     //@{
915     /**
916      * Prepare module configuration.
917      *
918      * We need to get configuration content in three steps
919      * and out of this class to keep backward compatibility.
920      *
921      * if ($xxx->setConfiguration()) {
922      *   include $xxx->includeConfiguration();
923      * }
924      * $xxx->getConfiguration();
925      * ... [put here page headers and other stuff]
926      * $xxx->displayConfiguration();
927      *
928      * @param string    $id       Module to work on or it gather through REQUEST
929      * @return     True if config set
930      */
931     public function setConfiguration($id=null)
932     {
933          if (empty($_REQUEST['conf']) || empty($_REQUEST['module']) && !$id) {
934               return false;
935          }
936         
937          if (!empty($_REQUEST['module']) && empty($id)) {
938               $id = $_REQUEST['module'];
939          }
940
941          if (!$this->modules->moduleExists($id)) {
942               $core->error->add(__('Unknow module ID'));
943               return false;
944          }
945
946          $module = $this->modules->getModules($id);
947          $module = self::sanitizeModule($id, $module);
948          $file = path::real($module['root'].'/_config.php');
949
950          if (!file_exists($file)) {
951               $core->error->add(__('This module has no configuration file.'));
952               return false;
953          }
954
955          $this->config_module = $module;
956          $this->config_file = $file;
957          $this->config_content = '';
958
959          if (!defined('DC_CONTEXT_MODULE')) {
960               define('DC_CONTEXT_MODULE', true);
961          }
962
963          return true;
964     }
965
966     /**
967      * Get path of module configuration file.
968      *
969      * @note Required previously set file info
970      * @return Full path of config file or null
971      */
972     public function includeConfiguration()
973     {
974          if (!$this->config_file) {
975               return null;
976          }
977
978          ob_start();
979
980          return $this->config_file;
981     }
982
983     /**
984      * Gather module configuration file content.
985      *
986      * @note Required previously file inclusion
987      * @return True if content has been captured
988      */
989     public function getConfiguration()
990     {
991          if ($this->config_file) {
992               $this->config_content = ob_get_contents();
993          }
994
995          ob_end_clean();
996
997          return !empty($this->file_content);
998     }
999
1000     /**
1001      * Display module configuration form.
1002      *
1003      * @note Required previously gathered content
1004      * @return     adminModulesList self instance
1005      */
1006     public function displayConfiguration()
1007     {
1008          if ($this->config_file) {
1009
1010               if (!$this->config_module['standalone_config']) {
1011                    echo
1012                    '<form id="module_config" action="'.$this->getURL('conf=1').'" method="post" enctype="multipart/form-data">'.
1013                    '<h3>'.sprintf(__('Configure plugin "%s"'), html::escapeHTML($this->config_module['name'])).'</h3>'.
1014                    '<p><a class="back" href="'.$this->getURL().'#plugins">'.__('Back').'</a></p>';
1015               }
1016
1017               echo $this->config_content;
1018
1019               if (!$this->config_module['standalone_config']) {
1020                    echo
1021                    '<p class="clear"><input type="submit" name="save" value="'.__('Save').'" />'.
1022                    form::hidden('module', $this->config_module['id']).
1023                    $this->core->formNonce().'</p>'.
1024                    '</form>';
1025               }
1026          }
1027
1028          return $this;
1029     }
1030     //@}
1031
1032     /**
1033      * Helper to sanitize a string.
1034      *
1035      * Used for search or id.
1036      *
1037      * @param string    $str      String to sanitize
1038      * @return     Sanitized string
1039      */
1040     public static function sanitizeString($str)
1041     {
1042          return preg_replace('/[^A-Za-z0-9\@\#+_-]/', '', strtolower($str));
1043     }
1044}
1045
1046/**
1047 * @ingroup DC_CORE
1048 * @brief Helper to manage list of themes.
1049 * @since 2.6
1050 */
1051class adminThemesList extends adminModulesList
1052{
1053     protected $page_url = 'blog_theme.php';
1054
1055     public function displayModules($cols=array('name', 'config', 'version', 'desc'), $actions=array(), $nav_limit=false)
1056     {
1057          echo 
1058          '<div id="'.html::escapeHTML($this->list_id).'" class="modules'.(in_array('expander', $cols) ? ' expandable' : '').' one-box">';
1059
1060          $sort_field = $this->getSort();
1061
1062          # Sort modules by id
1063          $modules = $this->getSearch() === null ?
1064               self::sortModules($this->data, $sort_field, $this->sort_asc) :
1065               $this->data;
1066
1067          $res = '';
1068          $count = 0;
1069          foreach ($modules as $id => $module)
1070          {
1071               # Show only requested modules
1072               if ($nav_limit && $this->getSearch() === null) {
1073                    $char = substr($module[$sort_field], 0, 1);
1074                    if (!in_array($char, $this->nav_list)) {
1075                         $char = $this->nav_special;
1076                    }
1077                    if ($this->getIndex() != $char) {
1078                         continue;
1079                    }
1080               }
1081
1082               $current = $this->core->blog->settings->system->theme == $id && $this->modules->moduleExists($id);
1083               $distrib = self::isDistributedModule($id) ? ' dc-box' : '';
1084
1085               $line = 
1086               '<div class="box '.($current ? 'medium current-theme' : 'small theme').$distrib.'">';
1087
1088               if (in_array('name', $cols)) {
1089                    $line .= 
1090                    '<h4 class="module-name">'.html::escapeHTML($module['name']).'</h4>';
1091               }
1092
1093               # Display score only for debug purpose
1094               if (in_array('score', $cols) && $this->getSearch() !== null && defined('DC_DEBUG') && DC_DEBUG) {
1095                    $line .= 
1096                    '<p class="module-score debug">'.sprintf(__('Score: %s'), $module['score']).'</p>';
1097               }
1098
1099               if (in_array('sshot', $cols)) {
1100                    # Screenshot from url
1101                    if (preg_match('#^http(s)?://#', $module['sshot'])) {
1102                         $sshot = $module['sshot'];
1103                    }
1104                    # Screenshot from installed module
1105                    elseif (file_exists($this->core->blog->themes_path.'/'.$id.'/screenshot.jpg')) {
1106                         $sshot = $this->getURL('shot='.rawurlencode($id));
1107                    }
1108                    # Default screenshot
1109                    else {
1110                         $sshot = 'images/noscreenshot.png';
1111                    }
1112
1113                    $line .= 
1114                    '<div class="module-sshot"><img src="'.$sshot.'" alt="'.
1115                    sprintf(__('%s screenshot.'), html::escapeHTML($module['name'])).'" /></div>';
1116               }
1117
1118               $line .= 
1119               '<div class="module-infos toggle-bloc">'.
1120               '<p>';
1121
1122               if (in_array('desc', $cols)) {
1123                    $line .= 
1124                    '<span class="module-desc">'.html::escapeHTML($module['desc']).'</span> ';
1125               }
1126
1127               if (in_array('author', $cols)) {
1128                    $line .= 
1129                    '<span class="module-author">'.sprintf(__('by %s'),html::escapeHTML($module['author'])).'</span> ';
1130               }
1131
1132               if (in_array('version', $cols)) {
1133                    $line .= 
1134                    '<span class="module-version">'.sprintf(__('version %s'),html::escapeHTML($module['version'])).'</span> ';
1135               }
1136
1137               if (in_array('current_version', $cols)) {
1138                    $line .= 
1139                    '<span class="module-current-version">'.sprintf(__('(current version %s)'),html::escapeHTML($module['current_version'])).'</span> ';
1140               }
1141
1142               if (in_array('parent', $cols) && !empty($module['parent'])) {
1143                    if ($this->modules->moduleExists($module['parent'])) {
1144                         $line .= 
1145                         '<span class="module-parent-ok">'.sprintf(__('(built on "%s")'),html::escapeHTML($module['parent'])).'</span> ';
1146                    }
1147                    else {
1148                         $line .= 
1149                         '<span class="module-parent-missing">'.sprintf(__('(requires "%s")'),html::escapeHTML($module['parent'])).'</span> ';
1150                    }
1151               }
1152
1153               $has_details = in_array('details', $cols) && !empty($module['details']);
1154               $has_support = in_array('support', $cols) && !empty($module['support']);
1155               if ($has_details || $has_support) {
1156                    $line .=
1157                    '<span class="mod-more">'.__('Help:').' ';
1158
1159                    if ($has_details) {
1160                         $line .= 
1161                         '<a class="module-details" href="'.$module['details'].'">'.__('Details').'</a>';
1162                    }
1163
1164                    if ($has_support) {
1165                         $line .= 
1166                         ' - <a class="module-support" href="'.$module['support'].'">'.__('Support').'</a>';
1167                    }
1168
1169                    $line .=
1170                    '</span>';
1171               }
1172
1173               $line .= 
1174               '</p>'.
1175               '</div>';
1176
1177               $line .= 
1178               '<div class="module-actions toggle-bloc">';
1179               
1180               # Plugins actions
1181               if ($current) {
1182
1183                    # _GET actions
1184                    if (file_exists(path::real($this->core->blog->themes_path.'/'.$id).'/style.css')) {
1185                         $theme_url = preg_match('#^http(s)?://#', $this->core->blog->settings->system->themes_url) ?
1186                              http::concatURL($this->core->blog->settings->system->themes_url, '/'.$id) :
1187                              http::concatURL($this->core->blog->url, $this->core->blog->settings->system->themes_url.'/'.$id);
1188                         $line .= 
1189                         '<p><a href="'.$theme_url.'/style.css">'.__('View stylesheet').'</a></p>';
1190                    }
1191
1192                    if (file_exists(path::real($this->core->blog->themes_path.'/'.$id).'/_config.php')) {
1193                         $line .= 
1194                         '<p><a href="'.$this->getURL('module='.$id.'&conf=1', false).'" class="button">'.__('Configure theme').'</a></p>';
1195                    }
1196
1197                    # --BEHAVIOR-- adminCurrentThemeDetails
1198                    $line .= 
1199                    $this->core->callBehavior('adminCurrentThemeDetails', $this->core, $id, $module);
1200               }
1201
1202               # _POST actions
1203               if (!empty($actions)) {
1204                    $line .=
1205                    '<form action="'.$this->getURL().'" method="post">'.
1206                    '<div>'.
1207                    $this->core->formNonce().
1208                    form::hidden(array('module'), html::escapeHTML($id)).
1209
1210                    implode(' ', $this->getActions($id, $module, $actions)).
1211 
1212                    '</div>'.
1213                    '</form>';
1214               }
1215
1216               $line .= 
1217               '</div>';
1218
1219               $line .=
1220               '</div>';
1221
1222               $count++;
1223
1224               $res = $current ? $line.$res : $res.$line;
1225          }
1226          echo 
1227          $res.
1228          '</div>';
1229
1230          if(!$count && $this->getSearch() === null) {
1231               echo 
1232               '<p class="message">'.__('No module matches your search.').'</p>';
1233          }
1234     }
1235
1236     protected function getActions($id, $module, $actions)
1237     {
1238          $submits = array();
1239
1240          $this->core->blog->settings->addNamespace('system');
1241          if ($id != $this->core->blog->settings->system->theme) {
1242
1243               # Select theme to use on curent blog
1244               if (in_array('select', $actions) && $this->path_writable) {
1245                    $submits[] = 
1246                    '<input type="submit" name="select" value="'.__('Use this one').'" />';
1247               }
1248          }
1249
1250          return array_merge(
1251               $submits,
1252               parent::getActions($id, $module, $actions)
1253          );
1254     }
1255
1256     public function doActions($prefix)
1257     {
1258          if (!empty($_POST) && empty($_REQUEST['conf']) && $this->isWritablePath()) {
1259
1260               # Select theme to use on curent blog
1261               if (!empty($_POST['module']) && !empty($_POST['select'])) {
1262                    $id = $_POST['module'];
1263
1264                    if (!$this->modules->moduleExists($id)) {
1265                         throw new Exception(__('No such module.'));
1266                    }
1267
1268                    $this->core->blog->settings->addNamespace('system');
1269                    $this->core->blog->settings->system->put('theme',$id);
1270                    $this->core->blog->triggerBlog();
1271
1272                    dcPage::addSuccessNotice(__('Module has been successfully selected.'));
1273                    http::redirect($this->getURL().'#themes');
1274               }
1275          }
1276
1277          return parent::doActions($prefix);
1278     }
1279}
Note: See TracBrowser for help on using the repository browser.

Sites map