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\AccessTable; 13use dokuwiki\plugin\struct\meta\Assignments; 14use dokuwiki\plugin\struct\meta\AccessTableData; 15use dokuwiki\plugin\struct\meta\Validator; 16use dokuwiki\plugin\struct\meta\Value; 17 18/** 19 * Class action_plugin_struct_entry 20 * 21 * Handles the whole struct data entry process 22 */ 23class action_plugin_struct_entry extends DokuWiki_Action_Plugin { 24 25 /** 26 * @var string The form name we use to transfer schema data 27 */ 28 protected static $VAR = 'struct_schema_data'; 29 30 /** @var helper_plugin_sqlite */ 31 protected $sqlite; 32 33 /** @var bool has the data been validated correctly? */ 34 protected $validated; 35 36 /** @var array these schemas have changed data and need to be saved */ 37 protected $tosave; 38 39 /** 40 * Registers a callback function for a given event 41 * 42 * @param Doku_Event_Handler $controller DokuWiki's event controller object 43 * @return void 44 */ 45 public function register(Doku_Event_Handler $controller) { 46 // add the struct editor to the edit form; 47 $controller->register_hook('HTML_EDITFORM_OUTPUT', 'BEFORE', $this, 'handle_editform'); 48 // validate data on preview and save; 49 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_validation'); 50 // ensure a page revision is created when struct data changes: 51 $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handle_pagesave_before'); 52 // save struct data after page has been saved: 53 $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after'); 54 } 55 56 /** 57 * Enhance the editing form with structural data editing 58 * 59 * @param Doku_Event $event event object by reference 60 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 61 * handler was registered] 62 * @return bool 63 */ 64 public function handle_editform(Doku_Event $event, $param) { 65 global $ID; 66 67 $assignments = new Assignments(); 68 $tables = $assignments->getPageAssignments($ID); 69 70 $html = ''; 71 foreach($tables as $table) { 72 $html .= $this->createForm($table); 73 } 74 75 /** @var Doku_Form $form */ 76 $form = $event->data; 77 $html = "<div class=\"struct_entry_form\">$html</div>"; 78 $pos = $form->findElementById('wiki__editbar'); // insert the form before the main buttons 79 $form->insertElement($pos, $html); 80 81 return true; 82 } 83 84 /** 85 * Clean up and validate the input data 86 * 87 * @param Doku_Event $event event object by reference 88 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 89 * handler was registered] 90 * @return bool 91 */ 92 public function handle_validation(Doku_Event $event, $param) { 93 global $ID, $INPUT; 94 $act = act_clean($event->data); 95 if(!in_array($act, array('save', 'preview'))) return false; 96 97 // execute the validator 98 $validator = new Validator(); 99 $this->validated = $validator->validate($INPUT->arr(self::$VAR), $ID); 100 $this->tosave = $validator->getChangedSchemas(); 101 $INPUT->post->set(self::$VAR, $validator->getCleanedData()); 102 103 if(!$this->validated) foreach($validator->getErrors() as $error) { 104 msg(hsc($error), -1); 105 } 106 107 // did validation go through? otherwise abort saving 108 if(!$this->validated && $act == 'save') { 109 $event->data = 'edit'; 110 } 111 112 return false; 113 } 114 115 /** 116 * Check if the page has to be changed 117 * 118 * @param Doku_Event $event event object by reference 119 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 120 * handler was registered] 121 * @return bool 122 */ 123 public function handle_pagesave_before(Doku_Event $event, $param) { 124 if($event->data['contentChanged']) return; // will be saved for page changes 125 global $ACT; 126 global $REV; 127 128 if(count($this->tosave) || isset($GLOBALS['struct_plugin_force_page_save'])) { 129 if(trim($event->data['newContent']) === '') { 130 // this happens when a new page is tried to be created with only struct data 131 msg($this->getLang('emptypage'), -1); 132 } else { 133 $event->data['contentChanged'] = true; // save for data changes 134 135 // add a summary 136 if(empty($event->data['summary'])) { 137 $event->data['summary'] = $this->getLang('summary'); 138 } 139 } 140 } else if($ACT == 'revert' && $REV) { 141 // revert actions are not validated, so we need to force changes extra 142 $assignments = new Assignments(); 143 $tosave = $assignments->getPageAssignments($event->data['id']); 144 if(count($tosave)) { 145 $event->data['contentChanged'] = true; // save for data changes 146 } 147 } 148 } 149 150 /** 151 * Save the data 152 * 153 * When this is called, INPUT data has been validated already. On a restore action, the data is 154 * loaded from the database and not validated again. 155 * 156 * @param Doku_Event $event event object by reference 157 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 158 * handler was registered] 159 * @return bool 160 */ 161 public function handle_pagesave_after(Doku_Event $event, $param) { 162 global $INPUT; 163 global $ACT; 164 global $REV; 165 166 $assignments = new Assignments(); 167 168 if($ACT == 'revert' && $REV) { 169 // reversion is a special case, we load the data to restore from DB: 170 $structData = array(); 171 $this->tosave = $assignments->getPageAssignments($event->data['id']); 172 foreach($this->tosave as $table) { 173 $oldData = AccessTable::byTableName($table, $event->data['id'], $REV); 174 $oldData->optionRawValue(true); 175 $structData[$table] = $oldData->getDataArray(); 176 } 177 } else { 178 // data comes from the edit form 179 $structData = $INPUT->arr(self::$VAR); 180 } 181 182 if($event->data['changeType'] == DOKU_CHANGE_TYPE_DELETE && empty($GLOBALS['PLUGIN_MOVE_WORKING'])) { 183 // clear all data on delete unless it's a move operation 184 $tables = $assignments->getPageAssignments($event->data['id']); 185 foreach($tables as $table) { 186 $schemaData = AccessTable::byTableName($table, $event->data['id'], time()); 187 $schemaData->clearData(); 188 } 189 } else { 190 // save the provided data 191 if($this->tosave) foreach($this->tosave as $table) { 192 $schemaData = AccessTable::byTableName($table, $event->data['id'], $event->data['newRevision']); 193 $schemaData->saveData($structData[$table]); 194 195 // make sure this schema is assigned 196 $assignments->assignPageSchema($event->data['id'], $table); 197 } 198 } 199 } 200 201 /** 202 * Create the form to edit schemadata 203 * 204 * @param string $tablename 205 * @return string The HTML for this schema's form 206 */ 207 protected function createForm($tablename) { 208 global $ID; 209 global $REV; 210 global $INPUT; 211 if (auth_quickaclcheck($ID) == AUTH_READ) return ''; 212 if (checklock($ID)) return ''; 213 $schema = AccessTable::byTableName($tablename, $ID, $REV); 214 $schemadata = $schema->getData(); 215 216 $structdata = $INPUT->arr(self::$VAR); 217 if(isset($structdata[$tablename])) { 218 $postdata = $structdata[$tablename]; 219 } else { 220 $postdata = array(); 221 } 222 223 // we need a short, unique identifier to use in the cookie. this should be good enough 224 $schemaid = 'SRCT'.substr(str_replace(array('+', '/'), '', base64_encode(sha1($tablename, true))), 0, 5); 225 $html = '<fieldset data-schema="' . $schemaid . '">'; 226 $html .= '<legend>' . hsc($tablename) . '</legend>'; 227 foreach($schemadata as $field) { 228 $label = $field->getColumn()->getLabel(); 229 if(isset($postdata[$label])) { 230 // posted data trumps stored data 231 $field->setValue($postdata[$label]); 232 } 233 $html .= $this->makeField($field, self::$VAR . "[$tablename][$label]"); 234 } 235 $html .= '</fieldset>'; 236 237 return $html; 238 } 239 240 /** 241 * Create the input field 242 * 243 * @param Value $field 244 * @param String $name field's name 245 * @return string 246 */ 247 public function makeField(Value $field, $name) { 248 $trans = hsc($field->getColumn()->getTranslatedLabel()); 249 $hint = hsc($field->getColumn()->getTranslatedHint()); 250 $class = $hint ? 'hashint' : ''; 251 $colname = $field->getColumn()->getFullQualifiedLabel(); 252 253 $input = $field->getValueEditor($name); 254 255 // we keep all the custom form stuff the field might produce, but hide it 256 if(!$field->getColumn()->isVisibleInEditor()) { 257 $hide = 'style="display:none"'; 258 } else { 259 $hide = ''; 260 } 261 262 $html = ''; 263 $html .= "<label $hide data-column=\"$colname\">"; 264 $html .= "<span class=\"label $class\" title=\"$hint\">$trans</span>"; 265 $html .= "<span class=\"input\">$input</span>"; 266 $html .= '</label>'; 267 268 return $html; 269 } 270} 271 272// vim:ts=4:sw=4:et: 273