1<?php 2/** 3 * DokuWiki Plugin struct (Action Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr, Michael Große <dokuwiki@cosmocode.de> 7 */ 8 9// must be run within Dokuwiki 10use dokuwiki\plugin\struct\meta\Column; 11use dokuwiki\plugin\struct\meta\SchemaData; 12use dokuwiki\plugin\struct\meta\StructException; 13use dokuwiki\plugin\struct\meta\Validator; 14 15if(!defined('DOKU_INC')) die(); 16 17/** 18 * Class action_plugin_struct_inline 19 * 20 * Handle inline editing 21 */ 22class action_plugin_struct_inline extends DokuWiki_Action_Plugin { 23 24 /** @var SchemaData */ 25 protected $schemadata = null; 26 27 /** @var Column */ 28 protected $column = null; 29 30 /** @var String */ 31 protected $pid = ''; 32 33 /** 34 * Registers a callback function for a given event 35 * 36 * @param Doku_Event_Handler $controller DokuWiki's event controller object 37 * @return void 38 */ 39 public function register(Doku_Event_Handler $controller) { 40 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax'); 41 } 42 43 /** 44 * @param Doku_Event $event 45 * @param $param 46 */ 47 public function handle_ajax(Doku_Event $event, $param) { 48 $len = strlen('plugin_struct_inline_'); 49 if(substr($event->data, 0, $len) != 'plugin_struct_inline_') return; 50 $event->preventDefault(); 51 $event->stopPropagation(); 52 53 if(substr($event->data, $len) == 'editor') { 54 $this->inline_editor(); 55 } 56 57 if(substr($event->data, $len) == 'save') { 58 try { 59 $this->inline_save(); 60 } catch(StructException $e) { 61 http_status(500); 62 header('Content-Type: text/plain; charset=utf-8'); 63 echo $e->getMessage(); 64 } 65 } 66 67 if(substr($event->data, $len) == 'cancel') { 68 $this->inline_cancel(); 69 } 70 } 71 72 /** 73 * Creates the inline editor 74 */ 75 protected function inline_editor() { 76 // silently fail when editing not possible 77 if(!$this->initFromInput()) return; 78 if(auth_quickaclcheck($this->pid) < AUTH_EDIT) return; 79 if(checklock($this->pid)) return; 80 81 // lock page 82 lock($this->pid); 83 84 // output the editor 85 $value = $this->schemadata->getDataColumn($this->column); 86 echo '<label data-column="'.hsc($this->column->getFullQualifiedLabel()).'">'; 87 echo $value->getValueEditor('entry'); 88 echo '</label>'; 89 $hint = $this->column->getType()->getTranslatedHint(); 90 if($hint) { 91 echo '<div class="hint">'; 92 echo hsc($hint); 93 echo '</div>'; 94 } 95 96 // csrf protection 97 formSecurityToken(); 98 } 99 100 /** 101 * Save the data posted by the inline editor 102 */ 103 protected function inline_save() { 104 global $INPUT; 105 106 if(!$this->initFromInput()) { 107 throw new StructException('inline save error: init'); 108 } 109 // our own implementation of checkSecurityToken because we don't want the msg() call 110 if( 111 $INPUT->server->str('REMOTE_USER') && 112 getSecurityToken() != $INPUT->str('sectok') 113 ) { 114 throw new StructException('inline save error: csrf'); 115 } 116 if(auth_quickaclcheck($this->pid) < AUTH_EDIT) { 117 throw new StructException('inline save error: acl'); 118 } 119 if(checklock($this->pid)) { 120 throw new StructException('inline save error: lock'); 121 } 122 123 // validate 124 $value = $INPUT->param('entry'); 125 $validator = new Validator(); 126 if(!$validator->validateValue($this->column, $value)) { 127 throw new StructException(join("\n", $validator->getErrors())); 128 } 129 130 // current data 131 $tosave = $this->schemadata->getDataArray(); 132 $tosave[$this->column->getLabel()] = $value; 133 $tosave = array($this->schemadata->getTable() => $tosave); 134 135 // save 136 /** @var helper_plugin_struct $helper */ 137 $helper = plugin_load('helper', 'struct'); 138 $helper->saveData($this->pid, $tosave, 'inline edit'); 139 140 // unlock 141 unlock($this->pid); 142 143 // reinit then render 144 $this->initFromInput(); 145 $value = $this->schemadata->getDataColumn($this->column); 146 $R = new Doku_Renderer_xhtml(); 147 $value->render($R, 'xhtml'); // FIXME use configured default renderer 148 echo $R->doc; 149 } 150 151 /** 152 * Unlock a page (on cancel action) 153 */ 154 protected function inline_cancel() { 155 global $INPUT; 156 $pid = $INPUT->str('pid'); 157 unlock($pid); 158 } 159 160 /** 161 * Initialize internal state based on input variables 162 * 163 * @return bool if initialization was successfull 164 */ 165 protected function initFromInput() { 166 global $INPUT; 167 168 $this->schemadata = null; 169 $this->column = null; 170 171 $pid = $INPUT->str('pid'); 172 list($table, $field) = explode('.', $INPUT->str('field')); 173 if(blank($pid)) return false; 174 if(blank($table)) return false; 175 if(blank($field)) return false; 176 177 $this->pid = $pid; 178 179 $this->schemadata = new SchemaData($table, $pid, 0); 180 if(!$this->schemadata->getId()) { 181 $this->schemadata = null; 182 return false; 183 } 184 185 $this->column = $this->schemadata->findColumn($field); 186 if(!$this->column || !$this->column->isVisibleInEditor()) { 187 $this->schemadata = null; 188 $this->column = null; 189 return false; 190 } 191 192 return true; 193 } 194 195} 196