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\ValidationResult; 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 ValidationResult[] these schemas are validated and 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 // validate data on preview and save; 46 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_validation'); 47 // ensure a page revision is created when struct data changes: 48 $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handle_pagesave_before'); 49 // save struct data after page has been saved: 50 $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after'); 51 } 52 53 /** 54 * Clean up and validate the input data 55 * 56 * @param Doku_Event $event event object by reference 57 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 58 * handler was registered] 59 * @return bool 60 */ 61 public function handle_validation(Doku_Event $event, $param) { 62 global $ID, $INPUT; 63 $act = act_clean($event->data); 64 if(!in_array($act, array('save', 'preview'))) return false; 65 $this->tosave = array(); 66 67 // run the validation for each assignded schema 68 $input = $INPUT->arr(self::$VAR); 69 $this->validated = true; 70 $assignments = new Assignments(); 71 $tables = $assignments->getPageAssignments($ID); 72 foreach($tables as $table) { 73 $access = AccessTable::byTableName($table, $ID); 74 $validation = $access->getValidator($input[$table]); 75 if(!$validation->validate()) { 76 $this->validated = false; 77 foreach($validation->getErrors() as $error) { 78 msg(hsc($error), -1); 79 } 80 } else { 81 if($validation->hasChanges()) { 82 $this->tosave[] = $validation; 83 } 84 } 85 } 86 87 // FIXME we used to set the cleaned data as new input data. this caused #140 88 // could we just not do that, and keep the cleaning to saving only? and fix that bug this way? 89 90 // did validation go through? otherwise abort saving 91 if(!$this->validated && $act == 'save') { 92 $event->data = 'edit'; 93 } 94 95 return true; 96 } 97 98 /** 99 * Check if the page has to be changed 100 * 101 * @param Doku_Event $event event object by reference 102 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 103 * handler was registered] 104 * @return bool 105 */ 106 public function handle_pagesave_before(Doku_Event $event, $param) { 107 if($event->data['contentChanged']) return false; // will be saved for page changes 108 global $ACT; 109 if($ACT == 'revert') return false; // this is handled in revert.php 110 111 if(count($this->tosave) || isset($GLOBALS['struct_plugin_force_page_save'])) { 112 if(trim($event->data['newContent']) === '') { 113 // this happens when a new page is tried to be created with only struct data 114 msg($this->getLang('emptypage'), -1); 115 } else { 116 $event->data['contentChanged'] = true; // save for data changes 117 118 // add a summary 119 if(empty($event->data['summary'])) { 120 $event->data['summary'] = $this->getLang('summary'); 121 } 122 } 123 } 124 125 return true; 126 } 127 128 /** 129 * Save the data 130 * 131 * When this is called, INPUT data has been validated already. 132 * 133 * @param Doku_Event $event event object by reference 134 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 135 * handler was registered] 136 * @return bool 137 */ 138 public function handle_pagesave_after(Doku_Event $event, $param) { 139 global $ACT; 140 if($ACT == 'revert') return false; // handled in revert 141 142 $assignments = new Assignments(); 143 if($event->data['changeType'] == DOKU_CHANGE_TYPE_DELETE && empty($GLOBALS['PLUGIN_MOVE_WORKING'])) { 144 // clear all data on delete unless it's a move operation 145 $tables = $assignments->getPageAssignments($event->data['id']); 146 foreach($tables as $table) { 147 $schemaData = AccessTable::byTableName($table, $event->data['id'], time()); 148 $schemaData->clearData(); 149 } 150 } else { 151 // save the provided data 152 if($this->tosave) foreach($this->tosave as $validation) { 153 $validation->saveData($event->data['newRevision']); 154 155 // make sure this schema is assigned 156 $assignments->assignPageSchema( 157 $event->data['id'], 158 $validation->getAccessTable()->getSchema()->getTable() 159 ); 160 } 161 } 162 return true; 163 } 164 165 /** 166 * Create the form to edit schemadata 167 * 168 * @param string $tablename 169 * @return string The HTML for this schema's form 170 */ 171 protected function createForm($tablename) { 172 global $ID; 173 global $REV; 174 global $INPUT; 175 if(auth_quickaclcheck($ID) == AUTH_READ) return ''; 176 if(checklock($ID)) return ''; 177 $schema = AccessTable::byTableName($tablename, $ID, $REV); 178 $schemadata = $schema->getData(); 179 180 $structdata = $INPUT->arr(self::$VAR); 181 if(isset($structdata[$tablename])) { 182 $postdata = $structdata[$tablename]; 183 } else { 184 $postdata = array(); 185 } 186 187 // we need a short, unique identifier to use in the cookie. this should be good enough 188 $schemaid = 'SRCT' . substr(str_replace(array('+', '/'), '', base64_encode(sha1($tablename, true))), 0, 5); 189 $html = '<fieldset data-schema="' . $schemaid . '">'; 190 $html .= '<legend>' . hsc($tablename) . '</legend>'; 191 foreach($schemadata as $field) { 192 $label = $field->getColumn()->getLabel(); 193 if(isset($postdata[$label])) { 194 // posted data trumps stored data 195 $field->setValue($postdata[$label]); 196 } 197 $html .= $this->makeField($field, self::$VAR . "[$tablename][$label]"); 198 } 199 $html .= '</fieldset>'; 200 201 return $html; 202 } 203 204 /** 205 * Create the input field 206 * 207 * @param Value $field 208 * @param String $name field's name 209 * @return string 210 */ 211 public function makeField(Value $field, $name) { 212 $trans = hsc($field->getColumn()->getTranslatedLabel()); 213 $hint = hsc($field->getColumn()->getTranslatedHint()); 214 $class = $hint ? 'hashint' : ''; 215 $colname = $field->getColumn()->getFullQualifiedLabel(); 216 217 $input = $field->getValueEditor($name); 218 219 // we keep all the custom form stuff the field might produce, but hide it 220 if(!$field->getColumn()->isVisibleInEditor()) { 221 $hide = 'style="display:none"'; 222 } else { 223 $hide = ''; 224 } 225 226 $html = ''; 227 $html .= "<label $hide data-column=\"$colname\">"; 228 $html .= "<span class=\"label $class\" title=\"$hint\">$trans</span>"; 229 $html .= "<span class=\"input\">$input</span>"; 230 $html .= '</label>'; 231 232 return $html; 233 } 234} 235 236// vim:ts=4:sw=4:et: 237