1<?php
2/**
3 * Action Plugin stylingpages
4 *
5 * @license    GPL-2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Flávio J. Saraiva <flaviojs2005@gmail.com>
7 */
8
9/**
10 * Allows users to change the css/js files of this plugin with wikitext.
11 *
12 * @see README
13 */
14class action_plugin_stylingpages extends DokuWiki_Action_Plugin
15{
16    /**
17     * Registers callback functions.
18     *
19     * @param Doku_Event_Handler $controller
20     */
21    public function register(Doku_Event_Handler $controller)
22    {
23        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handleSaveBefore', null, 10);
24        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handleSaveAfter', null, 10);
25    }
26
27    /**
28     * Trigger update when there are no changes.
29     */
30    public function handleSaveBefore(Doku_Event $event, $param)
31    {
32        if (!$event->data['contentChanged']) {
33            // the AFTER event is not called
34            $this->tryUpdate($event);
35        }
36    }
37
38    /**
39     * Trigger update when there are changes.
40     */
41    public function handleSaveAfter(Doku_Event $event, $param)
42    {
43        $this->tryUpdate($event);
44    }
45
46    /**
47     * Update files only if there are file patterns for
48     * the page and you are allowed to write in it.
49     */
50    public function tryUpdate(Doku_Event $event)
51    {
52        $page = $event->data['id'];
53
54        // get file patterns of the page
55        $patterns = array();
56        $entries = $this->getConf('page_files');
57        foreach ($entries as $entry) {
58            $parts = explode('=', $entry, 2); // "page=file_pattern"
59            if (count($parts) === 2 && $parts[0] === $page) {
60                $pattern = $parts[1];
61                if (@preg_match($pattern, '') === false) {
62                    msg('Invalid pattern <code>' . hsc($pattern) . '</code>', -1);
63                } else {
64                    $patterns[] = $parts[1];
65                }
66            }
67        }
68
69        // check update requirements
70        if (count($patterns) > 0 && auth_quickaclcheck($page) >= AUTH_WRITE) {
71            $wikitext = $event->data['newContent'];
72            $this->update($patterns, $wikitext);
73        }
74    }
75
76    /**
77     * Update files matching the patterns
78     * with the code blocks in the wikitext.
79     */
80    public function update($patterns, $wikitext)
81    {
82        global $conf;
83        global $config_cascade;
84
85        $dir = DOKU_PLUGIN . 'stylingpages/';
86        $haschanges = false;
87
88        // get file contents from wikitext
89        $contents = array(); // array(file => content)
90        $instructions = p_get_instructions($wikitext);
91        foreach ($instructions as $instruction) {
92            // array('code', array(content, format, file), pos)
93            if ($instruction[0] === 'code' && count($instruction[1]) > 2) {
94                $content = $instruction[1][0];
95                $file = $instruction[1][2];
96                if (isset($contents[$file])) {
97                    $contents[$file] .= $content; // multiple blocks with the same file are appended
98                } else {
99                    $contents[$file] = $content;
100                }
101            }
102        }
103
104        // delete unreferenced files
105        $it = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
106        $it = new RecursiveIteratorIterator($it);
107        $it->rewind();
108        while ($it->valid()) {
109            $file = $it->getSubPathName();
110            $file = str_replace('\\', '/', $file);
111            foreach ($patterns as $pattern) {
112                if (preg_match($pattern, $file) && !isset($contents[$file])) {
113                    $haschanges = true;
114                    $path = $dir . $file;
115                    $msg = $this->getLang('deleting_file');
116                    $msg = str_replace('$1', $file, $msg);
117                    msg($msg);
118                    @unlink($path);
119                }
120            }
121            $it->next();
122        }
123
124        // create or replace referenced files
125        foreach ($contents as $file => $content) {
126            foreach ($patterns as $pattern) {
127                if (preg_match($pattern, $file)) {
128                    $haschanges = true;
129                    $path = $dir . $file;
130                    if (file_exists($path)) {
131                        $msg = $this->getLang('replacing_file');
132                        $msg = str_replace('$1', $file, $msg);
133                        msg($msg);
134                    } else {
135                        $msg = $this->getLang('creating_file');
136                        $msg = str_replace('$1', $file, $msg);
137                        msg($msg);
138                        @mkdir(dirname($path), $conf['dmode'], true);
139                    }
140                    $fh = @fopen($path, 'wb') ;
141                    @fwrite($fh, $content);
142                    @fclose($fh);
143                }
144            }
145        }
146
147        // purge cache (from helper_plugin_extension_extension::purgeCache)
148        if ($haschanges) {
149            @touch(reset($config_cascade['main']['local']));
150        }
151    }
152}
153