xref: /plugin/struct/action/entry.php (revision ed3de3d67c65b9b61c3e1eeabb225a2664ee001d)
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 plugin\struct\meta\Assignments;
13use plugin\struct\meta\SchemaData;
14use plugin\struct\meta\ValidationException;
15use plugin\struct\meta\Validator;
16use plugin\struct\meta\Value;
17use plugin\struct\types\AbstractBaseType;
18
19/**
20 * Class action_plugin_struct_entry
21 *
22 * Handles the whole struct data entry process
23 */
24class action_plugin_struct_entry extends DokuWiki_Action_Plugin {
25
26    /**
27     * @var string The form name we use to transfer schema data
28     */
29    protected static $VAR = 'struct_schema_data';
30
31    /** @var helper_plugin_sqlite */
32    protected $sqlite;
33
34    /** @var  bool has the data been validated correctly? */
35    protected $validated;
36
37    /** @var  array these schemas have changed data and need to be saved */
38    protected $tosave;
39
40    /**
41     * Registers a callback function for a given event
42     *
43     * @param Doku_Event_Handler $controller DokuWiki's event controller object
44     * @return void
45     */
46    public function register(Doku_Event_Handler $controller) {
47        // add the struct editor to the edit form;
48        $controller->register_hook('HTML_EDITFORM_OUTPUT', 'BEFORE', $this, 'handle_editform');
49        // validate data on preview and save;
50        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_validation');
51        // ensure a page revision is created when struct data changes:
52        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handle_pagesave_before');
53        // save struct data after page has been saved:
54        $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handle_pagesave_after');
55    }
56
57    /**
58     * Enhance the editing form with structural data editing
59     *
60     * @param Doku_Event $event event object by reference
61     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
62     *                           handler was registered]
63     * @return bool
64     */
65    public function handle_editform(Doku_Event $event, $param) {
66        global $ID;
67
68        $assignments = new Assignments();
69        $tables = $assignments->getPageAssignments($ID);
70
71        $html = '';
72        foreach($tables as $table) {
73            $html .= $this->createForm($table);
74        }
75
76        /** @var Doku_Form $form */
77        $form = $event->data;
78        $html = "<div class=\"struct\">$html</div>";
79        $pos = $form->findElementById('wiki__editbar'); // insert the form before the main buttons
80        $form->insertElement($pos, $html);
81
82        return true;
83    }
84
85    /**
86     * Clean up and validate the input data
87     *
88     * @param Doku_Event $event event object by reference
89     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
90     *                           handler was registered]
91     * @return bool
92     */
93    public function handle_validation(Doku_Event $event, $param) {
94        global $ID, $INPUT;
95        $act = act_clean($event->data);
96        if(!in_array($act, array('save', 'preview'))) return false;
97
98        // execute the validator
99        $validator = new Validator();
100        $this->validated = $validator->validate($INPUT->arr(self::$VAR), $ID);
101        $this->tosave = $validator->getChangedSchemas();
102        $INPUT->post->set(self::$VAR, $validator->getCleanedData());
103
104        if(!$this->validated) foreach($validator->getErrors() as $error) {
105            msg(hsc($error), -1);
106        }
107
108        // did validation go through? otherwise abort saving
109        if(!$this->validated && $act == 'save') {
110            $event->data = 'edit';
111        }
112
113        return false;
114    }
115
116    /**
117     * Check if the page has to be changed
118     *
119     * @param Doku_Event $event event object by reference
120     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
121     *                           handler was registered]
122     * @return bool
123     */
124    public function handle_pagesave_before(Doku_Event $event, $param) {
125        if($event->data['contentChanged']) return; // will be saved for page changes
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        }
140    }
141
142    /**
143     * Save the data
144     *
145     * When this is called, INPUT data has been validated already. On a restore action, the data is
146     * loaded from the database and not validated again.
147     *
148     * @param Doku_Event $event event object by reference
149     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
150     *                           handler was registered]
151     * @return bool
152     */
153    public function handle_pagesave_after(Doku_Event $event, $param) {
154        global $INPUT;
155        global $ACT;
156        global $REV;
157
158        $assignments = new Assignments();
159
160        if($ACT == 'revert' && $REV) {
161            // reversion is a special case, we load the data to restore from DB:
162            $structData = array();
163            $this->tosave = $assignments->getPageAssignments($event->data['id']);
164            foreach($this->tosave as $table) {
165                $oldData = new SchemaData($table, $event->data['id'], $REV);
166                $structData[$table] = $oldData->getDataArray();
167            }
168        } else {
169            // data comes from the edit form
170            $structData = $INPUT->arr(self::$VAR);
171        }
172
173        if($event->data['changeType'] == DOKU_CHANGE_TYPE_DELETE && empty($GLOBALS['PLUGIN_MOVE_WORKING'])) {
174            // clear all data on delete unless it's a move operation
175            $tables = $assignments->getPageAssignments($event->data['id']);
176            foreach($tables as $table) {
177                $schemaData = new SchemaData($table, $event->data['id'], time());
178                $schemaData->clearData();
179            }
180        } else {
181            // save the provided data
182            if($this->tosave) foreach($this->tosave as $table) {
183                $schemaData = new SchemaData($table, $event->data['id'], $event->data['newRevision']);
184                $schemaData->saveData($structData[$table]);
185
186                // make sure this schema is assigned
187                $assignments->assignPageSchema($event->data['id'], $table);
188            }
189        }
190    }
191
192    /**
193     * Create the form to edit schemadata
194     *
195     * @param string $tablename
196     * @return string The HTML for this schema's form
197     */
198    protected function createForm($tablename) {
199        global $ID;
200        global $REV;
201        global $INPUT;
202        if (auth_quickaclcheck($ID) == AUTH_READ) return '';
203        if (checklock($ID)) return '';
204        $schema = new SchemaData($tablename, $ID, $REV);
205        $schemadata = $schema->getData();
206
207        $structdata = $INPUT->arr(self::$VAR);
208        if(isset($structdata[$tablename])) {
209            $postdata = $structdata[$tablename];
210        } else {
211            $postdata = array();
212        }
213
214        // we need a short, unique identifier to use in the cookie. this should be good enough
215        $schemaid = 'SRCT'.substr(str_replace(array('+', '/'), '', base64_encode(sha1($tablename, true))), 0, 5);
216        $html = '<fieldset data-schema="' . $schemaid . '">';
217        $html .= '<legend>' . hsc($tablename) . '</legend>';
218        foreach($schemadata as $field) {
219            $label = $field->getColumn()->getLabel();
220            if(isset($postdata[$label])) {
221                // posted data trumps stored data
222                $field->setValue($postdata[$label]);
223            }
224            $html .= self::makeField($field, self::$VAR . "[$tablename][$label]");
225        }
226        $html .= '</fieldset>';
227
228        return $html;
229    }
230
231    /**
232     * Create the input field
233     *
234     * @param Value $field
235     * @param String $name field's name
236     * @return string
237     */
238    static public function makeField(Value $field, $name) {
239        $trans = hsc($field->getColumn()->getTranslatedLabel());
240        $hint  = hsc($field->getColumn()->getTranslatedHint());
241        $class = $hint ? 'hashint' : '';
242        $colname = $field->getColumn()->getFullQualifiedLabel();
243
244        $input = $field->getValueEditor($name);
245
246        // we keep all the custom form stuff the field might produce, but hide it
247        if(!$field->getColumn()->isVisibleInEditor()) {
248            $hide = 'style="display:none"';
249        } else {
250            $hide = '';
251        }
252
253        $html = '';
254        $html .= "<label $hide data-column=\"$colname\">";
255        $html .= "<span class=\"label $class\" title=\"$hint\">$trans</span>";
256        $html .= "<span class=\"input\">$input</span>";
257        $html .= '</label>';
258
259        return $html;
260    }
261}
262
263// vim:ts=4:sw=4:et:
264