1<?php
2/**
3 * PyCode plugin: it embeds a Python script hosted in a remote repository.
4 *
5 * action.php: it defines all the methods used by PyCode plugin
6 *      who interact with DokuWiki's events.
7 *
8 * @author Torpedo <dgtorpedo@gmail.com>
9 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
10 * @package action
11 */
12
13if (!defined('DOKU_INC')) die();  // the plugin must be run within Dokuwiki
14
15require_once "method.php";  // common methods used by PyCode plugin
16
17/**
18 * This class defines all the methods used by the PyCode plugin to interact
19 * with the DokuWiki's events.
20 *
21 * It extends DokuWiki's basic action defined in lib/plugins/action.php.
22 *
23 * @package action_pycode
24 */
25class action_plugin_pycode extends DokuWiki_Action_Plugin {
26
27    /**
28     * Constructor method for class suitable for any initialization.
29     */
30    public function __construct() {
31        $this->mpp = new method_pycode_plugin;
32    }
33
34    /**
35     * Here are registered the event handlers
36     */
37    public function register(Doku_Event_Handler $controller) {
38        $controller->register_hook("ACTION_ACT_PREPROCESS", "BEFORE", $this, "update_code", array ());
39        $controller->register_hook("INDEXER_PAGE_ADD", "BEFORE",  $this, "add_code", array());
40        $controller->register_hook("DOKUWIKI_DONE", "BEFORE", $this, "update_log", array ());
41        $controller->register_hook("TOOLBAR_DEFINE", "AFTER", $this, "insert_button", array ());
42    }
43
44    /**
45     * It substitutes, in the local copy of <file>, only the pice of code which
46     * the user wants to update.
47     *
48     * After that it's necessary to refresh the page, but, since it's used
49     * the function header() to reload the page it's important to call this
50     * method before any actual output is sent.
51     * So it's necessary to use ACTION_ACT_PREPROCESS.
52     *
53     * In DokuWiki, instead of use $_POST, is strongly recommended to access
54     * to it using its input classes:
55     *      $_POST["foo"]; becomes $INPUT->post->str("foo");
56     *
57     * @param (obj) $event the event object
58     * @param (arr) $param data passed when this handler was registered
59     */
60    public function update_code(Doku_Event $event, $param) {
61        global $INPUT;
62
63        if ($INPUT->post->str("submit") == "Ok") {
64            $loc_url = $INPUT->post->str("url");
65            $code_new = unserialize(base64_decode($INPUT->post->str("new")));
66            $ln_s = $INPUT->post->str("start");
67            $ln_e = $INPUT->post->str("end");
68            list($code_all_old, $flag, $name, $subname) = $this->mpp->_get_code($loc_url);
69
70            $ls = $ln_s - 1;
71            $le = $ln_e - 1;
72            $ln = $le - $ls + 1;
73            array_splice($code_all_old, $ls, $ln, $code_new);
74            $this->mpp->_save_code($loc_url, $code_all_old);
75
76            $uri = rtrim(DOKU_URL, "/") . $_SERVER['REQUEST_URI'];
77            header("Location: " . $uri);
78            exit;
79        }
80    }
81
82    /**
83     * It adds the code to the page which has to be indexed.
84     *
85     * Since the code is embedded and not write whitin the page, normally,
86     * it is not parsed by the search engine.
87     * So it's necessary to append the code to the page.
88     *
89     * @param (obj) $event the event object
90     * @param (arr) $param data passed when this handler was registered
91     */
92    public function add_code(Doku_Event $event, $param) {
93        $log = DOKU_PLUGIN . "pycode/tmp/logfile";
94        $log = json_decode(file_get_contents($log), true);
95
96        foreach ($log as $page => $files) {
97            $page = substr($page, 0, -4);  // remove extension .txt
98            $page = str_replace("/", ":", $page);
99            if ($event->data["page"] == $page) {
100                $event->data["body"] = p_cached_output(wikiFN($page));
101            }
102        }
103    }
104
105    /**
106     * It records in a logfile all <file>(s) embedded in each wiki page.
107     *
108     * For each wiki page are listed all the <file>(s) embedded (same <file> is
109     * listed only one time) so that one of them is no longer used, the relative
110     * local copy can be safely deleted.
111     *
112     * During the creation of a wiki page, the list of <file>(s) is saved in a
113     * temporary logfile.tmp.
114     * When the page is created, logfile.tmp is used to update a permanent
115     * logfile.
116     * Same <file> in the same wiki page are listed only once.
117     *
118     * This function have to be called after the wiki page is created,
119     * so it's necessary to use an event like DOKUWIKI_DONE.
120     *
121     * @param (obj) $event the event object
122     * @param (arr) $param data passed when this handler was registered
123     */
124    public function update_log(Doku_Event $event, $param) {
125        global $ACT;
126        global $INFO;
127        $wiki = str_replace(DOKU_INC . "data/pages/", "", $INFO["filepath"]);
128        $tmp = DOKU_PLUGIN . "pycode/tmp/";
129        $log_old = DOKU_PLUGIN . "pycode/tmp/logfile";
130        $log_new = DOKU_PLUGIN . "pycode/tmp/logfile.tmp";
131
132        if ($ACT == "show") {
133            if (file_exists($log_old) == true) {
134                // recover data stored in the logfile so we have sth. like this:
135                // array {
136                // ["<wiki-pg>1"] => array {
137                //                   [0] => "<file>1",
138                //                   [1] => "<file>2"
139                //                   }
140                // }
141                $log_old = json_decode(file_get_contents($log_old), true);
142                if (file_exists($log_new) == true) {
143                    // recover data stored in the temporary logfile
144                    $log_new = json_decode(file_get_contents($log_new), true);
145                    if (array_key_exists($wiki, $log_old) == false) {
146                        // all <file>(s) added to this wiki page are new
147                        $log_old[$wiki] = array();
148                        foreach ($log_new[$wiki] as $file) {
149                            array_push($log_old[$wiki], $file);
150                        }
151                    }
152                    else {
153                        // look for new <file>(s) not present among old <file>(s)
154                        $add = array_diff($log_new[$wiki], $log_old[$wiki]);
155
156                        // add new entry <file>(s) in the logfile
157                        foreach ($add as $file_add) {
158                            array_push($log_old[$wiki], $file_add);
159                        }
160
161                        // look for old <file>(s) not present among new <file>(s)
162                        $del = array_diff($log_old[$wiki], $log_new[$wiki]);
163
164                        // check among all wiki pages if these <file>(s) deleted were
165                        // the last ones and, if so, remove them from the logfile
166                        // and the relatives <file>(s)
167                        foreach ($del as $key => $file_del) {
168                            $i = 0;
169                            foreach ($log_old as $page => $files) {
170                                if ($wiki == $page) {
171                                    continue;
172                                }
173                                if (array_search($file_del, $files) !== false) {
174                                    $i = 1;  // <file> is still embedded in another wiki page
175                                }
176                            }
177                            if ($i == 0) {
178                                unlink(DOKU_PLUGIN . "pycode/tmp/" . $file_del);
179                                unset($log_old[$wiki][$key]);
180                                $log_old[$wiki] = array_values($log_old[$wiki]);  // reindex
181                            }
182                        }
183                    }
184                }
185                else {
186                    // all <file>(s) deleted from this wiki page are old
187                    $del = $log_old[$wiki];
188                    if (empty($del) == false) {
189                        foreach ($del as $key => $file_del) {
190                            unlink(DOKU_PLUGIN . "pycode/tmp/" . $file_del);
191                        }
192                        unset($log_old[$wiki]);
193                    }
194                }
195
196                if (count($log_old) == 0) {
197                    unlink(DOKU_PLUGIN . "pycode/tmp/logfile");
198                }
199                else {
200                    // now the array for logfile is update and ready to be written
201                    $str = json_encode($log_old);
202                    file_put_contents(DOKU_PLUGIN . "pycode/tmp/logfile", $str);
203                }
204            }
205            elseif (file_exists($log_old) == false and file_exists($log_new) == true) {
206                rename($log_new, $log_old);
207            }
208
209            if (file_exists($tmp) == true) {
210                // remove empty directories
211                $tree_dir = $this->mpp->_get_tree_dir(DOKU_PLUGIN . "pycode/tmp/");
212                $this->mpp->_remove_empty_dir($tree_dir);
213
214                // destroy temporary logfile
215                if (file_exists(DOKU_PLUGIN . "pycode/tmp/logfile.tmp") == true) {
216                    unlink(DOKU_PLUGIN . "pycode/tmp/logfile.tmp");
217                }
218            }
219        }
220    }
221
222    /**
223     * It inserts the PyCode's buttons in the toolbar.
224     *
225     * @param (obj) $event the event object
226     * @param (arr) $param data passed when this handler was registered
227     */
228    public function insert_button(Doku_Event $event, $param) {
229        $btns = $this->getConf("btns");
230
231        if ($btns == 1){
232            $event->data[] = array (
233                'type' => "PyCode",
234                'title' => "PyCode Plugin",
235                'icon' => "../../plugins/pycode/images/picker.png"
236            );
237        }
238    }
239}
240