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