1<?php
2/**
3 * DokuWiki Plugin lightweightcss (Action Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  i-net /// software <tools@inetsoftware.de>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11
12class action_plugin_lightweightcss extends DokuWiki_Action_Plugin {
13
14    private const DEFAULT_ADMIN_INCLUDE = array(
15        '/css/admin/',
16        '/lib/plugins/config/',
17        '/lib/plugins/searchindex/',
18        '/lib/plugins/sync/',
19        '/lib/plugins/batchedit/',
20        '/lib/plugins/usermanager/',
21        '/lib/plugins/upgrade/',
22        '/lib/plugins/extension/',
23        '/lib/plugins/tagsections/',
24        '/lib/plugins/move/',
25        '/lib/plugins/acl/',
26        '/lib/plugins/multiorphan/',
27        '/lib/plugins/edittable/',
28        '/lib/plugins/sectionedit/',
29        '/lib/plugins/wrap/',
30        '/lib/scripts/',
31        '/lib/styles/',
32    );
33
34    private const DEFAULT_INCLUDE = array(
35        '/lib/tpl/',
36        '/lib/plugins/',
37    );
38
39    private const DEFAULT_EXCLUDE = array(
40        '/css/admin/',
41        '/lib/plugins/dw2pdf/',
42        '/lib/plugins/box2/',
43        '/lib/plugins/config/',
44        '/lib/plugins/uparrow/',
45        '/lib/plugins/imagebox/',
46        '/lib/plugins/imagereference/',
47
48        '/lib/plugins/searchindex/',
49        '/lib/plugins/poll/',
50        '/lib/plugins/cloud/',
51        '/lib/plugins/sync/',
52        '/lib/plugins/wrap/',
53        '/lib/plugins/blog/',
54        '/lib/plugins/batchedit/',
55        '/lib/plugins/usermanager/',
56        '/lib/plugins/upgrade/',
57        '/lib/plugins/imagebox/',
58        '/lib/plugins/extension/',
59        '/lib/plugins/tagsections/',
60        '/lib/plugins/javadoc/',
61        '/lib/plugins/toctweak/',
62        '/lib/plugins/move/',
63        '/lib/plugins/acl/',
64        '/lib/plugins/multiorphan/',
65        '/lib/plugins/edittable/',
66        '/lib/plugins/sectionedit/',
67        '/lib/plugins/styling/',
68        '/lib/plugins/tag/',
69/*
70        'lib/scripts/',
71        'lib/styles/',
72        'conf/userall',
73*/
74    );
75
76    private const templateStyles = null;
77
78    /**
79     * Registers a callback function for a given event
80     *
81     * @param Doku_Event_Handler $controller DokuWiki's event controller object
82     * @return void
83     */
84    public function register(Doku_Event_Handler $controller) {
85        global $INPUT;
86        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handle_tpl_metaheader_output');
87        $controller->register_hook('CSS_STYLES_INCLUDED', 'BEFORE', $this, 'handle_css_styles');
88
89        $controller->register_hook('CSS_CACHE_USE', 'BEFORE', $this, 'handle_use_cache');
90    }
91
92    /**
93     * Insert an extra script tag for users that have AUTH_EDIT or better
94     *
95     * @param Doku_Event $event  event object by reference
96     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
97     *                           handler was registered]
98     * @return void
99     */
100    public function handle_tpl_metaheader_output(Doku_Event &$event, $param) {
101        global $ID, $updateVersion, $conf;
102
103        // add css if user has better auth than AUTH_EDIT
104        if ( auth_quickaclcheck( $ID ) >= AUTH_EDIT ) {
105            $tseed = $updateVersion;
106            $tseed = md5($tseed.'admin');
107            $event->data['link'][] = array(
108                'rel' => 'stylesheet', 'type'=> 'text/css',
109                'href'=> DOKU_BASE.'lib/exe/css.php?t='.rawurlencode($conf['template']).'&f=admin&tseed='.$tseed
110            );
111        }
112    }
113
114    /**
115     * This function serves debugging purposes and has to be enabled in the register phase
116     *
117     * @param Doku_Event $event  event object by reference
118     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
119     *                           handler was registered]
120     * @return void
121     */
122    public function handle_use_cache(Doku_Event &$event, $param) {
123        global $INPUT;
124
125        // We need different keys for each style sheet.
126        $event->data->key .= $INPUT->str('f', 'style');
127        $event->data->cache = getCacheName( $event->data->key, $event->data->ext );
128
129        return true;
130    }
131
132    /**
133     * Finally, handle the JS script list. The script would be fit to do even more stuff / types
134     * but handles only admin and default currently.
135     *
136     * @param Doku_Event $event  event object by reference
137     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
138     *                           handler was registered]
139     * @return void
140     */
141    public function handle_css_styles(Doku_Event &$event, $param) {
142        global $INPUT;
143
144        $this->setupStyles();
145
146        switch( $event->data['mediatype'] ) {
147
148            case 'print':
149            case 'screen':
150            case 'all':
151                // Filter for user styles
152                $allowed = array_filter( array_keys($event->data['files']), array($this, 'filter_css') );
153                $event->data['files'] = array_intersect_key($event->data['files'], array_flip($allowed));
154                //$event->data['encapsulate'] = $INPUT->str('f', 'style') != 'admin';
155                break;
156
157            case 'speech':
158            case 'DW_DEFAULT':
159                $event->preventDefault();
160                break;
161        }
162    }
163
164    /**
165     * A simple filter function to check the input string against a list of path-parts that are allowed
166     *
167     * @param string    $str   the script file to check against the list
168     * @param mixed     $list  the list of path parts to test
169     * @return boolean
170     */
171    private function includeFilter( $str, $list ) {
172        foreach( $list as $entry ) {
173            if ( strpos( $str, $entry ) ) return true;
174        }
175
176        return false;
177    }
178
179    /**
180     * A simple filter function to check the input string against a list of path-parts that are allowed
181     * Is the inversion of includeFilter( $str, $list )
182     *
183     * @param string    $str   the script file to check against the list
184     * @param mixed     $list  the list of path parts to test
185     * @return boolean
186     */
187    private function excludeFilter( $str, $list ) {
188        return !$this->includeFilter( $str, $list );
189    }
190
191    /**
192     * Filters scripts that are intended for admins only
193     *
194     * @param string    $script   the script file to check against the list
195     * @return boolean
196     */
197    private function filter_css( $script ) {
198        global $INPUT;
199        if ( $INPUT->str('f', 'style') == 'admin' ) {
200            return $this->includeFilter( $script, $this->templateStyles['admin'] );
201        } else {
202            return $this->includeFilter( $script, $this->templateStyles['include']) && $this->excludeFilter( $script, $this->templateStyles['exclude']);
203        }
204    }
205
206    private function setupStyles() {
207        global $CONF;
208        global $INPUT;
209
210        if ( !is_null( $this->templateStyles) ) {
211            return;
212        }
213
214        $this->templateStyles = array(
215            'admin' => action_plugin_lightweightcss::DEFAULT_ADMIN_INCLUDE,
216            'include' => action_plugin_lightweightcss::DEFAULT_INCLUDE,
217            'exclude' => action_plugin_lightweightcss::DEFAULT_EXCLUDE
218        );
219
220        $tpl = $INPUT->str('t', $conf['template']);
221        $inifile = DOKU_INC . 'lib/tpl/' . $tpl . '/style.ini';
222
223        if (!file_exists($inifile)) {
224            return;
225        }
226
227        $styleini = parse_ini_file($inifile, true);
228        if ( array_key_exists('lightweightcss', $styleini) ) {
229
230            foreach( $styleini['lightweightcss'] as $file => $mode ) {
231
232                switch( $mode ) {
233                    case 'admin':
234                    case 'include':
235                    case 'exclude':
236                        break;
237                    default:
238                        continue 2;
239                }
240
241                if ( !array_key_exists($mode, $this->templateStyles) ) {
242                    $this->templateStyles[$mode] = array();
243                }
244
245                array_push( $this->templateStyles[$mode], $file);
246                $this->templateStyles[$mode] = array_unique( $this->templateStyles[$mode] );
247            }
248        }
249    }
250}
251
252// vim:ts=4:sw=4:et:
253