1<?php
2
3/**
4 * DokuWiki Plugin struct (Helper 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\AccessDataValidator;
11use dokuwiki\plugin\struct\meta\AccessTable;
12use dokuwiki\plugin\struct\meta\Assignments;
13use dokuwiki\plugin\struct\meta\Schema;
14use dokuwiki\plugin\struct\meta\StructException;
15
16/**
17 * The public interface for the struct plugin
18 *
19 * 3rd party developers should always interact with struct data through this
20 * helper plugin only. If additional interface functionality is needed,
21 * it should be added here.
22 *
23 * All functions will throw StructExceptions when something goes wrong.
24 *
25 * Remember to check permissions yourself!
26 */
27class helper_plugin_struct extends DokuWiki_Plugin
28{
29    /**
30     * Class names of renderers which should NOT render struct data.
31     * All descendants are also blacklisted.
32     */
33    public const BLACKLIST_RENDERER = [
34        'Doku_Renderer_metadata',
35        '\renderer_plugin_qc'
36    ];
37
38    /**
39     * Get the structured data of a given page
40     *
41     * @param string $page The page to get data for
42     * @param string|null $schema The schema to use null for all
43     * @param int $time A timestamp if you want historic data
44     * @return array ('schema' => ( 'fieldlabel' => 'value', ...))
45     * @throws StructException
46     */
47    public function getData($page, $schema = null, $time = 0)
48    {
49        $page = cleanID($page);
50        if (!$time) {
51            $time = time();
52        }
53
54        if (is_null($schema)) {
55            $assignments = Assignments::getInstance();
56            $schemas = $assignments->getPageAssignments($page, false);
57        } else {
58            $schemas = array($schema);
59        }
60
61        $result = array();
62        foreach ($schemas as $schema) {
63            $schemaData = AccessTable::getPageAccess($schema, $page, $time);
64            $result[$schema] = $schemaData->getDataArray();
65        }
66
67        return $result;
68    }
69
70    /**
71     * Saves data for a given page (creates a new revision)
72     *
73     * If this call succeeds you can assume your data has either been saved or it was
74     * not necessary to save it because the data already existed in the wanted form or
75     * the given schemas are no longer assigned to that page.
76     *
77     * Important: You have to check write permissions for the given page before calling
78     * this function yourself!
79     *
80     * this duplicates a bit of code from entry.php - we could also fake post data and let
81     * entry handle it, but that would be rather unclean and might be problematic when multiple
82     * calls are done within the same request.
83     *
84     * @param string $page
85     * @param array $data ('schema' => ( 'fieldlabel' => 'value', ...))
86     * @param string $summary
87     * @param string $summary
88     * @throws StructException
89     * @todo should this try to lock the page?
90     *
91     *
92     */
93    public function saveData($page, $data, $summary = '', $minor = false)
94    {
95        $page = cleanID($page);
96        $summary = trim($summary);
97        if (!$summary) $summary = $this->getLang('summary');
98
99        if (!page_exists($page)) throw new StructException("Page does not exist. You can not attach struct data");
100
101        // validate and see if anything changes
102        $valid = AccessDataValidator::validateDataForPage($data, $page, $errors);
103        if ($valid === false) {
104            throw new StructException("Validation failed:\n%s", join("\n", $errors));
105        }
106        if (!$valid) return; // empty array when no changes were detected
107
108        $newrevision = self::createPageRevision($page, $summary, $minor);
109
110        // save the provided data
111        $assignments = Assignments::getInstance();
112        foreach ($valid as $v) {
113            $v->saveData($newrevision);
114            // make sure this schema is assigned
115            $assignments->assignPageSchema($page, $v->getAccessTable()->getSchema()->getTable());
116        }
117    }
118
119    /**
120     * Save lookup data row
121     *
122     * @param AccessTable $access the table into which to save the data
123     * @param array $data data to be saved in the form of [columnName => 'data']
124     */
125    public function saveLookupData(AccessTable $access, $data)
126    {
127        if (!$access->getSchema()->isEditable()) {
128            throw new StructException('lookup save error: no permission for schema');
129        }
130        $validator = $access->getValidator($data);
131        if (!$validator->validate()) {
132            throw new StructException("Validation failed:\n%s", implode("\n", $validator->getErrors()));
133        }
134        if (!$validator->saveData()) {
135            throw new StructException('No data saved');
136        }
137    }
138
139    /**
140     * Creates a new page revision with the same page content as before
141     *
142     * @param string $page
143     * @param string $summary
144     * @param bool $minor
145     * @return int the new revision
146     */
147    public static function createPageRevision($page, $summary = '', $minor = false)
148    {
149        $summary = trim($summary);
150        // force a new page revision @see action_plugin_struct_entry::handle_pagesave_before()
151        $GLOBALS['struct_plugin_force_page_save'] = true;
152        saveWikiText($page, rawWiki($page), $summary, $minor);
153        unset($GLOBALS['struct_plugin_force_page_save']);
154        $file = wikiFN($page);
155        clearstatcache(false, $file);
156        return filemtime($file);
157    }
158
159    /**
160     * Get info about existing schemas
161     *
162     * @param string|null $schema the schema to query, null for all
163     * @return Schema[]
164     * @throws StructException
165     */
166    public function getSchema($schema = null)
167    {
168        if (is_null($schema)) {
169            $schemas = Schema::getAll();
170        } else {
171            $schemas = array($schema);
172        }
173
174        $result = array();
175        foreach ($schemas as $table) {
176            $result[$table] = new Schema($table);
177        }
178        return $result;
179    }
180
181    /**
182     * Returns all pages known to the struct plugin
183     *
184     * That means all pages that have or had once struct data saved
185     *
186     * @param string|null $schema limit the result to a given schema
187     * @return array (page => (schema => true), ...)
188     * @throws StructException
189     */
190    public function getPages($schema = null)
191    {
192        $assignments = Assignments::getInstance();
193        return $assignments->getPages($schema);
194    }
195
196    /**
197     * Returns decoded JSON value or throws exception
198     * when passed parameter is not JSON
199     *
200     * @param string $value
201     * @return mixed
202     * @throws StructException
203     */
204    public static function decodeJson($value)
205    {
206        if (!empty($value) && $value[0] !== '[') throw new StructException('Lookup expects JSON');
207        return json_decode($value);
208    }
209}
210