1c3437056SNickeau<?php 2c3437056SNickeau 3c3437056SNickeau 4c3437056SNickeaunamespace ComboStrap; 5c3437056SNickeau 604fd306cSNickeauuse ComboStrap\Meta\Api\Metadata; 704fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStore; 804fd306cSNickeauuse ComboStrap\Meta\Field\Aliases; 904fd306cSNickeauuse ComboStrap\Meta\Field\BacklinkCount; 1004fd306cSNickeauuse ComboStrap\Meta\Field\PageH1; 1104fd306cSNickeauuse ComboStrap\Meta\Field\Region; 1204fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDbStore; 1304fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDokuWikiStore; 144cadd4f8SNickeauuse DateTime; 1504fd306cSNickeauuse renderer_plugin_combo_analytics; 16c3437056SNickeau 17c3437056SNickeau/** 18c3437056SNickeau * The class that manage the replication 19c3437056SNickeau * Class Replicate 20c3437056SNickeau * @package ComboStrap 21c3437056SNickeau * 22c3437056SNickeau * The database can also be seen as a {@link MetadataStore} 23c3437056SNickeau * and an {@link Index} 24c3437056SNickeau */ 25c3437056SNickeauclass DatabasePageRow 26c3437056SNickeau{ 27c3437056SNickeau 28c3437056SNickeau 29c3437056SNickeau /** 30c3437056SNickeau * The list of attributes that are set 31c3437056SNickeau * at build time 32c3437056SNickeau * used in the build functions such as {@link DatabasePageRow::getDatabaseRowFromPage()} 33c3437056SNickeau * to build the sql 34c3437056SNickeau */ 35c3437056SNickeau private const PAGE_BUILD_ATTRIBUTES = 36c3437056SNickeau [ 37c3437056SNickeau self::ROWID, 38c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, 39c3437056SNickeau self::ANALYTICS_ATTRIBUTE, 40c3437056SNickeau PageDescription::PROPERTY_NAME, 41c3437056SNickeau Canonical::PROPERTY_NAME, 42c3437056SNickeau ResourceName::PROPERTY_NAME, 43c3437056SNickeau PageTitle::TITLE, 44c3437056SNickeau PageH1::PROPERTY_NAME, 45c3437056SNickeau PagePublicationDate::PROPERTY_NAME, 46c3437056SNickeau ModificationDate::PROPERTY_NAME, 4704fd306cSNickeau CreationDate::PROPERTY_NAME, 48c3437056SNickeau PagePath::PROPERTY_NAME, 49c3437056SNickeau StartDate::PROPERTY_NAME, 50c3437056SNickeau EndDate::PROPERTY_NAME, 51c3437056SNickeau Region::PROPERTY_NAME, 52c3437056SNickeau Lang::PROPERTY_NAME, 53c3437056SNickeau PageType::PROPERTY_NAME, 54c3437056SNickeau PageId::PROPERTY_NAME, 55c3437056SNickeau PageId::PAGE_ID_ABBR_ATTRIBUTE, 5604fd306cSNickeau ReplicationDate::PROPERTY_NAME, 57c3437056SNickeau BacklinkCount::PROPERTY_NAME 58c3437056SNickeau ]; 59c3437056SNickeau const ANALYTICS_ATTRIBUTE = "analytics"; 60c3437056SNickeau 61c3437056SNickeau /** 62c3437056SNickeau * For whatever reason, the row id is lowercase 63c3437056SNickeau */ 64c3437056SNickeau const ROWID = "rowid"; 65c3437056SNickeau 66c3437056SNickeau const CANONICAL = MetadataDbStore::CANONICAL; 67c3437056SNickeau 6804fd306cSNickeau const IS_HOME_COLUMN = "is_home"; 6904fd306cSNickeau const IS_INDEX_COLUMN = "is_index"; 7004fd306cSNickeau 71c3437056SNickeau /** 7204fd306cSNickeau * @var MarkupPath 73c3437056SNickeau */ 7404fd306cSNickeau private $markupPath; 75c3437056SNickeau /** 76c3437056SNickeau * @var Sqlite|null 77c3437056SNickeau */ 78c3437056SNickeau private $sqlite; 79c3437056SNickeau 80c3437056SNickeau /** 81c3437056SNickeau * @var array 82c3437056SNickeau */ 83c3437056SNickeau private $row; 84c3437056SNickeau 85c3437056SNickeau /** 86c3437056SNickeau * Replicate constructor. 8704fd306cSNickeau * @throws ExceptionSqliteNotAvailable 88c3437056SNickeau */ 89c3437056SNickeau public function __construct() 90c3437056SNickeau { 91c3437056SNickeau /** 92c3437056SNickeau * Persist on the DB 93c3437056SNickeau */ 94c3437056SNickeau $this->sqlite = Sqlite::createOrGetSqlite(); 95c3437056SNickeau 96c3437056SNickeau 97c3437056SNickeau } 98c3437056SNickeau 9904fd306cSNickeau public static function getFromPageObject(MarkupPath $page): DatabasePageRow 10004fd306cSNickeau { 10104fd306cSNickeau $databasePage = new DatabasePageRow(); 10204fd306cSNickeau try { 10304fd306cSNickeau $row = $databasePage->getDatabaseRowFromPage($page); 10404fd306cSNickeau $databasePage->setRow($row); 10504fd306cSNickeau return $databasePage; 10604fd306cSNickeau } catch (ExceptionNotExists $e) { 10704fd306cSNickeau // 10804fd306cSNickeau } 10904fd306cSNickeau return $databasePage; 11004fd306cSNickeau } 11104fd306cSNickeau 112c3437056SNickeau /** 113c3437056SNickeau * Delete the cache, 114c3437056SNickeau * Process the analytics 115c3437056SNickeau * Save it in the Db 116c3437056SNickeau * Delete from the page to refresh if any 117c3437056SNickeau * 118c3437056SNickeau * If you want the analytics: 119c3437056SNickeau * * from the cache use {@link self::getAnalyticsFromFs()} 120c3437056SNickeau * * from the db use {@link self::getAnalyticsFromDb()} 121c3437056SNickeau * 122c3437056SNickeau * 12304fd306cSNickeau * @throws ExceptionCompile 124c3437056SNickeau */ 125c3437056SNickeau public function replicate(): DatabasePageRow 126c3437056SNickeau { 127c3437056SNickeau 12804fd306cSNickeau if (!FileSystems::exists($this->markupPath)) { 12904fd306cSNickeau throw new ExceptionCompile("You can't replicate the non-existing page ($this->markupPath) on the file system"); 130c3437056SNickeau } 131c3437056SNickeau 132c3437056SNickeau 133c3437056SNickeau /** 134c3437056SNickeau * Page Replication should appears 135c3437056SNickeau */ 136c3437056SNickeau $this->replicatePage(); 137c3437056SNickeau 138c3437056SNickeau /** 139c3437056SNickeau * @var Metadata $tabularMetadataToSync 140c3437056SNickeau */ 141c3437056SNickeau $tabularMetadataToSync = [ 142c3437056SNickeau (new References()), 143c3437056SNickeau (new Aliases()) 144c3437056SNickeau ]; 14504fd306cSNickeau $fsStore = MetadataDokuWikiStore::getOrCreateFromResource($this->markupPath); 14604fd306cSNickeau $dbStore = MetadataDbStore::getOrCreateFromResource($this->markupPath); 147c3437056SNickeau foreach ($tabularMetadataToSync as $tabular) { 148c3437056SNickeau $tabular 14904fd306cSNickeau ->setResource($this->markupPath) 150c3437056SNickeau ->setReadStore($fsStore) 151c3437056SNickeau ->buildFromReadStore() 152c3437056SNickeau ->setWriteStore($dbStore) 153c3437056SNickeau ->persist(); 154c3437056SNickeau } 155c3437056SNickeau 156c3437056SNickeau /** 157c3437056SNickeau * Analytics (derived) 158c3437056SNickeau * Should appear at the end of the replication because it is based 159c3437056SNickeau * on the previous replication (ie backlink count) 160c3437056SNickeau */ 161c3437056SNickeau $this->replicateAnalytics(); 162c3437056SNickeau 163c3437056SNickeau 164c3437056SNickeau return $this; 165c3437056SNickeau 166c3437056SNickeau } 167c3437056SNickeau 168c3437056SNickeau /** 16904fd306cSNickeau * @throws ExceptionCompile 170c3437056SNickeau */ 17104fd306cSNickeau public function replicateAndRebuild(): DatabasePageRow 172c3437056SNickeau { 173c3437056SNickeau $this->replicate(); 174c3437056SNickeau $this->rebuild(); 175c3437056SNickeau return $this; 176c3437056SNickeau } 177c3437056SNickeau 17804fd306cSNickeau /** 17904fd306cSNickeau * @throws ExceptionNotExists - no page id to add 18004fd306cSNickeau */ 181c3437056SNickeau private function addPageIdMeta(array &$metaRecord) 182c3437056SNickeau { 18304fd306cSNickeau try { 18404fd306cSNickeau $pageId = $this->markupPath->getPageId(); 18504fd306cSNickeau } catch (ExceptionNotFound $e) { 18604fd306cSNickeau $pageId = PageId::generateAndStorePageId($this->markupPath); 18704fd306cSNickeau } 18804fd306cSNickeau $metaRecord[PageId::PROPERTY_NAME] = $pageId; 18904fd306cSNickeau $metaRecord[PageId::PAGE_ID_ABBR_ATTRIBUTE] = PageId::getAbbreviated($pageId); 190c3437056SNickeau } 191c3437056SNickeau 192c3437056SNickeau public static function createFromPageId(string $pageId): DatabasePageRow 193c3437056SNickeau { 194c3437056SNickeau $databasePage = new DatabasePageRow(); 19504fd306cSNickeau try { 196c3437056SNickeau $row = $databasePage->getDatabaseRowFromPageId($pageId); 197c3437056SNickeau $databasePage->setRow($row); 19804fd306cSNickeau } catch (ExceptionNotFound|ExceptionSqliteNotAvailable $e) { 19904fd306cSNickeau // not found 200c3437056SNickeau } 20104fd306cSNickeau 202c3437056SNickeau return $databasePage; 203c3437056SNickeau } 204c3437056SNickeau 20504fd306cSNickeau /** 20604fd306cSNickeau * @param MarkupPath $page 20704fd306cSNickeau * @return DatabasePageRow 20804fd306cSNickeau * @throws ExceptionSqliteNotAvailable - if there is no sqlite available 20904fd306cSNickeau * @noinspection PhpDocRedundantThrowsInspection 21004fd306cSNickeau */ 21104fd306cSNickeau public static function getOrCreateFromPageObject(MarkupPath $page): DatabasePageRow 212c3437056SNickeau { 213c3437056SNickeau 214c3437056SNickeau $databasePage = new DatabasePageRow(); 21504fd306cSNickeau try { 216c3437056SNickeau $row = $databasePage->getDatabaseRowFromPage($page); 217c3437056SNickeau $databasePage->setRow($row); 218c3437056SNickeau return $databasePage; 21904fd306cSNickeau } catch (ExceptionNotExists $e) { 22004fd306cSNickeau // page copied on the local system 22104fd306cSNickeau try { 22204fd306cSNickeau ComboFs::createIfNotExists($page); 22304fd306cSNickeau $row = $databasePage->getDatabaseRowFromPage($page); 22404fd306cSNickeau $databasePage->setRow($row); 22504fd306cSNickeau return $databasePage; 22604fd306cSNickeau } catch (ExceptionNotExists $e) { 22704fd306cSNickeau throw ExceptionRuntimeInternal::withMessageAndError("The row should exists as we created it specifically", $e); 22804fd306cSNickeau } 229c3437056SNickeau } 230c3437056SNickeau 23104fd306cSNickeau } 23204fd306cSNickeau 23304fd306cSNickeau 23404fd306cSNickeau /** 23504fd306cSNickeau * 23604fd306cSNickeau */ 23704fd306cSNickeau public 23804fd306cSNickeau static function createFromPageIdAbbr(string $pageIdAbbr): DatabasePageRow 239c3437056SNickeau { 240c3437056SNickeau $databasePage = new DatabasePageRow(); 24104fd306cSNickeau try { 242c3437056SNickeau $row = $databasePage->getDatabaseRowFromAttribute(PageId::PAGE_ID_ABBR_ATTRIBUTE, $pageIdAbbr); 243c3437056SNickeau $databasePage->setRow($row); 24404fd306cSNickeau } catch (ExceptionNotFound $e) { 24504fd306cSNickeau // ok 246c3437056SNickeau } 247c3437056SNickeau return $databasePage; 248c3437056SNickeau 249c3437056SNickeau } 250c3437056SNickeau 251c3437056SNickeau /** 252c3437056SNickeau * @param $canonical 253c3437056SNickeau * @return DatabasePageRow 254c3437056SNickeau */ 25504fd306cSNickeau public 25604fd306cSNickeau static function createFromCanonical($canonical): DatabasePageRow 257c3437056SNickeau { 258c3437056SNickeau 25904fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($canonical); 260c3437056SNickeau $databasePage = new DatabasePageRow(); 26104fd306cSNickeau try { 262c3437056SNickeau $row = $databasePage->getDatabaseRowFromAttribute(Canonical::PROPERTY_NAME, $canonical); 263c3437056SNickeau $databasePage->setRow($row); 26404fd306cSNickeau } catch (ExceptionNotFound $e) { 26504fd306cSNickeau // ok 266c3437056SNickeau } 267c3437056SNickeau return $databasePage; 268c3437056SNickeau 269c3437056SNickeau 270c3437056SNickeau } 271c3437056SNickeau 27204fd306cSNickeau public 27304fd306cSNickeau static function createFromAlias($alias): DatabasePageRow 274c3437056SNickeau { 275c3437056SNickeau 27604fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($alias); 277c3437056SNickeau $databasePage = new DatabasePageRow(); 278c3437056SNickeau $row = $databasePage->getDatabaseRowFromAlias($alias); 279c3437056SNickeau if ($row != null) { 280c3437056SNickeau $databasePage->setRow($row); 28104fd306cSNickeau $page = $databasePage->getMarkupPath(); 28258317768Sgerardnico if ($page !== null) { 28358317768Sgerardnico // page may be null in production 28458317768Sgerardnico // PHP Fatal error: Uncaught Error: Call to a member function setBuildAliasPath() on null in 28558317768Sgerardnico // /opt/www/bytle/farmer.bytle.net/lib/plugins/combo/ComboStrap/DatabasePageRow.php:220 28658317768Sgerardnico $page->setBuildAliasPath($alias); 28758317768Sgerardnico } 288c3437056SNickeau } 289c3437056SNickeau return $databasePage; 290c3437056SNickeau 291c3437056SNickeau } 292c3437056SNickeau 29304fd306cSNickeau /** 29404fd306cSNickeau * @throws ExceptionNotFound 29504fd306cSNickeau */ 29604fd306cSNickeau public 29704fd306cSNickeau static function getFromDokuWikiId($id): DatabasePageRow 298c3437056SNickeau { 299c3437056SNickeau $databasePage = new DatabasePageRow(); 300*4ebc3257Sgerardnico $databasePage->markupPath = MarkupPath::createMarkupFromId($id); 301c3437056SNickeau $row = $databasePage->getDatabaseRowFromDokuWikiId($id); 302c3437056SNickeau $databasePage->setRow($row); 303c3437056SNickeau return $databasePage; 304c3437056SNickeau } 305c3437056SNickeau 30604fd306cSNickeau public 30704fd306cSNickeau function getPageId() 308c3437056SNickeau { 309c3437056SNickeau return $this->getFromRow(PageId::PROPERTY_NAME); 310c3437056SNickeau } 311c3437056SNickeau 312c3437056SNickeau 313c3437056SNickeau public 314c3437056SNickeau function shouldReplicate(): bool 315c3437056SNickeau { 316c3437056SNickeau 31704fd306cSNickeau 3184cadd4f8SNickeau $dateReplication = $this->getReplicationDate(); 3194cadd4f8SNickeau if ($dateReplication === null) { 3204cadd4f8SNickeau return true; 3214cadd4f8SNickeau } 3224cadd4f8SNickeau 3234cadd4f8SNickeau /** 3244cadd4f8SNickeau * When the replication date is older than the actual document 3254cadd4f8SNickeau */ 32604fd306cSNickeau try { 32704fd306cSNickeau $modifiedTime = FileSystems::getModifiedTime($this->markupPath->getPathObject()); 3284cadd4f8SNickeau if ($modifiedTime > $dateReplication) { 3294cadd4f8SNickeau return true; 3304cadd4f8SNickeau } 33104fd306cSNickeau } catch (ExceptionNotFound $e) { 33204fd306cSNickeau return false; 33304fd306cSNickeau } 33404fd306cSNickeau 33504fd306cSNickeau 33604fd306cSNickeau $path = $this->markupPath->fetchAnalyticsPath(); 3374cadd4f8SNickeau 338c3437056SNickeau /** 339c3437056SNickeau * When the file does not exist 340c3437056SNickeau */ 34104fd306cSNickeau $exist = FileSystems::exists($path); 342c3437056SNickeau if (!$exist) { 343c3437056SNickeau return true; 344c3437056SNickeau } 345c3437056SNickeau 346c3437056SNickeau /** 3474cadd4f8SNickeau * When the analytics document is older 348c3437056SNickeau */ 34904fd306cSNickeau try { 35004fd306cSNickeau 35104fd306cSNickeau $modifiedTime = FileSystems::getModifiedTime($path); 352c3437056SNickeau if ($modifiedTime > $dateReplication) { 353c3437056SNickeau return true; 354c3437056SNickeau } 35504fd306cSNickeau } catch (ExceptionNotFound $e) { 35604fd306cSNickeau // 35704fd306cSNickeau } 358c3437056SNickeau 3594cadd4f8SNickeau 360c3437056SNickeau /** 361c3437056SNickeau * When the database version file is higher 362c3437056SNickeau */ 36304fd306cSNickeau $version = LocalPath::createFromPathString(__DIR__ . "/../db/latest.version"); 36404fd306cSNickeau try { 365c3437056SNickeau $versionModifiedTime = FileSystems::getModifiedTime($version); 36604fd306cSNickeau } catch (ExceptionNotFound $e) { 36704fd306cSNickeau return false; 36804fd306cSNickeau } 369c3437056SNickeau if ($versionModifiedTime > $dateReplication) { 370c3437056SNickeau return true; 371c3437056SNickeau } 372c3437056SNickeau 373c3437056SNickeau /** 374c3437056SNickeau * When the class date time is higher 375c3437056SNickeau */ 37604fd306cSNickeau $code = LocalPath::createFromPathString(__DIR__ . "/DatabasePageRow.php"); 37704fd306cSNickeau try { 378c3437056SNickeau $codeModified = FileSystems::getModifiedTime($code); 37904fd306cSNickeau } catch (ExceptionNotFound $e) { 38004fd306cSNickeau throw new ExceptionRuntime("The database file does not exist"); 38104fd306cSNickeau } 382c3437056SNickeau if ($codeModified > $dateReplication) { 383c3437056SNickeau return true; 384c3437056SNickeau } 385c3437056SNickeau 386c3437056SNickeau return false; 387c3437056SNickeau 388c3437056SNickeau } 389c3437056SNickeau 390c3437056SNickeau public 391c3437056SNickeau function delete() 392c3437056SNickeau { 393c3437056SNickeau 39404fd306cSNickeau $request = $this->sqlite 395c3437056SNickeau ->createRequest() 39604fd306cSNickeau ->setQueryParametrized('delete from pages where id = ?', [$this->markupPath->getWikiId()]); 397c3437056SNickeau try { 398c3437056SNickeau $request->execute(); 39904fd306cSNickeau } catch (ExceptionCompile $e) { 40004fd306cSNickeau LogUtility::msg("Something went wrong when deleting the page ({$this->markupPath}) from the database"); 401c3437056SNickeau } finally { 402c3437056SNickeau $request->close(); 403c3437056SNickeau } 404c3437056SNickeau $this->buildInitObjectFields(); 405c3437056SNickeau 406c3437056SNickeau } 407c3437056SNickeau 408c3437056SNickeau /** 40904fd306cSNickeau * @return Json the analytics array or null if not in db 410c3437056SNickeau */ 411c3437056SNickeau public 41204fd306cSNickeau function getAnalyticsData(): Json 413c3437056SNickeau { 414c3437056SNickeau 415c3437056SNickeau $jsonString = $this->getFromRow(self::ANALYTICS_ATTRIBUTE); 416c3437056SNickeau if ($jsonString === null) { 41704fd306cSNickeau // we put an empty json to not get any problem with the json database function 41804fd306cSNickeau // on an empty string / null (for sqlite) 41904fd306cSNickeau return Json::createEmpty(); 420c3437056SNickeau } 421c3437056SNickeau try { 422c3437056SNickeau return Json::createFromString($jsonString); 42304fd306cSNickeau } catch (ExceptionCompile $e) { 42404fd306cSNickeau throw ExceptionRuntimeInternal::withMessageAndError("Error while building back the analytics JSON object. {$e->getMessage()}", $e); 425c3437056SNickeau } 426c3437056SNickeau 427c3437056SNickeau } 428c3437056SNickeau 429c3437056SNickeau /** 430c3437056SNickeau * Return the database row 431c3437056SNickeau * 432c3437056SNickeau * 43304fd306cSNickeau * @throws ExceptionNotExists - if the row does not exists 434c3437056SNickeau */ 43504fd306cSNickeau public 43604fd306cSNickeau function getDatabaseRowFromPage(MarkupPath $markupPath): array 437c3437056SNickeau { 438c3437056SNickeau 43904fd306cSNickeau $this->setMarkupPath($markupPath); 440c3437056SNickeau 44104fd306cSNickeau /** 44204fd306cSNickeau * Generated identifier 44304fd306cSNickeau */ 44404fd306cSNickeau try { 44504fd306cSNickeau $pageId = $markupPath->getPageId(); 44604fd306cSNickeau return $this->getDatabaseRowFromPageId($pageId); 44704fd306cSNickeau } catch (ExceptionNotFound $e) { 44804fd306cSNickeau // no page id 449c3437056SNickeau } 450c3437056SNickeau 451c3437056SNickeau /** 45204fd306cSNickeau * Named identifier: path 453c3437056SNickeau */ 45404fd306cSNickeau try { 45504fd306cSNickeau $path = $markupPath->getPathObject(); 45604fd306cSNickeau return $this->getDatabaseRowFromPath($path); 45704fd306cSNickeau } catch (ExceptionNotFound $e) { 45804fd306cSNickeau // not found 45904fd306cSNickeau } 46004fd306cSNickeau 46104fd306cSNickeau /** 46204fd306cSNickeau * Named identifier: id (ie path) 46304fd306cSNickeau */ 46404fd306cSNickeau try { 46504fd306cSNickeau $id = $markupPath->getPathObject()->toWikiPath()->getWikiId(); 466c3437056SNickeau return $this->getDatabaseRowFromDokuWikiId($id); 46704fd306cSNickeau } catch (ExceptionCast|ExceptionNotFound $e) { 46804fd306cSNickeau } 46904fd306cSNickeau 47004fd306cSNickeau /** 47104fd306cSNickeau * Named identifier: canonical 47204fd306cSNickeau * (Note that canonical should become a soft link and therefore a path) 47304fd306cSNickeau */ 47404fd306cSNickeau try { 47504fd306cSNickeau $canonical = Canonical::createForPage($markupPath)->getValue(); 47604fd306cSNickeau return $this->getDatabaseRowFromCanonical($canonical->toAbsoluteId()); 47704fd306cSNickeau } catch (ExceptionNotFound $e) { 47804fd306cSNickeau // no canonical 47904fd306cSNickeau } 48004fd306cSNickeau 48104fd306cSNickeau // we send a not exist 48204fd306cSNickeau throw new ExceptionNotExists("No row could be found"); 483c3437056SNickeau 484c3437056SNickeau 485c3437056SNickeau } 486c3437056SNickeau 487c3437056SNickeau 4884cadd4f8SNickeau /** 4894cadd4f8SNickeau * @return DateTime|null 4904cadd4f8SNickeau */ 49104fd306cSNickeau public 49204fd306cSNickeau function getReplicationDate(): ?DateTime 493c3437056SNickeau { 49404fd306cSNickeau $dateString = $this->getFromRow(ReplicationDate::getPersistentName()); 495c3437056SNickeau if ($dateString === null) { 496c3437056SNickeau return null; 497c3437056SNickeau } 498c3437056SNickeau try { 499c3437056SNickeau return Iso8601Date::createFromString($dateString)->getDateTime(); 50004fd306cSNickeau } catch (ExceptionCompile $e) { 501c3437056SNickeau LogUtility::msg("Error while reading the replication date in the database. {$e->getMessage()}"); 502c3437056SNickeau return null; 503c3437056SNickeau } 504c3437056SNickeau 505c3437056SNickeau } 506c3437056SNickeau 507c3437056SNickeau /** 50804fd306cSNickeau * 50904fd306cSNickeau * @throws ExceptionBadState 51004fd306cSNickeau * @throws ExceptionSqliteNotAvailable 511c3437056SNickeau */ 51204fd306cSNickeau public 51304fd306cSNickeau function replicatePage(): void 514c3437056SNickeau { 515c3437056SNickeau 51604fd306cSNickeau if (!FileSystems::exists($this->markupPath)) { 51704fd306cSNickeau throw new ExceptionBadState("You can't replicate the page ($this->markupPath) because it does not exists."); 518c3437056SNickeau } 519c3437056SNickeau 520c3437056SNickeau /** 521c3437056SNickeau * Replication Date 522c3437056SNickeau */ 52304fd306cSNickeau $replicationDate = ReplicationDate::createFromPage($this->markupPath) 524c3437056SNickeau ->setWriteStore(MetadataDbStore::class) 5254cadd4f8SNickeau ->setValue(new DateTime()); 526c3437056SNickeau 527c3437056SNickeau /** 52804fd306cSNickeau * Same data as {@link MarkupPath::getMetadataForRendering()} 529c3437056SNickeau */ 530c3437056SNickeau $record = $this->getMetaRecord(); 531c3437056SNickeau $record[$replicationDate::getPersistentName()] = $replicationDate->toStoreValue(); 53204fd306cSNickeau $this->upsertAttributes($record); 533c3437056SNickeau 534c3437056SNickeau } 535c3437056SNickeau 536c3437056SNickeau 537c3437056SNickeau /** 53804fd306cSNickeau * 539c3437056SNickeau * 540c3437056SNickeau * Attribute that are scalar / modifiable in the database 541c3437056SNickeau * (not aliases or json data for instance) 54204fd306cSNickeau * 54304fd306cSNickeau * @throws ExceptionBadState 54404fd306cSNickeau * @throws ExceptionSqliteNotAvailable 545c3437056SNickeau */ 54604fd306cSNickeau public 54704fd306cSNickeau function replicateMetaAttributes(): void 548c3437056SNickeau { 549c3437056SNickeau 55004fd306cSNickeau $this->upsertAttributes($this->getMetaRecord()); 551c3437056SNickeau 552c3437056SNickeau } 553c3437056SNickeau 55404fd306cSNickeau /** 55504fd306cSNickeau * @throws ExceptionBadState - if the array is empty 55604fd306cSNickeau */ 55704fd306cSNickeau public 55804fd306cSNickeau function upsertAttributes(array $attributes): void 559c3437056SNickeau { 560c3437056SNickeau 561c3437056SNickeau if (empty($attributes)) { 56204fd306cSNickeau throw new ExceptionBadState("The page database attribute passed should not be empty"); 563c3437056SNickeau } 564c3437056SNickeau 565c3437056SNickeau $values = []; 566c3437056SNickeau $columnClauses = []; 567c3437056SNickeau foreach ($attributes as $key => $value) { 568c3437056SNickeau if (is_array($value)) { 56904fd306cSNickeau throw new ExceptionRuntime("The attribute ($key) has value that is an array (" . implode(", ", $value) . ")"); 570c3437056SNickeau } 571c3437056SNickeau $columnClauses[] = "$key = ?"; 572c3437056SNickeau $values[$key] = $value; 573c3437056SNickeau } 574c3437056SNickeau 575c3437056SNickeau /** 576c3437056SNickeau * Primary key has moved during the time 577c3437056SNickeau * It should be the UUID but not for older version 578c3437056SNickeau * 579c3437056SNickeau * If the primary key is null, no record was found 580c3437056SNickeau */ 581c3437056SNickeau $rowId = $this->getRowId(); 582c3437056SNickeau if ($rowId !== null) { 583c3437056SNickeau /** 584c3437056SNickeau * We just add the primary key 585c3437056SNickeau * otherwise as this is a associative 586c3437056SNickeau * array, we will miss a value for the update statement 587c3437056SNickeau */ 588c3437056SNickeau $values[] = $rowId; 589c3437056SNickeau 590c3437056SNickeau $updateStatement = "update PAGES SET " . implode(", ", $columnClauses) . " where ROWID = ?"; 591c3437056SNickeau $request = $this->sqlite 592c3437056SNickeau ->createRequest() 593c3437056SNickeau ->setQueryParametrized($updateStatement, $values); 594c3437056SNickeau $countChanges = 0; 595c3437056SNickeau try { 596c3437056SNickeau $countChanges = $request 597c3437056SNickeau ->execute() 598c3437056SNickeau ->getChangeCount(); 59904fd306cSNickeau } catch (ExceptionCompile $e) { 60004fd306cSNickeau throw new ExceptionBadState("There was a problem during the page attribute updates. : {$e->getMessage()}"); 601c3437056SNickeau } finally { 602c3437056SNickeau $request->close(); 603c3437056SNickeau } 604c3437056SNickeau if ($countChanges !== 1) { 60504fd306cSNickeau // internal error 60604fd306cSNickeau LogUtility::error("The database replication has not updated exactly 1 record but ($countChanges) record", \action_plugin_combo_indexer::CANONICAL); 607c3437056SNickeau } 608c3437056SNickeau 609c3437056SNickeau } else { 610c3437056SNickeau 611c3437056SNickeau /** 612c3437056SNickeau * Creation 613c3437056SNickeau */ 61404fd306cSNickeau if ($this->markupPath === null) { 61504fd306cSNickeau throw new ExceptionBadState("The page should be defined to create a page database row"); 616c3437056SNickeau } 61704fd306cSNickeau 61804fd306cSNickeau /** 61904fd306cSNickeau * If the user copy a frontmatter with the same page id abbr, we got a problem 62004fd306cSNickeau */ 62104fd306cSNickeau $pageIdAbbr = $values[PageId::PAGE_ID_ABBR_ATTRIBUTE]; 62204fd306cSNickeau if ($pageIdAbbr == null) { 62304fd306cSNickeau $pageId = $values[PageId::getPersistentName()]; 62404fd306cSNickeau if ($pageId === null) { 62504fd306cSNickeau throw new ExceptionBadState("You can't insert a page in the database without a page id"); 62604fd306cSNickeau } 62704fd306cSNickeau $pageIdAbbr = PageId::getAbbreviated($pageId); 62804fd306cSNickeau $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $pageIdAbbr; 62904fd306cSNickeau } 63004fd306cSNickeau 63104fd306cSNickeau $databasePage = DatabasePageRow::createFromPageIdAbbr($pageIdAbbr); 63204fd306cSNickeau if ($databasePage->exists()) { 63304fd306cSNickeau $duplicatePage = $databasePage->getMarkupPath(); 63404fd306cSNickeau if ($duplicatePage->getPathObject()->toUriString() === $this->markupPath->toUriString()) { 63504fd306cSNickeau $message = "The page ($this->markupPath) is already in the database with the uid ($pageIdAbbr)."; 63604fd306cSNickeau } else { 63704fd306cSNickeau $message = "The page ($this->markupPath) cannot be replicated to the database because it has the same page id abbreviation ($pageIdAbbr) than the page ($duplicatePage)"; 63804fd306cSNickeau } 63904fd306cSNickeau throw new ExceptionBadState($message); 64004fd306cSNickeau } 64104fd306cSNickeau 64204fd306cSNickeau $values[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] = $this->markupPath->getPathObject()->getWikiId(); 64304fd306cSNickeau $values[PagePath::PROPERTY_NAME] = $this->markupPath->getPathObject()->toAbsolutePath()->toAbsoluteId(); 644c3437056SNickeau /** 645c3437056SNickeau * Default implements the auto-canonical feature 646c3437056SNickeau */ 64704fd306cSNickeau try { 64804fd306cSNickeau $values[Canonical::PROPERTY_NAME] = $this->markupPath->getCanonicalOrDefault(); 64904fd306cSNickeau } catch (ExceptionNotFound $e) { 65004fd306cSNickeau $values[Canonical::PROPERTY_NAME] = null; 65104fd306cSNickeau } 652c3437056SNickeau 653c3437056SNickeau /** 654c3437056SNickeau * Analytics 655c3437056SNickeau */ 656c3437056SNickeau if (!isset($values[self::ANALYTICS_ATTRIBUTE])) { 657c3437056SNickeau // otherwise we get an empty string 658c3437056SNickeau // and a json function will not work 659c3437056SNickeau $values[self::ANALYTICS_ATTRIBUTE] = Json::createEmpty()->toPrettyJsonString(); 660c3437056SNickeau } 661c3437056SNickeau 662c3437056SNickeau /** 663c3437056SNickeau * Page Id / Abbr are mandatory for url redirection 664c3437056SNickeau */ 665c3437056SNickeau $this->addPageIdAttributeIfNeeded($values); 666c3437056SNickeau 667c3437056SNickeau $request = $this->sqlite 668c3437056SNickeau ->createRequest() 669c3437056SNickeau ->setTableRow('PAGES', $values); 670c3437056SNickeau try { 671c3437056SNickeau /** 672c3437056SNickeau * rowid is used in {@link DatabasePageRow::exists()} 673c3437056SNickeau * to check if the page exists in the database 674c3437056SNickeau * We update it 675c3437056SNickeau */ 676c3437056SNickeau $this->row[self::ROWID] = $request 677c3437056SNickeau ->execute() 678c3437056SNickeau ->getInsertId(); 679c3437056SNickeau $this->row = array_merge($values, $this->row); 68004fd306cSNickeau } catch (ExceptionCompile $e) { 68104fd306cSNickeau throw new ExceptionBadState("There was a problem during the updateAttributes insert. : {$e->getMessage()}"); 682c3437056SNickeau } finally { 683c3437056SNickeau $request->close(); 684c3437056SNickeau } 685c3437056SNickeau 686c3437056SNickeau } 687c3437056SNickeau 688c3437056SNickeau } 689c3437056SNickeau 690c3437056SNickeau public 691c3437056SNickeau function getDescription() 692c3437056SNickeau { 693c3437056SNickeau return $this->getFromRow(PageDescription::DESCRIPTION_PROPERTY); 694c3437056SNickeau } 695c3437056SNickeau 696c3437056SNickeau 697c3437056SNickeau public 698c3437056SNickeau function getPageName() 699c3437056SNickeau { 700c3437056SNickeau return $this->getFromRow(ResourceName::PROPERTY_NAME); 701c3437056SNickeau } 702c3437056SNickeau 703c3437056SNickeau public 704c3437056SNickeau function exists(): bool 705c3437056SNickeau { 706c3437056SNickeau return $this->getFromRow(self::ROWID) !== null; 707c3437056SNickeau } 708c3437056SNickeau 709c3437056SNickeau /** 710c3437056SNickeau * Called when a page is moved 711c3437056SNickeau * @param $targetId 712c3437056SNickeau */ 713c3437056SNickeau public 714c3437056SNickeau function updatePathAndDokuwikiId($targetId) 715c3437056SNickeau { 716c3437056SNickeau if (!$this->exists()) { 71704fd306cSNickeau LogUtility::error("The `database` page ($this) does not exist and cannot be moved to ($targetId)"); 718c3437056SNickeau } 719c3437056SNickeau 720c3437056SNickeau $path = $targetId; 72104fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 722c3437056SNickeau $attributes = [ 723c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE => $targetId, 724c3437056SNickeau PagePath::PROPERTY_NAME => $path 725c3437056SNickeau ]; 726c3437056SNickeau 727c3437056SNickeau $this->upsertAttributes($attributes); 728c3437056SNickeau 729c3437056SNickeau } 730c3437056SNickeau 731c3437056SNickeau public 732c3437056SNickeau function __toString() 733c3437056SNickeau { 73404fd306cSNickeau return $this->markupPath->__toString(); 735c3437056SNickeau } 736c3437056SNickeau 737c3437056SNickeau 738c3437056SNickeau /** 739c3437056SNickeau * Redirect are now added during a move 740c3437056SNickeau * Not when a duplicate is found. 741c3437056SNickeau * With the advent of the page id, it should never occurs anymore 74204fd306cSNickeau * @param MarkupPath $page 743c3437056SNickeau * @deprecated 2012-10-28 744c3437056SNickeau */ 745c3437056SNickeau private 74604fd306cSNickeau function deleteIfExistsAndAddRedirectAlias(MarkupPath $page): void 747c3437056SNickeau { 748c3437056SNickeau 74904fd306cSNickeau if ($this->markupPath != null) { 750c3437056SNickeau $page->getDatabasePage()->deleteIfExist(); 751c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($page); 752c3437056SNickeau } 753c3437056SNickeau 754c3437056SNickeau } 755c3437056SNickeau 756c3437056SNickeau public 757c3437056SNickeau function getCanonical() 758c3437056SNickeau { 759c3437056SNickeau return $this->getFromRow(Canonical::PROPERTY_NAME); 760c3437056SNickeau } 761c3437056SNickeau 762c3437056SNickeau /** 763c3437056SNickeau * Set the field to their values 764c3437056SNickeau * @param $row 765c3437056SNickeau */ 766c3437056SNickeau public 767c3437056SNickeau function setRow($row) 768c3437056SNickeau { 769c3437056SNickeau if ($row === null) { 770c3437056SNickeau LogUtility::msg("A row should not be null"); 771c3437056SNickeau return; 772c3437056SNickeau } 773c3437056SNickeau if (!is_array($row)) { 774c3437056SNickeau LogUtility::msg("A row should be an array"); 775c3437056SNickeau return; 776c3437056SNickeau } 777c3437056SNickeau 778c3437056SNickeau /** 779c3437056SNickeau * All get function lookup the row 780c3437056SNickeau */ 781c3437056SNickeau $this->row = $row; 782c3437056SNickeau 783c3437056SNickeau 784c3437056SNickeau } 785c3437056SNickeau 786c3437056SNickeau private 787c3437056SNickeau function buildInitObjectFields() 788c3437056SNickeau { 789c3437056SNickeau $this->row = null; 790c3437056SNickeau 791c3437056SNickeau } 792c3437056SNickeau 793c3437056SNickeau public 794c3437056SNickeau function rebuild(): DatabasePageRow 795c3437056SNickeau { 796c3437056SNickeau 79704fd306cSNickeau if ($this->markupPath != null) { 79804fd306cSNickeau $this->markupPath->rebuild(); 79904fd306cSNickeau try { 80004fd306cSNickeau $row = $this->getDatabaseRowFromPage($this->markupPath); 801c3437056SNickeau $this->setRow($row); 80204fd306cSNickeau } catch (ExceptionNotExists $e) { 80304fd306cSNickeau // ok 804c3437056SNickeau } 805c3437056SNickeau } 806c3437056SNickeau return $this; 807c3437056SNickeau 808c3437056SNickeau } 809c3437056SNickeau 810c3437056SNickeau /** 811c3437056SNickeau * @return array - an array of the fix page metadata (ie not derived) 812c3437056SNickeau * Therefore quick to insert/update 813c3437056SNickeau * 814c3437056SNickeau */ 815c3437056SNickeau private 816c3437056SNickeau function getMetaRecord(): array 817c3437056SNickeau { 81804fd306cSNickeau $sourceStore = MetadataDokuWikiStore::getOrCreateFromResource($this->markupPath); 81904fd306cSNickeau $targetStore = MetadataDbStore::getOrCreateFromResource($this->markupPath); 820c3437056SNickeau 821c3437056SNickeau $record = array( 822c3437056SNickeau Canonical::PROPERTY_NAME, 823c3437056SNickeau PagePath::PROPERTY_NAME, 824c3437056SNickeau ResourceName::PROPERTY_NAME, 825c3437056SNickeau PageTitle::TITLE, 826c3437056SNickeau PageH1::PROPERTY_NAME, 827c3437056SNickeau PageDescription::PROPERTY_NAME, 82804fd306cSNickeau CreationDate::PROPERTY_NAME, 829c3437056SNickeau ModificationDate::PROPERTY_NAME, 830c3437056SNickeau PagePublicationDate::PROPERTY_NAME, 831c3437056SNickeau StartDate::PROPERTY_NAME, 832c3437056SNickeau EndDate::PROPERTY_NAME, 833c3437056SNickeau Region::PROPERTY_NAME, 834c3437056SNickeau Lang::PROPERTY_NAME, 835c3437056SNickeau PageType::PROPERTY_NAME, 836c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, 83704fd306cSNickeau PageLevel::PROPERTY_NAME 838c3437056SNickeau ); 839c3437056SNickeau $metaRecord = []; 840c3437056SNickeau foreach ($record as $name) { 84104fd306cSNickeau try { 84204fd306cSNickeau $metadata = Meta\Api\MetadataSystem::getForName($name); 84304fd306cSNickeau } catch (ExceptionNotFound $e) { 84404fd306cSNickeau LogUtility::internalError("The metadata ($name) is unknown", self::CANONICAL); 84504fd306cSNickeau continue; 846c3437056SNickeau } 847c3437056SNickeau $metaRecord[$name] = $metadata 84804fd306cSNickeau ->setResource($this->markupPath) 849c3437056SNickeau ->setReadStore($sourceStore) 850c3437056SNickeau ->buildFromReadStore() 851c3437056SNickeau ->setWriteStore($targetStore) 852c3437056SNickeau ->toStoreValueOrDefault(); // used by the template, the value is or default 85304fd306cSNickeau 854c3437056SNickeau } 855c3437056SNickeau 85604fd306cSNickeau try { 857c3437056SNickeau $this->addPageIdMeta($metaRecord); 85804fd306cSNickeau } catch (ExceptionNotExists $e) { 85904fd306cSNickeau // no page id for non-existent page ok 860c3437056SNickeau } 86104fd306cSNickeau 86204fd306cSNickeau // Is index 86304fd306cSNickeau $metaRecord[self::IS_INDEX_COLUMN] = ($this->markupPath->isIndexPage() === true ? 1 : 0); 86404fd306cSNickeau 865c3437056SNickeau return $metaRecord; 866c3437056SNickeau } 867c3437056SNickeau 868c3437056SNickeau public 869c3437056SNickeau function deleteIfExist(): DatabasePageRow 870c3437056SNickeau { 871c3437056SNickeau if ($this->exists()) { 872c3437056SNickeau $this->delete(); 873c3437056SNickeau } 874c3437056SNickeau return $this; 875c3437056SNickeau } 876c3437056SNickeau 877c3437056SNickeau public 878c3437056SNickeau function getRowId() 879c3437056SNickeau { 880c3437056SNickeau return $this->getFromRow(self::ROWID); 881c3437056SNickeau } 882c3437056SNickeau 88304fd306cSNickeau /** 88404fd306cSNickeau * @throws ExceptionNotFound 88504fd306cSNickeau */ 886c3437056SNickeau private 88704fd306cSNickeau function getDatabaseRowFromPageId(string $pageIdValue) 888c3437056SNickeau { 889c3437056SNickeau 890c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 891c3437056SNickeau $query = $this->getParametrizedLookupQuery($pageIdAttribute); 892c3437056SNickeau $request = Sqlite::createOrGetSqlite() 893c3437056SNickeau ->createRequest() 89404fd306cSNickeau ->setQueryParametrized($query, [$pageIdValue]); 895c3437056SNickeau $rows = []; 896c3437056SNickeau try { 897c3437056SNickeau $rows = $request 898c3437056SNickeau ->execute() 899c3437056SNickeau ->getRows(); 90004fd306cSNickeau } catch (ExceptionCompile $e) { 90104fd306cSNickeau throw new ExceptionRuntimeInternal("Error while retrieving the object by id", self::CANONICAL, 1, $e); 902c3437056SNickeau } finally { 903c3437056SNickeau $request->close(); 904c3437056SNickeau } 905c3437056SNickeau 906c3437056SNickeau switch (sizeof($rows)) { 907c3437056SNickeau case 0: 90804fd306cSNickeau throw new ExceptionNotFound("No object by page id"); 909c3437056SNickeau case 1: 910c3437056SNickeau /** 911c3437056SNickeau * Page Id Collision detection 912c3437056SNickeau */ 91304fd306cSNickeau $rowId = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 91404fd306cSNickeau $this->checkCollision($rowId, $pageIdAttribute, $pageIdValue); 915c3437056SNickeau return $rows[0]; 916c3437056SNickeau default: 917c3437056SNickeau $existingPages = implode(", ", $rows); 91804fd306cSNickeau $message = "The pages ($existingPages) have all the same page id ($pageIdValue)"; 91904fd306cSNickeau throw new ExceptionRuntimeInternal($message, self::CANONICAL); 920c3437056SNickeau } 921c3437056SNickeau 922c3437056SNickeau } 923c3437056SNickeau 924c3437056SNickeau 925c3437056SNickeau private 92604fd306cSNickeau function getParametrizedLookupQuery(string $attributeName): string 927c3437056SNickeau { 928c3437056SNickeau $select = Sqlite::createSelectFromTableAndColumns("pages", self::PAGE_BUILD_ATTRIBUTES); 92904fd306cSNickeau return "$select where $attributeName = ?"; 930c3437056SNickeau } 931c3437056SNickeau 932c3437056SNickeau 93304fd306cSNickeau public 93404fd306cSNickeau function setMarkupPath(MarkupPath $page) 935c3437056SNickeau { 93604fd306cSNickeau $this->markupPath = $page; 937c3437056SNickeau return $this; 938c3437056SNickeau } 939c3437056SNickeau 94004fd306cSNickeau /** 94104fd306cSNickeau * @throws ExceptionNotFound 94204fd306cSNickeau */ 943c3437056SNickeau private 94404fd306cSNickeau function getDatabaseRowFromCanonical($canonicalValue) 945c3437056SNickeau { 94604fd306cSNickeau $canoncialName = Canonical::PROPERTY_NAME; 94704fd306cSNickeau $query = $this->getParametrizedLookupQuery($canoncialName); 948c3437056SNickeau $request = $this->sqlite 949c3437056SNickeau ->createRequest() 95004fd306cSNickeau ->setQueryParametrized($query, [$canonicalValue]); 951c3437056SNickeau $rows = []; 952c3437056SNickeau try { 953c3437056SNickeau $rows = $request 954c3437056SNickeau ->execute() 955c3437056SNickeau ->getRows(); 95604fd306cSNickeau } catch (ExceptionCompile $e) { 95704fd306cSNickeau throw new ExceptionRuntime("An exception has occurred with the page search from CANONICAL. " . $e->getMessage()); 958c3437056SNickeau } finally { 959c3437056SNickeau $request->close(); 960c3437056SNickeau } 961c3437056SNickeau 962c3437056SNickeau switch (sizeof($rows)) { 963c3437056SNickeau case 0: 96404fd306cSNickeau throw new ExceptionNotFound("No canonical row was found"); 965c3437056SNickeau case 1: 966c3437056SNickeau $id = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 96704fd306cSNickeau $this->checkCollision($id, $canoncialName, $canonicalValue); 968c3437056SNickeau return $rows[0]; 969c3437056SNickeau default: 970c3437056SNickeau $existingPages = []; 971c3437056SNickeau foreach ($rows as $row) { 972c3437056SNickeau $id = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 97304fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($id); 974c3437056SNickeau if (!$duplicatePage->exists()) { 975c3437056SNickeau 976c3437056SNickeau $this->deleteIfExistsAndAddRedirectAlias($duplicatePage); 977c3437056SNickeau 978c3437056SNickeau } else { 979c3437056SNickeau 980c3437056SNickeau /** 981c3437056SNickeau * Check if the error may come from the auto-canonical 982c3437056SNickeau * (Never ever save generated data) 983c3437056SNickeau */ 98404fd306cSNickeau $canonicalLastNamesCount = SiteConfig::getConfValue(Canonical::CONF_CANONICAL_LAST_NAMES_COUNT, 0); 985c3437056SNickeau if ($canonicalLastNamesCount > 0) { 98604fd306cSNickeau $this->markupPath->unsetMetadata($canoncialName); 98704fd306cSNickeau $duplicatePage->unsetMetadata($canoncialName); 988c3437056SNickeau } 989c3437056SNickeau 990c3437056SNickeau $existingPages[] = $row; 991c3437056SNickeau } 992c3437056SNickeau } 99304fd306cSNickeau if (sizeof($existingPages) > 1) { 994c3437056SNickeau $existingPages = implode(", ", $existingPages); 99504fd306cSNickeau $message = "The existing pages ($existingPages) have all the same canonical ($canonicalValue), return the first one"; 99604fd306cSNickeau LogUtility::error($message, self::CANONICAL); 997c3437056SNickeau } 99804fd306cSNickeau return $existingPages[0]; 999c3437056SNickeau } 1000c3437056SNickeau } 1001c3437056SNickeau 100204fd306cSNickeau /** 100304fd306cSNickeau * @throws ExceptionNotFound 100404fd306cSNickeau */ 1005c3437056SNickeau private 1006c3437056SNickeau function getDatabaseRowFromPath(string $path): ?array 1007c3437056SNickeau { 100804fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 1009c3437056SNickeau return $this->getDatabaseRowFromAttribute(PagePath::PROPERTY_NAME, $path); 1010c3437056SNickeau } 1011c3437056SNickeau 101204fd306cSNickeau /** 101304fd306cSNickeau * @throws ExceptionNotFound 101404fd306cSNickeau */ 1015c3437056SNickeau private 101604fd306cSNickeau function getDatabaseRowFromDokuWikiId(string $id): array 1017c3437056SNickeau { 1018c3437056SNickeau return $this->getDatabaseRowFromAttribute(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $id); 1019c3437056SNickeau } 1020c3437056SNickeau 102104fd306cSNickeau /** 102204fd306cSNickeau * @throws ExceptionNotFound 102304fd306cSNickeau */ 1024c3437056SNickeau public 1025c3437056SNickeau function getDatabaseRowFromAttribute(string $attribute, string $value) 1026c3437056SNickeau { 1027c3437056SNickeau $query = $this->getParametrizedLookupQuery($attribute); 1028c3437056SNickeau $request = $this->sqlite 1029c3437056SNickeau ->createRequest() 1030c3437056SNickeau ->setQueryParametrized($query, [$value]); 1031c3437056SNickeau $rows = []; 1032c3437056SNickeau try { 1033c3437056SNickeau $rows = $request 1034c3437056SNickeau ->execute() 1035c3437056SNickeau ->getRows(); 103604fd306cSNickeau } catch (ExceptionCompile $e) { 103704fd306cSNickeau $message = "Internal Error: An exception has occurred with the page search from a PATH: " . $e->getMessage(); 103804fd306cSNickeau LogUtility::log2file($message); 103904fd306cSNickeau throw new ExceptionNotFound($message); 1040c3437056SNickeau } finally { 1041c3437056SNickeau $request->close(); 1042c3437056SNickeau } 1043c3437056SNickeau 1044c3437056SNickeau switch (sizeof($rows)) { 1045c3437056SNickeau case 0: 104604fd306cSNickeau throw new ExceptionNotFound("No database row found for the page"); 1047c3437056SNickeau case 1: 1048c3437056SNickeau $value = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 104904fd306cSNickeau if ($this->markupPath != null && $value !== $this->markupPath->getWikiId()) { 105004fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($value); 1051c3437056SNickeau if (!$duplicatePage->exists()) { 1052c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 1053c3437056SNickeau } else { 105404fd306cSNickeau LogUtility::msg("The page ($this->markupPath) and the page ($value) have the same $attribute ($value)", LogUtility::LVL_MSG_ERROR); 1055c3437056SNickeau } 1056c3437056SNickeau } 1057c3437056SNickeau return $rows[0]; 1058c3437056SNickeau default: 1059c3437056SNickeau $existingPages = []; 1060c3437056SNickeau foreach ($rows as $row) { 1061c3437056SNickeau $value = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 106204fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($value); 1063c3437056SNickeau if (!$duplicatePage->exists()) { 1064c3437056SNickeau 1065c3437056SNickeau $this->deleteIfExistsAndAddRedirectAlias($duplicatePage); 1066c3437056SNickeau 1067c3437056SNickeau } else { 1068c3437056SNickeau $existingPages[] = $row; 1069c3437056SNickeau } 1070c3437056SNickeau } 1071c3437056SNickeau if (sizeof($existingPages) === 1) { 1072c3437056SNickeau return $existingPages[0]; 1073c3437056SNickeau } else { 107404fd306cSNickeau $existingPageIds = array_map( 107504fd306cSNickeau function ($row) { 107604fd306cSNickeau return $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 107704fd306cSNickeau }, 107804fd306cSNickeau $existingPages); 107904fd306cSNickeau $existingPages = implode(", ", $existingPageIds); 108004fd306cSNickeau throw new ExceptionNotFound("The existing pages ($existingPages) have all the same attribute $attribute with the value ($value)", LogUtility::LVL_MSG_ERROR); 1081c3437056SNickeau } 1082c3437056SNickeau } 1083c3437056SNickeau } 1084c3437056SNickeau 1085c3437056SNickeau public 108604fd306cSNickeau function getMarkupPath(): ?MarkupPath 1087c3437056SNickeau { 1088c3437056SNickeau if ( 108904fd306cSNickeau $this->markupPath === null 1090c3437056SNickeau && $this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] !== null 1091c3437056SNickeau ) { 109204fd306cSNickeau $this->markupPath = MarkupPath::createMarkupFromId($this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]); 1093c3437056SNickeau } 109404fd306cSNickeau return $this->markupPath; 1095c3437056SNickeau } 1096c3437056SNickeau 1097c3437056SNickeau private 1098c3437056SNickeau function getDatabaseRowFromAlias($alias): ?array 1099c3437056SNickeau { 1100c3437056SNickeau 1101c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 1102c3437056SNickeau $buildFields = self::PAGE_BUILD_ATTRIBUTES; 1103c3437056SNickeau $fields = array_reduce($buildFields, function ($carry, $element) { 1104c3437056SNickeau if ($carry !== null) { 1105c3437056SNickeau return "$carry, p.{$element}"; 1106c3437056SNickeau } else { 1107c3437056SNickeau return "p.{$element}"; 1108c3437056SNickeau } 1109c3437056SNickeau }, null); 111004fd306cSNickeau /** @noinspection SqlResolve */ 1111c3437056SNickeau $query = "select {$fields} from PAGES p, PAGE_ALIASES pa where p.{$pageIdAttribute} = pa.{$pageIdAttribute} and pa.PATH = ? "; 1112c3437056SNickeau $request = $this->sqlite 1113c3437056SNickeau ->createRequest() 1114c3437056SNickeau ->setQueryParametrized($query, [$alias]); 1115c3437056SNickeau $rows = []; 1116c3437056SNickeau try { 1117c3437056SNickeau $rows = $request 1118c3437056SNickeau ->execute() 1119c3437056SNickeau ->getRows(); 112004fd306cSNickeau } catch (ExceptionCompile $e) { 1121c3437056SNickeau LogUtility::msg("An exception has occurred with the alias selection query. {$e->getMessage()}"); 1122c3437056SNickeau return null; 1123c3437056SNickeau } finally { 1124c3437056SNickeau $request->close(); 1125c3437056SNickeau } 1126c3437056SNickeau switch (sizeof($rows)) { 1127c3437056SNickeau case 0: 1128c3437056SNickeau return null; 1129c3437056SNickeau case 1: 1130c3437056SNickeau return $rows[0]; 1131c3437056SNickeau default: 1132c3437056SNickeau $id = $rows[0]['ID']; 1133c3437056SNickeau $pages = implode(",", 1134c3437056SNickeau array_map( 1135c3437056SNickeau function ($row) { 1136c3437056SNickeau return $row['ID']; 1137c3437056SNickeau }, 1138c3437056SNickeau $rows 1139c3437056SNickeau ) 1140c3437056SNickeau ); 1141c3437056SNickeau LogUtility::msg("For the alias $alias, there is more than one page defined ($pages), the first one ($id) was used", LogUtility::LVL_MSG_ERROR, Aliases::PROPERTY_NAME); 1142c3437056SNickeau return $rows[0]; 1143c3437056SNickeau } 1144c3437056SNickeau } 1145c3437056SNickeau 1146c3437056SNickeau 1147c3437056SNickeau /** 1148c3437056SNickeau * Utility function 114904fd306cSNickeau * @param MarkupPath $pageAlias 1150c3437056SNickeau */ 1151c3437056SNickeau private 115204fd306cSNickeau function addRedirectAliasWhileBuildingRow(MarkupPath $pageAlias) 1153c3437056SNickeau { 1154c3437056SNickeau 115504fd306cSNickeau $aliasPath = $pageAlias->getPathObject()->toAbsoluteId(); 1156c3437056SNickeau try { 115704fd306cSNickeau Aliases::createForPage($this->markupPath) 1158c3437056SNickeau ->addAlias($aliasPath) 1159c3437056SNickeau ->sendToWriteStore(); 116004fd306cSNickeau } catch (ExceptionCompile $e) { 1161c3437056SNickeau // we don't throw while getting 116204fd306cSNickeau LogUtility::msg("Unable to add the alias ($aliasPath) for the page ($this->markupPath)"); 1163c3437056SNickeau } 1164c3437056SNickeau 1165c3437056SNickeau } 1166c3437056SNickeau 1167c3437056SNickeau private 1168c3437056SNickeau function addPageIdAttributeIfNeeded(array &$values) 1169c3437056SNickeau { 1170c3437056SNickeau if (!isset($values[PageId::getPersistentName()])) { 117104fd306cSNickeau $values[PageId::getPersistentName()] = $this->markupPath->getPageId(); 1172c3437056SNickeau } 1173c3437056SNickeau if (!isset($values[PageId::PAGE_ID_ABBR_ATTRIBUTE])) { 117404fd306cSNickeau $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $this->markupPath->getPageIdAbbr(); 1175c3437056SNickeau } 1176c3437056SNickeau } 1177c3437056SNickeau 1178c3437056SNickeau public 1179c3437056SNickeau function getFromRow(string $attribute) 1180c3437056SNickeau { 1181c3437056SNickeau if ($this->row === null) { 1182c3437056SNickeau return null; 1183c3437056SNickeau } 1184c3437056SNickeau 1185c3437056SNickeau if (!array_key_exists($attribute, $this->row)) { 1186c3437056SNickeau /** 1187c3437056SNickeau * An attribute should be added to {@link DatabasePageRow::PAGE_BUILD_ATTRIBUTES} 1188c3437056SNickeau * or in the table 1189c3437056SNickeau */ 119004fd306cSNickeau throw new ExceptionRuntime("The metadata ($attribute) was not found in the returned database row.", $this->getCanonical()); 1191c3437056SNickeau } 1192c3437056SNickeau 1193c3437056SNickeau $value = $this->row[$attribute]; 1194c3437056SNickeau 1195c3437056SNickeau if ($value !== null) { 1196c3437056SNickeau return $value; 1197c3437056SNickeau } 1198c3437056SNickeau 1199c3437056SNickeau // don't know why but the sqlite plugin returns them uppercase 1200c3437056SNickeau // rowid is returned lowercase from the sqlite plugin 1201c3437056SNickeau $upperAttribute = strtoupper($attribute); 1202c3437056SNickeau return $this->row[$upperAttribute]; 1203c3437056SNickeau 1204c3437056SNickeau } 1205c3437056SNickeau 1206c3437056SNickeau 12074cadd4f8SNickeau /** 120804fd306cSNickeau * @throws ExceptionCompile 12094cadd4f8SNickeau */ 121004fd306cSNickeau public 121104fd306cSNickeau function replicateAnalytics() 1212c3437056SNickeau { 1213c3437056SNickeau 1214c3437056SNickeau try { 121504fd306cSNickeau $fetchPath = $this->markupPath->fetchAnalyticsPath(); 121604fd306cSNickeau $analyticsJson = Json::createFromPath($fetchPath); 121704fd306cSNickeau } catch (ExceptionCompile $e) { 121804fd306cSNickeau if (PluginUtility::isDevOrTest()) { 121904fd306cSNickeau throw $e; 122004fd306cSNickeau } 122104fd306cSNickeau throw new ExceptionCompile("Unable to get the analytics document", self::CANONICAL, 0, $e); 1222c3437056SNickeau } 1223c3437056SNickeau 1224c3437056SNickeau /** 1225c3437056SNickeau * Replication Date 1226c3437056SNickeau */ 122704fd306cSNickeau $replicationDateMeta = ReplicationDate::createFromPage($this->markupPath) 1228c3437056SNickeau ->setWriteStore(MetadataDbStore::class) 12294cadd4f8SNickeau ->setValue(new DateTime()); 1230c3437056SNickeau 1231c3437056SNickeau /** 1232c3437056SNickeau * Analytics 1233c3437056SNickeau */ 1234c3437056SNickeau $analyticsJsonAsString = $analyticsJson->toPrettyJsonString(); 1235c3437056SNickeau $analyticsJsonAsArray = $analyticsJson->toArray(); 1236c3437056SNickeau 1237c3437056SNickeau /** 1238c3437056SNickeau * Record 1239c3437056SNickeau */ 1240c3437056SNickeau $record[self::ANALYTICS_ATTRIBUTE] = $analyticsJsonAsString; 124104fd306cSNickeau $record['IS_LOW_QUALITY'] = ($this->markupPath->isLowQualityPage() === true ? 1 : 0); 124204fd306cSNickeau $record['WORD_COUNT'] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][renderer_plugin_combo_analytics::WORD_COUNT]; 124304fd306cSNickeau $record[BacklinkCount::getPersistentName()] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][BacklinkCount::getPersistentName()]; 1244c3437056SNickeau $record[$replicationDateMeta::getPersistentName()] = $replicationDateMeta->toStoreValue(); 1245c3437056SNickeau $this->upsertAttributes($record); 1246c3437056SNickeau } 1247c3437056SNickeau 124804fd306cSNickeau private 124904fd306cSNickeau function checkCollision($wikiIdInDatabase, $attribute, $value) 125004fd306cSNickeau { 125104fd306cSNickeau if ($this->markupPath === null) { 125204fd306cSNickeau return; 125304fd306cSNickeau } 125404fd306cSNickeau try { 125504fd306cSNickeau $markupWikiId = $this->markupPath->toWikiPath()->getWikiId(); 125604fd306cSNickeau } catch (ExceptionCast $e) { 125704fd306cSNickeau return; 125804fd306cSNickeau } 125904fd306cSNickeau if ($wikiIdInDatabase !== $markupWikiId) { 126004fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($wikiIdInDatabase); 126104fd306cSNickeau if (!FileSystems::exists($duplicatePage)) { 126204fd306cSNickeau // Move 126304fd306cSNickeau LogUtility::info("The non-existing duplicate page ($wikiIdInDatabase) has been added as redirect alias for the page ($this->markupPath)", self::CANONICAL); 126404fd306cSNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 126504fd306cSNickeau } else { 126604fd306cSNickeau // This can happens if two page were created not on the same website 126704fd306cSNickeau // of if the sqlite database was deleted and rebuilt. 126804fd306cSNickeau // The chance is really, really low 126904fd306cSNickeau $errorMessage = "The page ($this->markupPath) and the page ($wikiIdInDatabase) have the same $attribute value ($value)"; 127004fd306cSNickeau throw new ExceptionRuntimeInternal($errorMessage, self::CANONICAL); 127104fd306cSNickeau // What to do ? 127204fd306cSNickeau // The database does not allow two page id with the same value 127304fd306cSNickeau // If it happens, ugh, ugh, ..., a replication process between website may be. 127404fd306cSNickeau } 127504fd306cSNickeau } 127604fd306cSNickeau } 127704fd306cSNickeau 1278c3437056SNickeau 1279c3437056SNickeau} 1280