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 
13 if (!defined('DOKU_INC')) die();  // the plugin must be run within Dokuwiki
14 
15 require_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  */
25 class 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