Dotclear

source: inc/admin/lib.moduleslist.php @ 3725:b47f38c701ee

Revision 3725:b47f38c701ee, 70.6 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

Use specialized input fields (color, email, url, number, …) where is relevant

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

Sites map