xref: /plugin/struct/action/entry.php (revision 87dc1344fb5ebba89b6bf5f09fe8e70f0793e751)
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\AccessTableData;
14use dokuwiki\plugin\struct\meta\Assignments;
15use dokuwiki\plugin\struct\meta\ValidationResult;
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  ValidationResult[]|string[] these schemas are validated and 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        // validate data on preview and save;
47        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_validation');
48        // ensure a page revision is created when struct data changes:
49        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handle_pagesave_before');
50        // save struct data after page has been saved:
51        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after');
52    }
53
54    /**
55     * Clean up and validate the input data
56     *
57     * @param Doku_Event $event event object by reference
58     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
59     *                           handler was registered]
60     * @return bool
61     */
62    public function handle_validation(Doku_Event $event, $param) {
63        global $ID, $INPUT;
64        $act = act_clean($event->data);
65        if(!in_array($act, array('save', 'preview'))) return false;
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 = new AccessTableData($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 = new AccessTableData($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