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 dokuwiki\plugin\struct\meta\Assignments; 13use dokuwiki\plugin\struct\meta\SchemaData; 14use dokuwiki\plugin\struct\meta\Validator; 15use dokuwiki\plugin\struct\meta\Value; 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_entry_form\">$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 // execute the validator 97 $validator = new Validator(); 98 $this->validated = $validator->validate($INPUT->arr(self::$VAR), $ID); 99 $this->tosave = $validator->getChangedSchemas(); 100 $INPUT->post->set(self::$VAR, $validator->getCleanedData()); 101 102 if(!$this->validated) foreach($validator->getErrors() as $error) { 103 msg(hsc($error), -1); 104 } 105 106 // did validation go through? otherwise abort saving 107 if(!$this->validated && $act == 'save') { 108 $event->data = 'edit'; 109 } 110 111 return false; 112 } 113 114 /** 115 * Check if the page has to be changed 116 * 117 * @param Doku_Event $event event object by reference 118 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 119 * handler was registered] 120 * @return bool 121 */ 122 public function handle_pagesave_before(Doku_Event $event, $param) { 123 if($event->data['contentChanged']) return; // will be saved for page changes 124 global $ACT; 125 global $REV; 126 127 if(count($this->tosave) || isset($GLOBALS['struct_plugin_force_page_save'])) { 128 if(trim($event->data['newContent']) === '') { 129 // this happens when a new page is tried to be created with only struct data 130 msg($this->getLang('emptypage'), -1); 131 } else { 132 $event->data['contentChanged'] = true; // save for data changes 133 134 // add a summary 135 if(empty($event->data['summary'])) { 136 $event->data['summary'] = $this->getLang('summary'); 137 } 138 } 139 } else if($ACT == 'revert' && $REV) { 140 // revert actions are not validated, so we need to force changes extra 141 $assignments = new Assignments(); 142 $tosave = $assignments->getPageAssignments($event->data['id']); 143 if(count($tosave)) { 144 $event->data['contentChanged'] = true; // save for data changes 145 } 146 } 147 } 148 149 /** 150 * Save the data 151 * 152 * When this is called, INPUT data has been validated already. On a restore action, the data is 153 * loaded from the database and not validated again. 154 * 155 * @param Doku_Event $event event object by reference 156 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 157 * handler was registered] 158 * @return bool 159 */ 160 public function handle_pagesave_after(Doku_Event $event, $param) { 161 global $INPUT; 162 global $ACT; 163 global $REV; 164 165 $assignments = new Assignments(); 166 167 if($ACT == 'revert' && $REV) { 168 // reversion is a special case, we load the data to restore from DB: 169 $structData = array(); 170 $this->tosave = $assignments->getPageAssignments($event->data['id']); 171 foreach($this->tosave as $table) { 172 $oldData = new SchemaData($table, $event->data['id'], $REV); 173 $oldData->optionRawValue(true); 174 $structData[$table] = $oldData->getDataArray(); 175 } 176 } else { 177 // data comes from the edit form 178 $structData = $INPUT->arr(self::$VAR); 179 } 180 181 if($event->data['changeType'] == DOKU_CHANGE_TYPE_DELETE && empty($GLOBALS['PLUGIN_MOVE_WORKING'])) { 182 // clear all data on delete unless it's a move operation 183 $tables = $assignments->getPageAssignments($event->data['id']); 184 foreach($tables as $table) { 185 $schemaData = new SchemaData($table, $event->data['id'], time()); 186 $schemaData->clearData(); 187 } 188 } else { 189 // save the provided data 190 if($this->tosave) foreach($this->tosave as $table) { 191 $schemaData = new SchemaData($table, $event->data['id'], $event->data['newRevision']); 192 $schemaData->saveData($structData[$table]); 193 194 // make sure this schema is assigned 195 $assignments->assignPageSchema($event->data['id'], $table); 196 } 197 } 198 } 199 200 /** 201 * Create the form to edit schemadata 202 * 203 * @param string $tablename 204 * @return string The HTML for this schema's form 205 */ 206 protected function createForm($tablename) { 207 global $ID; 208 global $REV; 209 global $INPUT; 210 if (auth_quickaclcheck($ID) == AUTH_READ) return ''; 211 if (checklock($ID)) return ''; 212 $schema = new SchemaData($tablename, $ID, $REV); 213 $schemadata = $schema->getData(); 214 215 $structdata = $INPUT->arr(self::$VAR); 216 if(isset($structdata[$tablename])) { 217 $postdata = $structdata[$tablename]; 218 } else { 219 $postdata = array(); 220 } 221 222 // we need a short, unique identifier to use in the cookie. this should be good enough 223 $schemaid = 'SRCT'.substr(str_replace(array('+', '/'), '', base64_encode(sha1($tablename, true))), 0, 5); 224 $html = '<fieldset data-schema="' . $schemaid . '">'; 225 $html .= '<legend>' . hsc($tablename) . '</legend>'; 226 foreach($schemadata as $field) { 227 $label = $field->getColumn()->getLabel(); 228 if(isset($postdata[$label])) { 229 // posted data trumps stored data 230 $field->setValue($postdata[$label]); 231 } 232 $html .= $this->makeField($field, self::$VAR . "[$tablename][$label]"); 233 } 234 $html .= '</fieldset>'; 235 236 return $html; 237 } 238 239 /** 240 * Create the input field 241 * 242 * @param Value $field 243 * @param String $name field's name 244 * @return string 245 */ 246 public function makeField(Value $field, $name) { 247 $trans = hsc($field->getColumn()->getTranslatedLabel()); 248 $hint = hsc($field->getColumn()->getTranslatedHint()); 249 $class = $hint ? 'hashint' : ''; 250 $colname = $field->getColumn()->getFullQualifiedLabel(); 251 252 $input = $field->getValueEditor($name); 253 254 // we keep all the custom form stuff the field might produce, but hide it 255 if(!$field->getColumn()->isVisibleInEditor()) { 256 $hide = 'style="display:none"'; 257 } else { 258 $hide = ''; 259 } 260 261 $html = ''; 262 $html .= "<label $hide data-column=\"$colname\">"; 263 $html .= "<span class=\"label $class\" title=\"$hint\">$trans</span>"; 264 $html .= "<span class=\"input\">$input</span>"; 265 $html .= '</label>'; 266 267 return $html; 268 } 269} 270 271// vim:ts=4:sw=4:et: 272