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