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 10if(!defined('DOKU_INC')) die(); 11 12use plugin\struct\meta\Assignments; 13use plugin\struct\meta\SchemaData; 14use plugin\struct\meta\ValidationException; 15use plugin\struct\types\AbstractBaseType; 16 17/** 18 * Class action_plugin_struct_entry 19 * 20 * Handles the whole struct data entry process 21 */ 22class action_plugin_struct_entry extends DokuWiki_Action_Plugin { 23 24 /** 25 * @var string The form name we use to transfer schema data 26 */ 27 protected static $VAR = 'struct_schema_data'; 28 29 /** @var helper_plugin_sqlite */ 30 protected $sqlite; 31 32 /** @var bool has the data been validated correctly? */ 33 protected $validated; 34 35 /** @var array these schemas have changed data and need to be saved */ 36 protected $tosave; 37 38 /** 39 * Registers a callback function for a given event 40 * 41 * @param Doku_Event_Handler $controller DokuWiki's event controller object 42 * @return void 43 */ 44 public function register(Doku_Event_Handler $controller) { 45 // add the struct editor to the edit form; 46 $controller->register_hook('HTML_EDITFORM_OUTPUT', 'BEFORE', $this, 'handle_editform'); 47 // validate data on preview and save; 48 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_validation'); 49 // ensure a page revision is created when struct data changes: 50 $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handle_pagesave_before'); 51 // save struct data after page has been saved: 52 $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after'); 53 } 54 55 /** 56 * Enhance the editing form with structural data editing 57 * 58 * @param Doku_Event $event event object by reference 59 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 60 * handler was registered] 61 * @return bool 62 */ 63 public function handle_editform(Doku_Event $event, $param) { 64 global $ID; 65 66 $assignments = new Assignments(); 67 $tables = $assignments->getPageAssignments($ID); 68 69 $html = ''; 70 foreach($tables as $table) { 71 $html .= $this->createForm($table); 72 } 73 74 /** @var Doku_Form $form */ 75 $form = $event->data; 76 $html = "<div class=\"struct\">$html</div>"; 77 $pos = $form->findElementById('wiki__editbar'); // insert the form before the main buttons 78 $form->insertElement($pos, $html); 79 80 return true; 81 } 82 83 /** 84 * Clean up and validate the input data 85 * 86 * @param Doku_Event $event event object by reference 87 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 88 * handler was registered] 89 * @return bool 90 */ 91 public function handle_validation(Doku_Event $event, $param) { 92 global $ID, $INPUT; 93 $act = act_clean($event->data); 94 if(!in_array($act, array('save', 'preview'))) return false; 95 96 $assignments = new Assignments(); 97 $tables = $assignments->getPageAssignments($ID); 98 $structData = $INPUT->arr(self::$VAR); 99 $timestamp = time(); 100 101 $this->tosave = array(); 102 $this->validated = true; 103 foreach($tables as $table) { 104 $schemaData = new SchemaData($table, $ID, $timestamp); 105 if(!$schemaData->getId()) { 106 // this schema is not available for some reason. skip it 107 continue; 108 } 109 110 $newData = $structData[$table]; 111 foreach($schemaData->getColumns() as $col) { 112 // fix multi value types 113 $type = $col->getType(); 114 $label = $type->getLabel(); 115 $trans = $type->getTranslatedLabel(); 116 if($type->isMulti() && !is_array($newData[$label])) { 117 $newData[$label] = $type->splitValues($newData[$label]); 118 } 119 // strip empty fields from multi vals 120 if(is_array($newData[$label])) { 121 $newData[$label] = array_filter($newData[$label], array($this, 'filter')); 122 $newData[$label] = array_values($newData[$label]); // reset the array keys 123 } 124 125 // validate data 126 $this->validated = $this->validated && $this->validate($type, $trans, $newData[$label]); 127 } 128 129 // has the data changed? mark it for saving. 130 $olddata = $schemaData->getDataArray(); 131 if($olddata != $newData) { 132 $this->tosave[] = $table; 133 } 134 135 // write back cleaned up data 136 $structData[$table] = $newData; 137 } 138 // write back cleaned up structData 139 $INPUT->post->set(self::$VAR, $structData); 140 141 // did validation go through? otherwise abort saving 142 if(!$this->validated && $act == 'save') { 143 $event->data = 'edit'; 144 } 145 146 return false; 147 } 148 149 /** 150 * Check if the page has to be changed 151 * 152 * @param Doku_Event $event event object by reference 153 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 154 * handler was registered] 155 * @return bool 156 */ 157 public function handle_pagesave_before(Doku_Event $event, $param) { 158 if($event->data['contentChanged']) return; // will be saved for page changes 159 if(count($this->tosave)) { 160 if(trim($event->data['newContent']) === '') { 161 // this happens when a new page is tried to be created with only struct data 162 msg($this->getLang('emptypage'), -1); 163 } else { 164 $event->data['contentChanged'] = true; // save for data changes 165 } 166 } 167 } 168 169 /** 170 * Save the data 171 * 172 * When this is called, INPUT data has been validated already. On a restore action, the data is 173 * loaded from the database and not validated again. 174 * 175 * @param Doku_Event $event event object by reference 176 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 177 * handler was registered] 178 * @return bool 179 */ 180 public function handle_pagesave_after(Doku_Event $event, $param) { 181 global $INPUT; 182 global $ACT; 183 global $REV; 184 185 $assignments = new Assignments(); 186 187 if($ACT == 'revert' && $REV) { 188 // reversion is a special case, we load the data to restore from DB: 189 $structData = array(); 190 $this->tosave = $assignments->getPageAssignments($event->data['id']); 191 foreach($this->tosave as $table) { 192 $oldData = new SchemaData($table, $event->data['id'], $REV); 193 $structData[$table] = $oldData->getDataArray(); 194 } 195 } else { 196 // data comes from the edit form 197 $structData = $INPUT->arr(self::$VAR); 198 } 199 200 if($event->data['changeType'] == DOKU_CHANGE_TYPE_DELETE) { 201 // clear all data 202 $tables = $assignments->getPageAssignments($event->data['id']); 203 foreach($tables as $table) { 204 $schemaData = new SchemaData($table, $event->data['id'], time()); 205 $schemaData->clearData(); 206 } 207 } else { 208 // save the provided data 209 foreach($this->tosave as $table) { 210 $schemaData = new SchemaData($table, $event->data['id'], $event->data['newRevision']); 211 $schemaData->saveData($structData[$table]); 212 213 // make sure this schema is assigned 214 $assignments->assignPageSchema($event->data['id'], $table); 215 } 216 } 217 } 218 219 /** 220 * Validate the given data 221 * 222 * Catches the Validation exceptions and transforms them into proper messages. 223 * 224 * Blank values are not validated and always pass 225 * 226 * @param AbstractBaseType $type 227 * @param string $label 228 * @param array|string|int $data 229 * @return bool true if the data validates, otherwise false 230 */ 231 protected function validate(AbstractBaseType $type, $label, $data) { 232 $prefix = sprintf($this->getLang('validation_prefix'), $label); 233 234 $ok = true; 235 if(is_array($data)) { 236 foreach($data as $value) { 237 if(!blank($value)) { 238 try { 239 $type->validate($value); 240 } catch(ValidationException $e) { 241 msg($prefix . $e->getMessage(), -1); 242 $ok = false; 243 } 244 } 245 } 246 return $ok; 247 } 248 249 if(!blank($data)) { 250 try { 251 $type->validate($data); 252 } catch(ValidationException $e) { 253 msg($prefix . $e->getMessage(), -1); 254 $ok = false; 255 } 256 } 257 return $ok; 258 } 259 260 /** 261 * Create the form to edit schemadata 262 * 263 * @param string $tablename 264 * @return string The HTML for this schema's form 265 */ 266 protected function createForm($tablename) { 267 global $ID; 268 global $REV; 269 global $INPUT; 270 $schema = new SchemaData($tablename, $ID, $REV); 271 $schemadata = $schema->getData(); 272 273 $structdata = $INPUT->arr(self::$VAR); 274 if(isset($structdata[$tablename])) { 275 $postdata = $structdata[$tablename]; 276 } else { 277 $postdata = array(); 278 } 279 280 $html = '<fieldset>'; 281 $html .= '<legend>' . hsc($tablename) . '</legend>'; 282 foreach($schemadata as $field) { 283 $label = $field->getColumn()->getLabel(); 284 if(isset($postdata[$label])) { 285 // posted data trumps stored data 286 $field->setValue($postdata[$label]); 287 } 288 $trans = hsc($field->getColumn()->getTranslatedLabel()); 289 $name = self::$VAR . "[$tablename][$label]"; 290 $input = $field->getValueEditor($name); 291 $html .= "<label><span class=\"label\">$trans</span><span class=\"input\">$input</span></label>"; 292 } 293 $html .= '</fieldset>'; 294 295 return $html; 296 } 297 298 /** 299 * Simple filter to remove blank values 300 * 301 * @param string $val 302 * @return bool 303 */ 304 public function filter($val) { 305 return !blank($val); 306 } 307} 308 309// vim:ts=4:sw=4:et: 310