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