xref: /plugin/struct/action/inline.php (revision 4ec54c67014d991a72b136c6fcc8d0b06b86462d)
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
10use dokuwiki\plugin\struct\meta\AccessTable;
11use dokuwiki\plugin\struct\meta\Column;
12use dokuwiki\plugin\struct\meta\SchemaData;
13use dokuwiki\plugin\struct\meta\StructException;
14use dokuwiki\plugin\struct\meta\Validator;
15
16if(!defined('DOKU_INC')) die();
17
18/**
19 * Class action_plugin_struct_inline
20 *
21 * Handle inline editing
22 */
23class action_plugin_struct_inline extends DokuWiki_Action_Plugin {
24
25    /** @var  SchemaData */
26    protected $schemadata = null;
27
28    /** @var  Column */
29    protected $column = null;
30
31    /** @var String */
32    protected $pid = '';
33
34    /**
35     * Registers a callback function for a given event
36     *
37     * @param Doku_Event_Handler $controller DokuWiki's event controller object
38     * @return void
39     */
40    public function register(Doku_Event_Handler $controller) {
41        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax');
42    }
43
44    /**
45     * @param Doku_Event $event
46     * @param $param
47     */
48    public function handle_ajax(Doku_Event $event, $param) {
49        $len = strlen('plugin_struct_inline_');
50        if(substr($event->data, 0, $len) != 'plugin_struct_inline_') return;
51        $event->preventDefault();
52        $event->stopPropagation();
53
54        if(substr($event->data, $len) == 'editor') {
55            $this->inline_editor();
56        }
57
58        if(substr($event->data, $len) == 'save') {
59            try {
60                $this->inline_save();
61            } catch(StructException $e) {
62                http_status(500);
63                header('Content-Type: text/plain; charset=utf-8');
64                echo $e->getMessage();
65            }
66        }
67
68        if(substr($event->data, $len) == 'cancel') {
69            $this->inline_cancel();
70        }
71    }
72
73    /**
74     * Creates the inline editor
75     */
76    protected function inline_editor() {
77        // silently fail when editing not possible
78        if(!$this->initFromInput()) return;
79        if(auth_quickaclcheck($this->pid) < AUTH_EDIT) return;
80        if(checklock($this->pid)) return;
81
82        // lock page
83        lock($this->pid);
84
85        // output the editor
86        $value = $this->schemadata->getDataColumn($this->column);
87        echo '<label data-column="'.hsc($this->column->getFullQualifiedLabel()).'">';
88        echo $value->getValueEditor('entry');
89        echo '</label>';
90        $hint = $this->column->getType()->getTranslatedHint();
91        if($hint) {
92            echo '<div class="hint">';
93            echo hsc($hint);
94            echo '</div>';
95        }
96
97        // csrf protection
98        formSecurityToken();
99    }
100
101    /**
102     * Save the data posted by the inline editor
103     */
104    protected function inline_save() {
105        global $INPUT;
106
107        if(!$this->initFromInput()) {
108            throw new StructException('inline save error: init');
109        }
110        // our own implementation of checkSecurityToken because we don't want the msg() call
111        if(
112            $INPUT->server->str('REMOTE_USER') &&
113            getSecurityToken() != $INPUT->str('sectok')
114        ) {
115            throw new StructException('inline save error: csrf');
116        }
117        if(auth_quickaclcheck($this->pid) < AUTH_EDIT) {
118            throw new StructException('inline save error: acl');
119        }
120        if(checklock($this->pid)) {
121            throw new StructException('inline save error: lock');
122        }
123
124        // validate
125        $value = $INPUT->param('entry');
126        $validator = new Validator();
127        if(!$validator->validateValue($this->column, $value)) {
128            throw new StructException(join("\n", $validator->getErrors()));
129        }
130
131        // current data
132        $tosave = $this->schemadata->getDataArray();
133        $tosave[$this->column->getLabel()] = $value;
134        $tosave = array($this->schemadata->getSchema()->getTable() => $tosave);
135
136        // save
137        /** @var helper_plugin_struct $helper */
138        $helper = plugin_load('helper', 'struct');
139        $helper->saveData($this->pid, $tosave, 'inline edit');
140
141        // unlock
142        unlock($this->pid);
143
144        // reinit then render
145        $this->initFromInput();
146        $value = $this->schemadata->getDataColumn($this->column);
147        $R = new Doku_Renderer_xhtml();
148        $value->render($R, 'xhtml'); // FIXME use configured default renderer
149        echo $R->doc;
150    }
151
152    /**
153     * Unlock a page (on cancel action)
154     */
155    protected function inline_cancel() {
156        global $INPUT;
157        $pid = $INPUT->str('pid');
158        unlock($pid);
159    }
160
161    /**
162     * Initialize internal state based on input variables
163     *
164     * @return bool if initialization was successfull
165     */
166    protected function initFromInput() {
167        global $INPUT;
168
169        $this->schemadata = null;
170        $this->column = null;
171
172        $pid = $INPUT->str('pid');
173        list($table, $field) = explode('.', $INPUT->str('field'));
174        if(blank($pid)) return false;
175        if(blank($table)) return false;
176        if(blank($field)) return false;
177
178        $this->pid = $pid;
179        try {
180            $this->schemadata = AccessTable::byTableName($table, $pid);
181        } catch(StructException $ignore) {
182            return false;
183        }
184
185        $this->column = $this->schemadata->getSchema()->findColumn($field);
186        if(!$this->column || !$this->column->isVisibleInEditor()) {
187            $this->schemadata = null;
188            $this->column = null;
189            return false;
190        }
191
192        return true;
193    }
194
195}
196