1<?php
2
3namespace ComboStrap\Api;
4
5
6use ComboStrap\DataType;
7use ComboStrap\ExceptionCompile;
8use ComboStrap\ExceptionNotFound;
9use ComboStrap\ExecutionContext;
10use ComboStrap\HttpResponseStatus;
11use ComboStrap\Identity;
12use ComboStrap\Json;
13use ComboStrap\LogUtility;
14use ComboStrap\LowQualityPageOverwrite;
15use ComboStrap\MarkupPath;
16use ComboStrap\Message;
17use ComboStrap\Meta\Api\Metadata;
18use ComboStrap\Meta\Api\MetadataSystem;
19use ComboStrap\Meta\Form\FormMeta;
20use ComboStrap\Meta\Form\FormMetaField;
21use ComboStrap\Meta\Store\MetadataDokuWikiStore;
22use ComboStrap\MetadataFormDataStore;
23use ComboStrap\MetadataFrontmatterStore;
24use ComboStrap\MetadataStoreTransfer;
25use ComboStrap\MetaManagerForm;
26use ComboStrap\Mime;
27use ComboStrap\PluginUtility;
28use ComboStrap\QualityDynamicMonitoringOverwrite;
29use Doku_Event;
30use dokuwiki\Extension\Event;
31
32class MetaManagerHandler
33{
34
35    public const SUCCESS_MESSAGE = "The data were updated without errors.";
36    public const CANONICAL = "meta-manager";
37    public const META_MANAGER_CALL_ID = "combo-meta-manager";
38    public const META_VIEWER_CALL_ID = "combo-meta-viewer";
39
40    public static function handle(Event $event)
41    {
42        $call = $event->data;
43
44        /**
45         * Shared check between post and get HTTP method
46         */
47        $id = $_GET["id"] ?? null;
48        if ($id === null) {
49            /**
50             * With {@link TestRequest}
51             * for instance
52             */
53            $id = $_REQUEST["id"];
54        }
55        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
56        if (empty($id)) {
57            $executionContext
58                ->response()
59                ->setStatus(HttpResponseStatus::BAD_REQUEST)
60                ->setEvent($event)
61                ->setCanonical(MetaManagerHandler::CANONICAL)
62                ->setBodyAsJsonMessage("The page path (id form) is empty")
63                ->end();
64            return;
65        }
66        $page = MarkupPath::createMarkupFromId($id);
67        if (!$page->exists()) {
68            $executionContext->response()
69                ->setStatus(HttpResponseStatus::DOES_NOT_EXIST)
70                ->setEvent($event)
71                ->setCanonical(MetaManagerHandler::CANONICAL)
72                ->setBodyAsJsonMessage("The page ($id) does not exist")
73                ->end();
74            return;
75        }
76
77        /**
78         * Security
79         */
80        if (!$page->canBeUpdatedByCurrentUser()) {
81            $user = Identity::getUser();
82            $executionContext->response()
83                ->setStatus(HttpResponseStatus::NOT_AUTHORIZED)
84                ->setEvent($event)
85                ->setCanonical(MetaManagerHandler::CANONICAL)
86                ->setBodyAsJsonMessage("Not Authorized: The user ($user) has not the `write` permission for the page (:$id).")
87                ->end();
88            return;
89        }
90
91        /**
92         * Functional code
93         */
94
95        $requestMethod = $_SERVER['REQUEST_METHOD'];
96        switch ($requestMethod) {
97            case 'POST':
98
99                if ($_SERVER["CONTENT_TYPE"] !== "application/json") {
100                    /**
101                     * We can't set the mime content in a {@link TestRequest}
102                     */
103                    if (!PluginUtility::isTest()) {
104                        $executionContext
105                            ->response()->setStatus(HttpResponseStatus::UNSUPPORTED_MEDIA_TYPE)
106                            ->setEvent($event)
107                            ->setCanonical(MetaManagerHandler::CANONICAL)
108                            ->setBodyAsJsonMessage("The post content should be in json format")
109                            ->end();
110                        return;
111                    }
112                }
113
114                /**
115                 * We can't simulate a php://input in a {@link TestRequest}
116                 * We set therefore the post
117                 */
118                if (!PluginUtility::isTest()) {
119                    $jsonString = file_get_contents('php://input');
120                    try {
121                        $_POST = Json::createFromString($jsonString)->toArray();
122                    } catch (ExceptionCompile $e) {
123                        $executionContext
124                            ->response()
125                            ->setStatus(HttpResponseStatus::BAD_REQUEST)
126                            ->setEvent($event)
127                            ->setCanonical(MetaManagerHandler::CANONICAL)
128                            ->setBodyAsJsonMessage("The json payload could not decoded. Error: {$e->getMessage()}")
129                            ->end();
130                        return;
131                    }
132                }
133
134                if ($call === MetaManagerHandler::META_MANAGER_CALL_ID) {
135                    self::handleManagerPost($event, $page, $_POST);
136                } else {
137                    self::handleViewerPost($event, $page, $_POST);
138                }
139
140                return;
141            case "GET":
142
143                if ($call === MetaManagerHandler::META_MANAGER_CALL_ID) {
144                    self::handleManagerGet($event, $page);
145                } else {
146                    self::handleViewerGet($event, $page);
147                }
148                return;
149
150        }
151
152    }
153
154    /**
155     * @param $event
156     * @param MarkupPath $page
157     * @param array $post
158     */
159    private static function handleManagerPost($event, MarkupPath $page, array $post)
160    {
161
162        $formStore = MetadataFormDataStore::getOrCreateFromResource($page, $post);
163        $targetStore = MetadataDokuWikiStore::getOrCreateFromResource($page);
164
165        /**
166         * Boolean form field (default values)
167         * are not send back by the HTML form
168         */
169        $defaultBooleanMetadata = [
170            LowQualityPageOverwrite::PROPERTY_NAME,
171            QualityDynamicMonitoringOverwrite::PROPERTY_NAME
172        ];
173        $defaultBoolean = [];
174        foreach ($defaultBooleanMetadata as $booleanMeta) {
175            try {
176                $metadata = MetadataSystem::getForName($booleanMeta)
177                    ->setResource($page)
178                    ->setReadStore($formStore)
179                    ->setWriteStore($targetStore);
180            } catch (ExceptionNotFound $e) {
181                LogUtility::internalError("The boolean metadata name ($booleanMeta) was not found", self::CANONICAL, $e);
182                continue;
183            }
184            $defaultBoolean[$metadata::getName()] = $metadata->toStoreDefaultValue();
185        }
186        $post = array_merge($defaultBoolean, $post);
187
188        /**
189         * Processing
190         */
191        $transfer = MetadataStoreTransfer::createForPage($page)
192            ->fromStore($formStore)
193            ->toStore($targetStore)
194            ->process($post);
195        $processingMessages = $transfer->getMessages();
196
197
198        $responseMessages = [];
199        $responseStatus = HttpResponseStatus::ALL_GOOD;
200        foreach ($processingMessages as $upsertMessages) {
201            $responseMessage = [ucfirst($upsertMessages->getType())];
202            $documentationHyperlink = $upsertMessages->getDocumentationHyperLink();
203            if ($documentationHyperlink !== null) {
204                $responseMessage[] = $documentationHyperlink;
205            }
206            $responseMessage[] = $upsertMessages->getContent(Mime::PLAIN_TEXT);
207            $responseMessages[] = implode(" - ", $responseMessage);
208            if ($upsertMessages->getType() === Message::TYPE_ERROR && $responseStatus !== HttpResponseStatus::BAD_REQUEST) {
209                $responseStatus = HttpResponseStatus::BAD_REQUEST;
210            }
211        }
212
213        if (sizeof($responseMessages) === 0) {
214            $responseMessages[] = MetaManagerHandler::SUCCESS_MESSAGE;
215        }
216
217        try {
218            $frontMatterMessage = MetadataFrontmatterStore::createFromPage($page)
219                ->sync();
220            $responseMessages[] = $frontMatterMessage->getPlainTextContent();
221        } catch (ExceptionCompile $e) {
222            $responseMessages[] = $e->getMessage();
223        }
224
225
226        /**
227         * Response
228         */
229        ExecutionContext::getActualOrCreateFromEnv()
230            ->response()
231            ->setStatus(HttpResponseStatus::ALL_GOOD)
232            ->setEvent($event)
233            ->setBodyAsJsonMessage($responseMessages)
234            ->end();
235
236
237    }
238
239    /**
240     * @param Doku_Event $event
241     * @param MarkupPath $page
242     */
243    private static function handleManagerGet(Doku_Event $event, MarkupPath $page)
244    {
245        $formMeta = MetaManagerForm::createForPage($page)->toFormMeta();
246        $formMetaAssociativeArray = $formMeta->toAssociativeArray();
247        $payload = json_encode($formMetaAssociativeArray);
248        ExecutionContext::getActualOrCreateFromEnv()
249            ->response()
250            ->setStatus(HttpResponseStatus::ALL_GOOD)
251            ->setEvent($event)
252            ->setBody($payload, Mime::getJson())
253            ->end();
254    }
255
256    /**
257     * @param Doku_Event $event
258     * @param MarkupPath $page
259     */
260    private static function handleViewerGet(Doku_Event $event, MarkupPath $page)
261    {
262        if (!Identity::isManager()) {
263            ExecutionContext::getActualOrCreateFromEnv()
264                ->response()
265                ->setStatus(HttpResponseStatus::NOT_AUTHORIZED)
266                ->setEvent($event)
267                ->setCanonical(MetaManagerHandler::CANONICAL)
268                ->setBodyAsJsonMessage("Not Authorized (managers only)")
269                ->end();
270            return;
271        }
272        $metadata = MetadataDokuWikiStore::getOrCreateFromResource($page)->getDataCurrentAndPersistent();
273        $persistent = $metadata[MetadataDokuWikiStore::PERSISTENT_DOKUWIKI_KEY];
274        ksort($persistent);
275        $current = $metadata[MetadataDokuWikiStore::CURRENT_METADATA];
276        ksort($current);
277        $form = FormMeta::create("raw_metadata")
278            ->addField(
279                FormMetaField::create(MetadataDokuWikiStore::PERSISTENT_DOKUWIKI_KEY, DataType::JSON_TYPE_VALUE)
280                    ->setLabel("Persistent Metadata (User Metadata)")
281                    ->setTab("persistent")
282                    ->setDescription("The persistent metadata contains raw values. They contains the values set by the user and the fixed values such as page id.")
283                    ->addValue(json_encode($persistent))
284            )
285            ->addField(FormMetaField::create(MetadataDokuWikiStore::CURRENT_METADATA, DataType::JSON_TYPE_VALUE)
286                ->setLabel("Current (Derived) Metadata")
287                ->setTab("current")
288                ->setDescription("The current metadata are the derived / calculated / runtime metadata values (extended with the persistent metadata).")
289                ->addValue(json_encode($current))
290                ->setMutable(false)
291            )
292            ->toAssociativeArray();
293
294        ExecutionContext::getActualOrCreateFromEnv()
295            ->response()
296            ->setStatus(HttpResponseStatus::ALL_GOOD)
297            ->setEvent($event)
298            ->setCanonical(MetaManagerHandler::CANONICAL)
299            ->setBody(json_encode($form), Mime::getJson())
300            ->end();
301
302    }
303
304    private static function handleViewerPost(Doku_Event $event, MarkupPath $page, array $post)
305    {
306
307        $metadataStore = MetadataDokuWikiStore::getOrCreateFromResource($page);
308        $actualMeta = $metadataStore->getDataCurrentAndPersistent();
309
310        /**
311         * @var Message[]
312         */
313        $messages = [];
314        /**
315         * Technically, persistent is a copy of persistent data
316         * but on the ui for now, only persistent data can be modified
317         */
318        $postMeta = json_decode($post[MetadataDokuWikiStore::PERSISTENT_DOKUWIKI_KEY], true);
319        if ($postMeta === null) {
320            ExecutionContext::getActualOrCreateFromEnv()
321                ->response()
322                ->setStatus(HttpResponseStatus::BAD_REQUEST)
323                ->setEvent($event)
324                ->setBodyAsJsonMessage("The metadata should be in json format")
325                ->end();
326            return;
327        }
328
329
330        $managedMetaMessageSuffix = "is a managed metadata, you need to use the metadata manager to delete it";
331
332        /**
333         * Process the actual attribute
334         * We loop only over the persistent metadata
335         * that are the one that we want change
336         */
337        $persistentMetadata = $actualMeta[MetadataDokuWikiStore::PERSISTENT_DOKUWIKI_KEY];
338        foreach ($persistentMetadata as $key => $value) {
339
340            $postMetaValue = null;
341            if (isset($postMeta[$key])) {
342                $postMetaValue = $postMeta[$key];
343                unset($postMeta[$key]);
344            }
345
346            try {
347                $metadata = MetadataSystem::getForName($key);
348            } catch (ExceptionNotFound $e) {
349                $metadata = null;
350            }
351
352            if ($postMetaValue === null) {
353
354                if ($metadata !== null && $metadata->isMutable()) {
355                    $messages[] = Message::createInfoMessage("The metadata ($key) $managedMetaMessageSuffix");
356                    continue;
357                }
358                if (in_array($key, Metadata::NOT_MODIFIABLE_PERSISTENT_METADATA)) {
359                    $messages[] = Message::createInfoMessage("The metadata ($key) is a internal metadata, you can't delete it");
360                    continue;
361                }
362                unset($actualMeta[MetadataDokuWikiStore::CURRENT_METADATA][$key]);
363                unset($actualMeta[MetadataDokuWikiStore::PERSISTENT_DOKUWIKI_KEY][$key]);
364                $stringValue = DataType::toString($value);
365                $messages[] = Message::createInfoMessage("The metadata ($key) with the value ($stringValue) was deleted");
366            } else {
367                if ($value !== $postMetaValue) {
368                    if ($metadata !== null && $metadata->isMutable()) {
369                        $messages[] = Message::createInfoMessage("The metadata ($key) $managedMetaMessageSuffix");
370                        continue;
371                    }
372                    if (in_array($key, Metadata::NOT_MODIFIABLE_PERSISTENT_METADATA)) {
373                        $messages[] = Message::createInfoMessage("The metadata ($key) is a internal metadata, you can't modify it");
374                        continue;
375                    }
376                    $actualMeta[MetadataDokuWikiStore::CURRENT_METADATA][$key] = $postMetaValue;
377                    $actualMeta[MetadataDokuWikiStore::PERSISTENT_DOKUWIKI_KEY][$key] = $postMetaValue;
378                    $messages[] = Message::createInfoMessage("The metadata ($key) was updated to the value ($postMetaValue) - Old value ($value)");
379                }
380            }
381        }
382        /**
383         * Process the new attribute
384         */
385        foreach ($postMeta as $key => $value) {
386
387            try {
388                $metadata = MetadataSystem::getForName($key);
389                if ($metadata->isMutable()) {
390                    // This meta should be modified via the form
391                    $messages[] = Message::createInfoMessage("The metadata ($key) can only be added via the meta manager");
392                    continue;
393                }
394            } catch (ExceptionNotFound $e) {
395                //
396            }
397
398            if (in_array($key, Metadata::NOT_MODIFIABLE_PERSISTENT_METADATA)) {
399                // this meta are not modifiable
400                $messages[] = Message::createInfoMessage("The metadata ($key) is a internal metadata, you can't modify it");
401                continue;
402            }
403            $actualMeta[MetadataDokuWikiStore::CURRENT_METADATA][$key] = $value;
404            $actualMeta[MetadataDokuWikiStore::PERSISTENT_DOKUWIKI_KEY][$key] = $value;
405            $messages[] = Message::createInfoMessage("The metadata ($key) was created with the value ($value)");
406        }
407
408
409        p_save_metadata($page->getWikiId(), $actualMeta);
410
411        if (sizeof($messages) !== 0) {
412            $messagesToSend = [];
413            foreach ($messages as $message) {
414                $messagesToSend[] = $message->getPlainTextContent();
415            }
416        } else {
417            $messagesToSend = "No metadata has been changed.";
418        }
419        ExecutionContext::getActualOrCreateFromEnv()
420            ->response()
421            ->setStatus(HttpResponseStatus::ALL_GOOD)
422            ->setEvent($event)
423            ->setBodyAsJsonMessage($messagesToSend)
424            ->end();
425
426    }
427}
428