xref: /plugin/struct/action/entry.php (revision 94c9aa4c35772fbb179d28f4aef658deddfcccb4)
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