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