Dotclear

source: inc/admin/lib.moduleslist.php @ 3775:6c367dee2de0

Revision 3775:6c367dee2de0, 70.5 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

Add module description to title attribute of raw, hide description column on small displays (<= 1280px wide) ; should fixes #2268

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

Sites map