xref: /plugin/struct/action/entry.php (revision 4a2883e0eebc2f861000d0f0f5ebd1b16e25a258)
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\">$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    protected 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