1<?php
2
3/**
4 * DokuWiki Plugin struct (Action Component)
5 *
6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
7 * @author  Andreas Gohr, Michael Große <dokuwiki@cosmocode.de>
8 */
9
10use dokuwiki\Extension\ActionPlugin;
11use dokuwiki\Extension\EventHandler;
12use dokuwiki\Extension\Event;
13use dokuwiki\plugin\struct\meta\AccessTable;
14use dokuwiki\plugin\struct\meta\AccessTableGlobal;
15use dokuwiki\plugin\struct\meta\AggregationEditorTable;
16use dokuwiki\plugin\struct\meta\Column;
17use dokuwiki\plugin\struct\meta\Schema;
18use dokuwiki\plugin\struct\meta\SearchConfig;
19use dokuwiki\plugin\struct\meta\StructException;
20use dokuwiki\plugin\struct\meta\Value;
21
22/**
23 * Class action_plugin_struct_lookup
24 *
25 * Handle global and serial data table editing
26 */
27class action_plugin_struct_aggregationeditor extends ActionPlugin
28{
29    /** @var  Column */
30    protected $column;
31
32    /** @var string */
33    protected $pid = '';
34
35    /** @var int */
36    protected $rid = 0;
37
38    /**
39     * Registers a callback function for a given event
40     *
41     * @param EventHandler $controller DokuWiki's event controller object
42     * @return void
43     */
44    public function register(EventHandler $controller)
45    {
46        $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'addJsinfo');
47        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjax');
48    }
49
50    /**
51     * Add user's permissions to JSINFO
52     *
53     * @param Event $event
54     */
55    public function addJsinfo(Event $event)
56    {
57        global $ID;
58        global $JSINFO;
59        $JSINFO['plugins']['struct']['isPageEditor'] = auth_quickaclcheck($ID) >= AUTH_EDIT;
60    }
61
62
63    /**
64     * @param Event $event
65     */
66    public function handleAjax(Event $event)
67    {
68        $len = strlen('plugin_struct_aggregationeditor_');
69        if (substr($event->data, 0, $len) != 'plugin_struct_aggregationeditor_') {
70            return;
71        }
72        $event->preventDefault();
73        $event->stopPropagation();
74
75        try {
76            if (substr($event->data, $len) == 'new') {
77                $this->newRowEditor();
78            }
79
80            if (substr($event->data, $len) == 'save') {
81                $this->saveRow();
82            }
83
84            if (substr($event->data, $len) == 'delete') {
85                $this->deleteRow();
86            }
87        } catch (StructException $e) {
88            http_status(500);
89            header('Content-Type: text/plain');
90            echo $e->getMessage();
91        }
92    }
93
94    /**
95     * Deletes a row
96     */
97    protected function deleteRow()
98    {
99        global $INPUT;
100        $tablename = $INPUT->str('schema');
101        if (!$tablename) {
102            throw new StructException('No schema given');
103        }
104
105        $this->rid = $INPUT->int('rid');
106        $this->validate();
107
108        action_plugin_struct_inline::checkCSRF();
109
110        $access = $this->getAccess($tablename);
111        if (!$access->getSchema()->isEditable()) {
112            throw new StructException('lookup delete error: no permission for schema');
113        }
114        $access->clearData();
115    }
116
117    /**
118     * Save one new row
119     */
120    protected function saveRow()
121    {
122        global $INPUT;
123        $tablename = $INPUT->str('schema');
124        $data = $INPUT->arr('entry');
125        $this->pid = $INPUT->str('pid');
126        action_plugin_struct_inline::checkCSRF();
127
128        // create a new row based on the original aggregation config
129        $access = $this->getAccess($tablename);
130
131        /** @var helper_plugin_struct $helper */
132        $helper = plugin_load('helper', 'struct');
133        $helper->saveLookupData($access, $data);
134
135        $config = json_decode($INPUT->str('searchconf'), true, 512, JSON_THROW_ON_ERROR);
136        // update row id
137        $this->rid = $access->getRid();
138        $config = $this->addTypeFilter($config);
139
140        $editorTable = new AggregationEditorTable(
141            $this->pid,
142            'xhtml',
143            new Doku_Renderer_xhtml(),
144            new SearchConfig($config)
145        );
146
147        echo $editorTable->getFirstRow();
148    }
149
150    /**
151     * Create the Editor for a new row
152     */
153    protected function newRowEditor()
154    {
155        global $INPUT;
156        global $lang;
157
158        $searchconf = $INPUT->arr('searchconf');
159        $tablename = $searchconf['schemas'][0][0];
160
161        $schema = new Schema($tablename);
162        if (!$schema->isEditable()) {
163            return;
164        } // no permissions, no editor
165        // separate check for serial data in JS
166
167        echo '<div class="struct_entry_form">';
168        echo '<fieldset>';
169        echo '<legend>' . $this->getLang('lookup new entry') . '</legend>';
170        /** @var action_plugin_struct_edit $edit */
171        $edit = plugin_load('action', 'struct_edit');
172
173        // filter columns based on searchconf cols from syntax
174        $columns = $this->resolveColumns($searchconf, $schema);
175
176        foreach ($columns as $column) {
177            $label = $column->getLabel();
178            $field = new Value($column, '');
179            echo $edit->makeField($field, "entry[$label]");
180        }
181        formSecurityToken(); // csrf protection
182        echo '<input type="hidden" name="call" value="plugin_struct_aggregationeditor_save" />';
183        echo '<input type="hidden" name="schema" value="' . hsc($tablename) . '" />';
184
185        echo '<button type="submit">' . $lang['btn_save'] . '</button>';
186
187        echo '<div class="err"></div>';
188        echo '</fieldset>';
189        echo '</div>';
190    }
191
192    /**
193     * Names of columns in the new entry editor: either all,
194     * or the selection defined in config. If config contains '*',
195     * just return the full set.
196     *
197     * @param array $searchconf
198     * @param Schema $schema
199     * @return array
200     */
201    protected function resolveColumns($searchconf, $schema)
202    {
203        // if no valid column config, return all columns
204        if (
205            empty($searchconf['cols']) ||
206            !is_array($searchconf['cols']) ||
207            in_array('*', $searchconf['cols'])
208        ) {
209            return $schema->getColumns(false);
210        }
211
212        $columns = [];
213        foreach ($searchconf['cols'] as $col) {
214            $columns[] = $schema->findColumn($col);
215        }
216        // filter invalid columns (where findColumn() returned false)
217        return array_filter($columns);
218    }
219
220    /**
221     * Returns data accessor
222     *
223     * @param string $tablename
224     * @return AccessTableGlobal
225     */
226    protected function getAccess($tablename)
227    {
228        if ($this->pid) {
229            return AccessTable::getSerialAccess($tablename, $this->pid, $this->rid);
230        }
231        return AccessTable::getGlobalAccess($tablename, $this->rid);
232    }
233
234    /**
235     * Adds filter to search config to differentiate data types
236     *
237     * @param array $config
238     * @return array
239     */
240    protected function addTypeFilter($config)
241    {
242        $config['filter'][] = ['%rowid%', '=', $this->rid, 'AND'];
243        if ($this->pid) {
244            $config['filter'][] = ['%pageid%', '=', $this->pid, 'AND'];
245        }
246        return $config;
247    }
248
249    /**
250     * Throws an exception if data is invalid
251     */
252    protected function validate()
253    {
254        if (!$this->rid) {
255            throw new StructException('No row id given');
256        }
257    }
258}
259