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(); 3004ebc3257Sgerardnico $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 */ 621be61a7dfSgerardnico $pageIdAbbr = $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] ?? null; 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 */ 656be61a7dfSgerardnico $analyticsAttributeValue = $values[self::ANALYTICS_ATTRIBUTE] ?? null; 657be61a7dfSgerardnico if (!isset($analyticsAttributeValue)) { 658c3437056SNickeau // otherwise we get an empty string 659c3437056SNickeau // and a json function will not work 660c3437056SNickeau $values[self::ANALYTICS_ATTRIBUTE] = Json::createEmpty()->toPrettyJsonString(); 661c3437056SNickeau } 662c3437056SNickeau 663c3437056SNickeau /** 664c3437056SNickeau * Page Id / Abbr are mandatory for url redirection 665c3437056SNickeau */ 666c3437056SNickeau $this->addPageIdAttributeIfNeeded($values); 667c3437056SNickeau 668c3437056SNickeau $request = $this->sqlite 669c3437056SNickeau ->createRequest() 670c3437056SNickeau ->setTableRow('PAGES', $values); 671c3437056SNickeau try { 672c3437056SNickeau /** 673c3437056SNickeau * rowid is used in {@link DatabasePageRow::exists()} 674c3437056SNickeau * to check if the page exists in the database 675c3437056SNickeau * We update it 676c3437056SNickeau */ 677c3437056SNickeau $this->row[self::ROWID] = $request 678c3437056SNickeau ->execute() 679c3437056SNickeau ->getInsertId(); 680c3437056SNickeau $this->row = array_merge($values, $this->row); 68104fd306cSNickeau } catch (ExceptionCompile $e) { 68204fd306cSNickeau throw new ExceptionBadState("There was a problem during the updateAttributes insert. : {$e->getMessage()}"); 683c3437056SNickeau } finally { 684c3437056SNickeau $request->close(); 685c3437056SNickeau } 686c3437056SNickeau 687c3437056SNickeau } 688c3437056SNickeau 689c3437056SNickeau } 690c3437056SNickeau 691c3437056SNickeau public 692c3437056SNickeau function getDescription() 693c3437056SNickeau { 694c3437056SNickeau return $this->getFromRow(PageDescription::DESCRIPTION_PROPERTY); 695c3437056SNickeau } 696c3437056SNickeau 697c3437056SNickeau 698c3437056SNickeau public 699c3437056SNickeau function getPageName() 700c3437056SNickeau { 701c3437056SNickeau return $this->getFromRow(ResourceName::PROPERTY_NAME); 702c3437056SNickeau } 703c3437056SNickeau 704c3437056SNickeau public 705c3437056SNickeau function exists(): bool 706c3437056SNickeau { 707c3437056SNickeau return $this->getFromRow(self::ROWID) !== null; 708c3437056SNickeau } 709c3437056SNickeau 710c3437056SNickeau /** 711c3437056SNickeau * Called when a page is moved 712c3437056SNickeau * @param $targetId 713c3437056SNickeau */ 714c3437056SNickeau public 715c3437056SNickeau function updatePathAndDokuwikiId($targetId) 716c3437056SNickeau { 717c3437056SNickeau if (!$this->exists()) { 71804fd306cSNickeau LogUtility::error("The `database` page ($this) does not exist and cannot be moved to ($targetId)"); 719c3437056SNickeau } 720c3437056SNickeau 721c3437056SNickeau $path = $targetId; 72204fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 723c3437056SNickeau $attributes = [ 724c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE => $targetId, 725c3437056SNickeau PagePath::PROPERTY_NAME => $path 726c3437056SNickeau ]; 727c3437056SNickeau 728c3437056SNickeau $this->upsertAttributes($attributes); 729c3437056SNickeau 730c3437056SNickeau } 731c3437056SNickeau 732c3437056SNickeau public 733c3437056SNickeau function __toString() 734c3437056SNickeau { 73504fd306cSNickeau return $this->markupPath->__toString(); 736c3437056SNickeau } 737c3437056SNickeau 738c3437056SNickeau 739c3437056SNickeau /** 740c3437056SNickeau * Redirect are now added during a move 741c3437056SNickeau * Not when a duplicate is found. 742c3437056SNickeau * With the advent of the page id, it should never occurs anymore 74304fd306cSNickeau * @param MarkupPath $page 744c3437056SNickeau * @deprecated 2012-10-28 745c3437056SNickeau */ 746c3437056SNickeau private 74704fd306cSNickeau function deleteIfExistsAndAddRedirectAlias(MarkupPath $page): void 748c3437056SNickeau { 749c3437056SNickeau 75004fd306cSNickeau if ($this->markupPath != null) { 751c3437056SNickeau $page->getDatabasePage()->deleteIfExist(); 752c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($page); 753c3437056SNickeau } 754c3437056SNickeau 755c3437056SNickeau } 756c3437056SNickeau 757c3437056SNickeau public 758c3437056SNickeau function getCanonical() 759c3437056SNickeau { 760c3437056SNickeau return $this->getFromRow(Canonical::PROPERTY_NAME); 761c3437056SNickeau } 762c3437056SNickeau 763c3437056SNickeau /** 764c3437056SNickeau * Set the field to their values 765c3437056SNickeau * @param $row 766c3437056SNickeau */ 767c3437056SNickeau public 768c3437056SNickeau function setRow($row) 769c3437056SNickeau { 770c3437056SNickeau if ($row === null) { 771c3437056SNickeau LogUtility::msg("A row should not be null"); 772c3437056SNickeau return; 773c3437056SNickeau } 774c3437056SNickeau if (!is_array($row)) { 775c3437056SNickeau LogUtility::msg("A row should be an array"); 776c3437056SNickeau return; 777c3437056SNickeau } 778c3437056SNickeau 779c3437056SNickeau /** 780c3437056SNickeau * All get function lookup the row 781c3437056SNickeau */ 782c3437056SNickeau $this->row = $row; 783c3437056SNickeau 784c3437056SNickeau 785c3437056SNickeau } 786c3437056SNickeau 787c3437056SNickeau private 788c3437056SNickeau function buildInitObjectFields() 789c3437056SNickeau { 790c3437056SNickeau $this->row = null; 791c3437056SNickeau 792c3437056SNickeau } 793c3437056SNickeau 794c3437056SNickeau public 795c3437056SNickeau function rebuild(): DatabasePageRow 796c3437056SNickeau { 797c3437056SNickeau 79804fd306cSNickeau if ($this->markupPath != null) { 79904fd306cSNickeau $this->markupPath->rebuild(); 80004fd306cSNickeau try { 80104fd306cSNickeau $row = $this->getDatabaseRowFromPage($this->markupPath); 802c3437056SNickeau $this->setRow($row); 80304fd306cSNickeau } catch (ExceptionNotExists $e) { 80404fd306cSNickeau // ok 805c3437056SNickeau } 806c3437056SNickeau } 807c3437056SNickeau return $this; 808c3437056SNickeau 809c3437056SNickeau } 810c3437056SNickeau 811c3437056SNickeau /** 812c3437056SNickeau * @return array - an array of the fix page metadata (ie not derived) 813c3437056SNickeau * Therefore quick to insert/update 814c3437056SNickeau * 815c3437056SNickeau */ 816c3437056SNickeau private 817c3437056SNickeau function getMetaRecord(): array 818c3437056SNickeau { 81904fd306cSNickeau $sourceStore = MetadataDokuWikiStore::getOrCreateFromResource($this->markupPath); 82004fd306cSNickeau $targetStore = MetadataDbStore::getOrCreateFromResource($this->markupPath); 821c3437056SNickeau 822c3437056SNickeau $record = array( 823c3437056SNickeau Canonical::PROPERTY_NAME, 824c3437056SNickeau PagePath::PROPERTY_NAME, 825c3437056SNickeau ResourceName::PROPERTY_NAME, 826c3437056SNickeau PageTitle::TITLE, 827c3437056SNickeau PageH1::PROPERTY_NAME, 828c3437056SNickeau PageDescription::PROPERTY_NAME, 82904fd306cSNickeau CreationDate::PROPERTY_NAME, 830c3437056SNickeau ModificationDate::PROPERTY_NAME, 831c3437056SNickeau PagePublicationDate::PROPERTY_NAME, 832c3437056SNickeau StartDate::PROPERTY_NAME, 833c3437056SNickeau EndDate::PROPERTY_NAME, 834c3437056SNickeau Region::PROPERTY_NAME, 835c3437056SNickeau Lang::PROPERTY_NAME, 836c3437056SNickeau PageType::PROPERTY_NAME, 837c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, 83804fd306cSNickeau PageLevel::PROPERTY_NAME 839c3437056SNickeau ); 840c3437056SNickeau $metaRecord = []; 841c3437056SNickeau foreach ($record as $name) { 84204fd306cSNickeau try { 84304fd306cSNickeau $metadata = Meta\Api\MetadataSystem::getForName($name); 84404fd306cSNickeau } catch (ExceptionNotFound $e) { 84504fd306cSNickeau LogUtility::internalError("The metadata ($name) is unknown", self::CANONICAL); 84604fd306cSNickeau continue; 847c3437056SNickeau } 848c3437056SNickeau $metaRecord[$name] = $metadata 84904fd306cSNickeau ->setResource($this->markupPath) 850c3437056SNickeau ->setReadStore($sourceStore) 851c3437056SNickeau ->buildFromReadStore() 852c3437056SNickeau ->setWriteStore($targetStore) 853c3437056SNickeau ->toStoreValueOrDefault(); // used by the template, the value is or default 85404fd306cSNickeau 855c3437056SNickeau } 856c3437056SNickeau 85704fd306cSNickeau try { 858c3437056SNickeau $this->addPageIdMeta($metaRecord); 85904fd306cSNickeau } catch (ExceptionNotExists $e) { 86004fd306cSNickeau // no page id for non-existent page ok 861c3437056SNickeau } 86204fd306cSNickeau 86304fd306cSNickeau // Is index 86404fd306cSNickeau $metaRecord[self::IS_INDEX_COLUMN] = ($this->markupPath->isIndexPage() === true ? 1 : 0); 86504fd306cSNickeau 866c3437056SNickeau return $metaRecord; 867c3437056SNickeau } 868c3437056SNickeau 869c3437056SNickeau public 870c3437056SNickeau function deleteIfExist(): DatabasePageRow 871c3437056SNickeau { 872c3437056SNickeau if ($this->exists()) { 873c3437056SNickeau $this->delete(); 874c3437056SNickeau } 875c3437056SNickeau return $this; 876c3437056SNickeau } 877c3437056SNickeau 878c3437056SNickeau public 879c3437056SNickeau function getRowId() 880c3437056SNickeau { 881c3437056SNickeau return $this->getFromRow(self::ROWID); 882c3437056SNickeau } 883c3437056SNickeau 88404fd306cSNickeau /** 88504fd306cSNickeau * @throws ExceptionNotFound 88604fd306cSNickeau */ 887c3437056SNickeau private 88804fd306cSNickeau function getDatabaseRowFromPageId(string $pageIdValue) 889c3437056SNickeau { 890c3437056SNickeau 891c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 892c3437056SNickeau $query = $this->getParametrizedLookupQuery($pageIdAttribute); 893c3437056SNickeau $request = Sqlite::createOrGetSqlite() 894c3437056SNickeau ->createRequest() 89504fd306cSNickeau ->setQueryParametrized($query, [$pageIdValue]); 896c3437056SNickeau $rows = []; 897c3437056SNickeau try { 898c3437056SNickeau $rows = $request 899c3437056SNickeau ->execute() 900c3437056SNickeau ->getRows(); 90104fd306cSNickeau } catch (ExceptionCompile $e) { 90204fd306cSNickeau throw new ExceptionRuntimeInternal("Error while retrieving the object by id", self::CANONICAL, 1, $e); 903c3437056SNickeau } finally { 904c3437056SNickeau $request->close(); 905c3437056SNickeau } 906c3437056SNickeau 907c3437056SNickeau switch (sizeof($rows)) { 908c3437056SNickeau case 0: 90904fd306cSNickeau throw new ExceptionNotFound("No object by page id"); 910c3437056SNickeau case 1: 911c3437056SNickeau /** 912c3437056SNickeau * Page Id Collision detection 913c3437056SNickeau */ 91404fd306cSNickeau $rowId = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 91504fd306cSNickeau $this->checkCollision($rowId, $pageIdAttribute, $pageIdValue); 916c3437056SNickeau return $rows[0]; 917c3437056SNickeau default: 918c3437056SNickeau $existingPages = implode(", ", $rows); 91904fd306cSNickeau $message = "The pages ($existingPages) have all the same page id ($pageIdValue)"; 92004fd306cSNickeau throw new ExceptionRuntimeInternal($message, self::CANONICAL); 921c3437056SNickeau } 922c3437056SNickeau 923c3437056SNickeau } 924c3437056SNickeau 925c3437056SNickeau 926c3437056SNickeau private 92704fd306cSNickeau function getParametrizedLookupQuery(string $attributeName): string 928c3437056SNickeau { 929c3437056SNickeau $select = Sqlite::createSelectFromTableAndColumns("pages", self::PAGE_BUILD_ATTRIBUTES); 93004fd306cSNickeau return "$select where $attributeName = ?"; 931c3437056SNickeau } 932c3437056SNickeau 933c3437056SNickeau 93404fd306cSNickeau public 93504fd306cSNickeau function setMarkupPath(MarkupPath $page) 936c3437056SNickeau { 93704fd306cSNickeau $this->markupPath = $page; 938c3437056SNickeau return $this; 939c3437056SNickeau } 940c3437056SNickeau 94104fd306cSNickeau /** 94204fd306cSNickeau * @throws ExceptionNotFound 94304fd306cSNickeau */ 944c3437056SNickeau private 94504fd306cSNickeau function getDatabaseRowFromCanonical($canonicalValue) 946c3437056SNickeau { 94704fd306cSNickeau $canoncialName = Canonical::PROPERTY_NAME; 94804fd306cSNickeau $query = $this->getParametrizedLookupQuery($canoncialName); 949c3437056SNickeau $request = $this->sqlite 950c3437056SNickeau ->createRequest() 95104fd306cSNickeau ->setQueryParametrized($query, [$canonicalValue]); 952c3437056SNickeau $rows = []; 953c3437056SNickeau try { 954c3437056SNickeau $rows = $request 955c3437056SNickeau ->execute() 956c3437056SNickeau ->getRows(); 95704fd306cSNickeau } catch (ExceptionCompile $e) { 95804fd306cSNickeau throw new ExceptionRuntime("An exception has occurred with the page search from CANONICAL. " . $e->getMessage()); 959c3437056SNickeau } finally { 960c3437056SNickeau $request->close(); 961c3437056SNickeau } 962c3437056SNickeau 963c3437056SNickeau switch (sizeof($rows)) { 964c3437056SNickeau case 0: 96504fd306cSNickeau throw new ExceptionNotFound("No canonical row was found"); 966c3437056SNickeau case 1: 967c3437056SNickeau $id = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 96804fd306cSNickeau $this->checkCollision($id, $canoncialName, $canonicalValue); 969c3437056SNickeau return $rows[0]; 970c3437056SNickeau default: 971c3437056SNickeau $existingPages = []; 972c3437056SNickeau foreach ($rows as $row) { 973c3437056SNickeau $id = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 97404fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($id); 975c3437056SNickeau if (!$duplicatePage->exists()) { 976c3437056SNickeau 977c3437056SNickeau $this->deleteIfExistsAndAddRedirectAlias($duplicatePage); 978c3437056SNickeau 979c3437056SNickeau } else { 980c3437056SNickeau 981c3437056SNickeau /** 982c3437056SNickeau * Check if the error may come from the auto-canonical 983c3437056SNickeau * (Never ever save generated data) 984c3437056SNickeau */ 98504fd306cSNickeau $canonicalLastNamesCount = SiteConfig::getConfValue(Canonical::CONF_CANONICAL_LAST_NAMES_COUNT, 0); 986c3437056SNickeau if ($canonicalLastNamesCount > 0) { 98704fd306cSNickeau $this->markupPath->unsetMetadata($canoncialName); 98804fd306cSNickeau $duplicatePage->unsetMetadata($canoncialName); 989c3437056SNickeau } 990c3437056SNickeau 991c3437056SNickeau $existingPages[] = $row; 992c3437056SNickeau } 993c3437056SNickeau } 99404fd306cSNickeau if (sizeof($existingPages) > 1) { 995c3437056SNickeau $existingPages = implode(", ", $existingPages); 99604fd306cSNickeau $message = "The existing pages ($existingPages) have all the same canonical ($canonicalValue), return the first one"; 99704fd306cSNickeau LogUtility::error($message, self::CANONICAL); 998c3437056SNickeau } 99904fd306cSNickeau return $existingPages[0]; 1000c3437056SNickeau } 1001c3437056SNickeau } 1002c3437056SNickeau 100304fd306cSNickeau /** 100404fd306cSNickeau * @throws ExceptionNotFound 100504fd306cSNickeau */ 1006c3437056SNickeau private 1007c3437056SNickeau function getDatabaseRowFromPath(string $path): ?array 1008c3437056SNickeau { 100904fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 1010c3437056SNickeau return $this->getDatabaseRowFromAttribute(PagePath::PROPERTY_NAME, $path); 1011c3437056SNickeau } 1012c3437056SNickeau 101304fd306cSNickeau /** 101404fd306cSNickeau * @throws ExceptionNotFound 101504fd306cSNickeau */ 1016c3437056SNickeau private 101704fd306cSNickeau function getDatabaseRowFromDokuWikiId(string $id): array 1018c3437056SNickeau { 1019c3437056SNickeau return $this->getDatabaseRowFromAttribute(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $id); 1020c3437056SNickeau } 1021c3437056SNickeau 102204fd306cSNickeau /** 102304fd306cSNickeau * @throws ExceptionNotFound 102404fd306cSNickeau */ 1025c3437056SNickeau public 1026c3437056SNickeau function getDatabaseRowFromAttribute(string $attribute, string $value) 1027c3437056SNickeau { 1028c3437056SNickeau $query = $this->getParametrizedLookupQuery($attribute); 1029c3437056SNickeau $request = $this->sqlite 1030c3437056SNickeau ->createRequest() 1031c3437056SNickeau ->setQueryParametrized($query, [$value]); 1032c3437056SNickeau $rows = []; 1033c3437056SNickeau try { 1034c3437056SNickeau $rows = $request 1035c3437056SNickeau ->execute() 1036c3437056SNickeau ->getRows(); 103704fd306cSNickeau } catch (ExceptionCompile $e) { 103804fd306cSNickeau $message = "Internal Error: An exception has occurred with the page search from a PATH: " . $e->getMessage(); 103904fd306cSNickeau LogUtility::log2file($message); 104004fd306cSNickeau throw new ExceptionNotFound($message); 1041c3437056SNickeau } finally { 1042c3437056SNickeau $request->close(); 1043c3437056SNickeau } 1044c3437056SNickeau 1045c3437056SNickeau switch (sizeof($rows)) { 1046c3437056SNickeau case 0: 104704fd306cSNickeau throw new ExceptionNotFound("No database row found for the page"); 1048c3437056SNickeau case 1: 1049c3437056SNickeau $value = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 105004fd306cSNickeau if ($this->markupPath != null && $value !== $this->markupPath->getWikiId()) { 105104fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($value); 1052c3437056SNickeau if (!$duplicatePage->exists()) { 1053c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 1054c3437056SNickeau } else { 105504fd306cSNickeau LogUtility::msg("The page ($this->markupPath) and the page ($value) have the same $attribute ($value)", LogUtility::LVL_MSG_ERROR); 1056c3437056SNickeau } 1057c3437056SNickeau } 1058c3437056SNickeau return $rows[0]; 1059c3437056SNickeau default: 1060c3437056SNickeau $existingPages = []; 1061c3437056SNickeau foreach ($rows as $row) { 1062c3437056SNickeau $value = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 106304fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($value); 1064c3437056SNickeau if (!$duplicatePage->exists()) { 1065c3437056SNickeau 1066c3437056SNickeau $this->deleteIfExistsAndAddRedirectAlias($duplicatePage); 1067c3437056SNickeau 1068c3437056SNickeau } else { 1069c3437056SNickeau $existingPages[] = $row; 1070c3437056SNickeau } 1071c3437056SNickeau } 1072c3437056SNickeau if (sizeof($existingPages) === 1) { 1073c3437056SNickeau return $existingPages[0]; 1074c3437056SNickeau } else { 107504fd306cSNickeau $existingPageIds = array_map( 107604fd306cSNickeau function ($row) { 107704fd306cSNickeau return $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 107804fd306cSNickeau }, 107904fd306cSNickeau $existingPages); 108004fd306cSNickeau $existingPages = implode(", ", $existingPageIds); 108104fd306cSNickeau throw new ExceptionNotFound("The existing pages ($existingPages) have all the same attribute $attribute with the value ($value)", LogUtility::LVL_MSG_ERROR); 1082c3437056SNickeau } 1083c3437056SNickeau } 1084c3437056SNickeau } 1085c3437056SNickeau 1086c3437056SNickeau public 108704fd306cSNickeau function getMarkupPath(): ?MarkupPath 1088c3437056SNickeau { 1089*70bbd7f1Sgerardnico if ($this->row === null) { 1090*70bbd7f1Sgerardnico return null; 1091*70bbd7f1Sgerardnico } 1092c3437056SNickeau if ( 109304fd306cSNickeau $this->markupPath === null 1094c3437056SNickeau && $this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] !== null 1095c3437056SNickeau ) { 109604fd306cSNickeau $this->markupPath = MarkupPath::createMarkupFromId($this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]); 1097c3437056SNickeau } 109804fd306cSNickeau return $this->markupPath; 1099c3437056SNickeau } 1100c3437056SNickeau 1101c3437056SNickeau private 1102c3437056SNickeau function getDatabaseRowFromAlias($alias): ?array 1103c3437056SNickeau { 1104c3437056SNickeau 1105c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 1106c3437056SNickeau $buildFields = self::PAGE_BUILD_ATTRIBUTES; 1107c3437056SNickeau $fields = array_reduce($buildFields, function ($carry, $element) { 1108c3437056SNickeau if ($carry !== null) { 1109c3437056SNickeau return "$carry, p.{$element}"; 1110c3437056SNickeau } else { 1111c3437056SNickeau return "p.{$element}"; 1112c3437056SNickeau } 1113c3437056SNickeau }, null); 111404fd306cSNickeau /** @noinspection SqlResolve */ 1115c3437056SNickeau $query = "select {$fields} from PAGES p, PAGE_ALIASES pa where p.{$pageIdAttribute} = pa.{$pageIdAttribute} and pa.PATH = ? "; 1116c3437056SNickeau $request = $this->sqlite 1117c3437056SNickeau ->createRequest() 1118c3437056SNickeau ->setQueryParametrized($query, [$alias]); 1119c3437056SNickeau $rows = []; 1120c3437056SNickeau try { 1121c3437056SNickeau $rows = $request 1122c3437056SNickeau ->execute() 1123c3437056SNickeau ->getRows(); 112404fd306cSNickeau } catch (ExceptionCompile $e) { 1125c3437056SNickeau LogUtility::msg("An exception has occurred with the alias selection query. {$e->getMessage()}"); 1126c3437056SNickeau return null; 1127c3437056SNickeau } finally { 1128c3437056SNickeau $request->close(); 1129c3437056SNickeau } 1130c3437056SNickeau switch (sizeof($rows)) { 1131c3437056SNickeau case 0: 1132c3437056SNickeau return null; 1133c3437056SNickeau case 1: 1134c3437056SNickeau return $rows[0]; 1135c3437056SNickeau default: 1136c3437056SNickeau $id = $rows[0]['ID']; 1137c3437056SNickeau $pages = implode(",", 1138c3437056SNickeau array_map( 1139c3437056SNickeau function ($row) { 1140c3437056SNickeau return $row['ID']; 1141c3437056SNickeau }, 1142c3437056SNickeau $rows 1143c3437056SNickeau ) 1144c3437056SNickeau ); 1145c3437056SNickeau 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); 1146c3437056SNickeau return $rows[0]; 1147c3437056SNickeau } 1148c3437056SNickeau } 1149c3437056SNickeau 1150c3437056SNickeau 1151c3437056SNickeau /** 1152c3437056SNickeau * Utility function 115304fd306cSNickeau * @param MarkupPath $pageAlias 1154c3437056SNickeau */ 1155c3437056SNickeau private 115604fd306cSNickeau function addRedirectAliasWhileBuildingRow(MarkupPath $pageAlias) 1157c3437056SNickeau { 1158c3437056SNickeau 115904fd306cSNickeau $aliasPath = $pageAlias->getPathObject()->toAbsoluteId(); 1160c3437056SNickeau try { 116104fd306cSNickeau Aliases::createForPage($this->markupPath) 1162c3437056SNickeau ->addAlias($aliasPath) 1163c3437056SNickeau ->sendToWriteStore(); 116404fd306cSNickeau } catch (ExceptionCompile $e) { 1165c3437056SNickeau // we don't throw while getting 116604fd306cSNickeau LogUtility::msg("Unable to add the alias ($aliasPath) for the page ($this->markupPath)"); 1167c3437056SNickeau } 1168c3437056SNickeau 1169c3437056SNickeau } 1170c3437056SNickeau 1171c3437056SNickeau private 1172c3437056SNickeau function addPageIdAttributeIfNeeded(array &$values) 1173c3437056SNickeau { 1174c3437056SNickeau if (!isset($values[PageId::getPersistentName()])) { 117504fd306cSNickeau $values[PageId::getPersistentName()] = $this->markupPath->getPageId(); 1176c3437056SNickeau } 1177c3437056SNickeau if (!isset($values[PageId::PAGE_ID_ABBR_ATTRIBUTE])) { 117804fd306cSNickeau $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $this->markupPath->getPageIdAbbr(); 1179c3437056SNickeau } 1180c3437056SNickeau } 1181c3437056SNickeau 1182c3437056SNickeau public 1183c3437056SNickeau function getFromRow(string $attribute) 1184c3437056SNickeau { 1185c3437056SNickeau if ($this->row === null) { 1186c3437056SNickeau return null; 1187c3437056SNickeau } 1188c3437056SNickeau 1189c3437056SNickeau if (!array_key_exists($attribute, $this->row)) { 1190c3437056SNickeau /** 1191c3437056SNickeau * An attribute should be added to {@link DatabasePageRow::PAGE_BUILD_ATTRIBUTES} 1192c3437056SNickeau * or in the table 1193c3437056SNickeau */ 119404fd306cSNickeau throw new ExceptionRuntime("The metadata ($attribute) was not found in the returned database row.", $this->getCanonical()); 1195c3437056SNickeau } 1196c3437056SNickeau 1197c3437056SNickeau $value = $this->row[$attribute]; 1198c3437056SNickeau 1199c3437056SNickeau if ($value !== null) { 1200c3437056SNickeau return $value; 1201c3437056SNickeau } 1202c3437056SNickeau 1203c3437056SNickeau // don't know why but the sqlite plugin returns them uppercase 1204c3437056SNickeau // rowid is returned lowercase from the sqlite plugin 1205c3437056SNickeau $upperAttribute = strtoupper($attribute); 1206*70bbd7f1Sgerardnico return $this->row[$upperAttribute] ?? null; 1207c3437056SNickeau 1208c3437056SNickeau } 1209c3437056SNickeau 1210c3437056SNickeau 12114cadd4f8SNickeau /** 121204fd306cSNickeau * @throws ExceptionCompile 12134cadd4f8SNickeau */ 121404fd306cSNickeau public 121504fd306cSNickeau function replicateAnalytics() 1216c3437056SNickeau { 1217c3437056SNickeau 1218c3437056SNickeau try { 121904fd306cSNickeau $fetchPath = $this->markupPath->fetchAnalyticsPath(); 122004fd306cSNickeau $analyticsJson = Json::createFromPath($fetchPath); 122104fd306cSNickeau } catch (ExceptionCompile $e) { 122204fd306cSNickeau if (PluginUtility::isDevOrTest()) { 122304fd306cSNickeau throw $e; 122404fd306cSNickeau } 122504fd306cSNickeau throw new ExceptionCompile("Unable to get the analytics document", self::CANONICAL, 0, $e); 1226c3437056SNickeau } 1227c3437056SNickeau 1228c3437056SNickeau /** 1229c3437056SNickeau * Replication Date 1230c3437056SNickeau */ 123104fd306cSNickeau $replicationDateMeta = ReplicationDate::createFromPage($this->markupPath) 1232c3437056SNickeau ->setWriteStore(MetadataDbStore::class) 12334cadd4f8SNickeau ->setValue(new DateTime()); 1234c3437056SNickeau 1235c3437056SNickeau /** 1236c3437056SNickeau * Analytics 1237c3437056SNickeau */ 1238c3437056SNickeau $analyticsJsonAsString = $analyticsJson->toPrettyJsonString(); 1239c3437056SNickeau $analyticsJsonAsArray = $analyticsJson->toArray(); 1240c3437056SNickeau 1241c3437056SNickeau /** 1242c3437056SNickeau * Record 1243c3437056SNickeau */ 1244c3437056SNickeau $record[self::ANALYTICS_ATTRIBUTE] = $analyticsJsonAsString; 124504fd306cSNickeau $record['IS_LOW_QUALITY'] = ($this->markupPath->isLowQualityPage() === true ? 1 : 0); 124604fd306cSNickeau $record['WORD_COUNT'] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][renderer_plugin_combo_analytics::WORD_COUNT]; 124704fd306cSNickeau $record[BacklinkCount::getPersistentName()] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][BacklinkCount::getPersistentName()]; 1248c3437056SNickeau $record[$replicationDateMeta::getPersistentName()] = $replicationDateMeta->toStoreValue(); 1249c3437056SNickeau $this->upsertAttributes($record); 1250c3437056SNickeau } 1251c3437056SNickeau 125204fd306cSNickeau private 125304fd306cSNickeau function checkCollision($wikiIdInDatabase, $attribute, $value) 125404fd306cSNickeau { 125504fd306cSNickeau if ($this->markupPath === null) { 125604fd306cSNickeau return; 125704fd306cSNickeau } 125804fd306cSNickeau try { 125904fd306cSNickeau $markupWikiId = $this->markupPath->toWikiPath()->getWikiId(); 126004fd306cSNickeau } catch (ExceptionCast $e) { 126104fd306cSNickeau return; 126204fd306cSNickeau } 126304fd306cSNickeau if ($wikiIdInDatabase !== $markupWikiId) { 126404fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($wikiIdInDatabase); 126504fd306cSNickeau if (!FileSystems::exists($duplicatePage)) { 126604fd306cSNickeau // Move 126704fd306cSNickeau LogUtility::info("The non-existing duplicate page ($wikiIdInDatabase) has been added as redirect alias for the page ($this->markupPath)", self::CANONICAL); 126804fd306cSNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 126904fd306cSNickeau } else { 127004fd306cSNickeau // This can happens if two page were created not on the same website 127104fd306cSNickeau // of if the sqlite database was deleted and rebuilt. 127204fd306cSNickeau // The chance is really, really low 127304fd306cSNickeau $errorMessage = "The page ($this->markupPath) and the page ($wikiIdInDatabase) have the same $attribute value ($value)"; 127404fd306cSNickeau throw new ExceptionRuntimeInternal($errorMessage, self::CANONICAL); 127504fd306cSNickeau // What to do ? 127604fd306cSNickeau // The database does not allow two page id with the same value 127704fd306cSNickeau // If it happens, ugh, ugh, ..., a replication process between website may be. 127804fd306cSNickeau } 127904fd306cSNickeau } 128004fd306cSNickeau } 128104fd306cSNickeau 1282c3437056SNickeau 1283c3437056SNickeau} 1284