1<?php 2/** 3 * DokuWiki Plugin json (Action Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Janez Paternoster <janez.paternoster@siol.net> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) { 11 die(); 12} 13 14//Resolve to absolute page ID 15use dokuwiki\File\PageResolver; 16 17class action_plugin_json extends DokuWiki_Action_Plugin 18{ 19 20 /** 21 * Registers a callback function for a given event 22 * 23 * @param Doku_Event_Handler $controller DokuWiki's event controller object 24 * 25 * @return void 26 */ 27 public function register(Doku_Event_Handler $controller) { 28 $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'handle_jsinfo'); 29 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_unknown'); 30 } 31 32 33 /** 34 * Handle event DOKUWIKI_STARTED - add data to JSINFO 35 * 36 * @param Doku_Event $event event object by reference 37 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 38 * handler was registered] 39 */ 40 public function handle_jsinfo(&$event, $param) { 41 global $JSINFO; 42 global $ID; 43 44 if(page_exists($ID)) { 45 $JSINFO['json_lastmod'] = filemtime(wikiFN($ID)); 46 $JSINFO['enable_ejs'] = $this->getConf('enable_ejs'); 47 } 48 } 49 50 51 /** 52 * Handle event AJAX_CALL_UNKNOWN for json_plugin_save_inline and 53 * json_plugin_archive call 54 * 55 * @param Doku_Event $event event object by reference 56 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 57 * handler was registered] 58 * 59 * @return Ajax response as json 60 */ 61 public function handle_ajax_call_unknown(Doku_Event $event, $param) { 62 if ($event->data === 'json_plugin_save_inline') { 63 $this->ajax_save_inline($event, $param); 64 } 65 else if ($event->data === 'json_plugin_archive') { 66 $this->ajax_archive($event, $param); 67 } 68 } 69 70 private function ajax_save_inline(Doku_Event $event, $param) { 71 //no other ajax call handlers needed 72 $event->stopPropagation(); 73 $event->preventDefault(); 74 75 //access additional request variables 76 global $INPUT; 77 $page_to_modify = $INPUT->str('file'); 78 $json_id = $INPUT->str('id'); 79 $hash_received = $INPUT->str('hash'); 80 $text_received = $INPUT->str('text'); 81 82 $resolver = new PageResolver(''); 83 $page_to_modify = $resolver->resolveId($page_to_modify); 84 85 $err = ''; 86 $hash_new = ''; 87 88 if(!page_exists($page_to_modify)) { 89 $err = sprintf($this->getLang('file_not_found'), $page_to_modify); 90 } 91 //verify file write rights 92 else if(auth_quickaclcheck($page_to_modify) < AUTH_EDIT) { 93 $err = sprintf($this->getLang('permision_denied_write'), $page_to_modify); 94 } 95 //verify id 96 else if(!$json_id) { 97 $err = sprintf($this->getLang('missing_id'), $page_to_modify); 98 } 99 100 //verify lock 101 $locked = false; 102 if($err === '') { 103 if(checklock($page_to_modify)) { 104 $err = sprintf($this->getLang('file_locked'), $page_to_modify); 105 } 106 else { 107 lock($page_to_modify); 108 $locked = true; 109 } 110 } 111 112 //read the file 113 if($err === '') { 114 $file = rawWiki($page_to_modify); 115 if(!$file) { 116 $err = sprintf($this->getLang('file_not_found'), $page_to_modify); 117 } 118 } 119 120 //replace json data; must be one match 121 if($err === '') { 122 $file_updated = preg_replace_callback( 123 '/(<(json[a-z0-9]*)\b[^>]*?id\s*=[\s"\']*'.$json_id.'\b.*?>)(.*?)(<\/\2>)/s', 124 function($matches) use(&$err, $hash_received, $text_received, &$hash_new) { 125 //Make sure, no one changed the original data. Ajax call must know the md5 hash 126 //of the actual data saved inside the file. If someone changed the data between 127 //page reload (or previous ajax call) and last ajax call, then last ajax call 128 //will fail with error message. 129 $hash_original = md5($matches[3]); 130 if($hash_original === $hash_received) { 131 $replacement = $matches[1].$text_received.$matches[4]; 132 $hash_new = md5($text_received); 133 return $replacement; 134 } 135 else { 136 //set error and keep original data 137 $err = 'e'; 138 return $matches[0]; 139 } 140 }, 141 $file, 142 -1, 143 $count 144 ); 145 if($file_updated) { 146 if($count === 0) { 147 $err = sprintf($this->getLang('element_not_found'), $json_id); 148 } 149 else if($count !== 1) { 150 $err = sprintf($this->getLang('duplicated_id'), $json_id); 151 } 152 else if($err === 'e') { 153 $err = sprintf($this->getLang('hash_not_equal'), $json_id); 154 } 155 } 156 else { 157 $err = sprintf($this->getLang('internal_error'), 'plugin/json/action'); 158 } 159 } 160 161 //write file 162 if($err === '') { 163 saveWikiText($page_to_modify, $file_updated, sprintf($this->getLang('json_updated_ajax'), $json_id), true); 164 $response = array('response' => 'OK', 'hash' => $hash_new); 165 } 166 else { 167 $response = array('response' => 'error', 'error' => $err); 168 } 169 170 //unlock for editing 171 if($locked === true) { 172 unlock($page_to_modify); 173 } 174 175 //send response 176 header('Content-Type: application/json'); 177 echo json_encode($response); 178 } 179 180 private function ajax_archive(Doku_Event $event, $param) { 181 //no other ajax call handlers needed 182 $event->stopPropagation(); 183 $event->preventDefault(); 184 185 //access additional request variables 186 global $INPUT; 187 $page_to_modify = $INPUT->str('file'); 188 $resolver = new PageResolver(''); 189 $page_to_modify = $resolver->resolveId($page_to_modify); 190 191 $lastmod_current = filemtime(wikiFN($page_to_modify)); 192 $lastmod_call = $INPUT->str('lastmod'); 193 $data_original = $INPUT->arr('data'); 194 $subdir = $INPUT->str('subdir'); 195 $err = ''; 196 197 if(!page_exists($page_to_modify)) { 198 $err = sprintf($this->getLang('file_not_found'), $page_to_modify); 199 } 200 //verify file write rights 201 else if(auth_quickaclcheck($page_to_modify) < AUTH_EDIT) { 202 $err = sprintf($this->getLang('permision_denied_write'), $page_to_modify); 203 } 204 //verify if page was not modified 205 else if($lastmod_call != $lastmod_current) { 206 $err = sprintf($this->getLang('file_changed'), $page_to_modify); 207 } 208 209 //verify lock 210 $locked = false; 211 if($err === '') { 212 if(checklock($page_to_modify)) { 213 $err = sprintf($this->getLang('file_locked'), $page_to_modify); 214 } 215 else { 216 lock($page_to_modify); 217 $locked = true; 218 } 219 } 220 221 //read the file 222 if($err === '') { 223 $file = rawWiki($page_to_modify); 224 if(!$file) { 225 $err = sprintf($this->getLang('file_not_found'), $page_to_modify); 226 } 227 } 228 229 //replace json data 230 if($err === '') { 231 $counter = 0; 232 $file_updated = preg_replace_callback( 233 '/(<(json[a-z0-9]*)\b[^>]*?)(archive\s*=\s*(["\']?)(make|disable)\4)(.*?>)(.*?<\/\2>)/is', 234 function($matches) use($data_original, &$counter) { 235 //el_1='<jsonxx ...', el_2='achive=make', el_3=' ...>', rest='... </jsonxxx>' 236 list(, $el_1, ,$el_2, ,$make_disable, $el_3, $rest) = $matches; 237 if($make_disable === 'make') { 238 $data = $data_original[$counter]; 239 $data = str_replace('\'', ''', $data); 240 $data = str_replace('<', '<', $data); 241 $data = str_replace('>', '>', $data); 242 $element = $el_1."archive=\n'$data'\n".$el_3; 243 } 244 else { 245 $element = $el_1.'archive_disabled=disable'.$el_3; 246 $element = preg_replace('/\bsrc\s*=/', 'src_disabled=', $element); 247 $element = preg_replace('/\bsrc_ext\s*=/', 'src_ext_disabled=', $element); 248 } 249 $counter++; 250 return $element.$rest; 251 }, 252 $file 253 ); 254 if($counter === 0 || $counter !== count($data_original)) { 255 $err = 'Internal error - number of <json archive=make ...>('.$counter 256 .') is zero or not equal to number of requests('.count($data_original).')'; 257 } 258 } 259 260 if($err === '') { 261 //move the file to the sub directory 262 if ($subdir) { 263 if (str_contains($page_to_modify, ':')) { 264 $newId = preg_replace('/^(.*:)([^:]+)$/', 265 '${1}'.$subdir.':${2}', 266 $page_to_modify); 267 } 268 else { 269 $newId = $subdir.':'.$page_to_modify; 270 } 271 272 if(page_exists($newId)) { 273 $err = sprintf($this->getLang('file_exists'), $newId); 274 } 275 //verify file write rights 276 else if(auth_quickaclcheck($newId) < AUTH_EDIT) { 277 $err = sprintf($this->getLang('permision_denied_write'), $page_to_modify); 278 } 279 //save to the new location and delete the old file 280 else { 281 saveWikiText($newId, $file_updated, $this->getLang('archived')); 282 if(page_exists($newId)) { 283 saveWikiText($page_to_modify, null, $this->getLang('archived')); 284 } 285 } 286 } 287 //only write the file to the current location 288 else { 289 saveWikiText($page_to_modify, $file_updated, $this->getLang('archived')); 290 } 291 } 292 293 if($err === '') { 294 $response = array('response' => 'OK'); 295 } 296 else { 297 $response = array('response' => 'error', 'error' => $err); 298 } 299 300 //unlock for editing 301 if($locked === true) { 302 unlock($page_to_modify); 303 } 304 305 //send response 306 header('Content-Type: application/json'); 307 echo json_encode($response); 308 } 309} 310