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