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