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 205031d4b49Sgerardnico public static function createFromRowId(string $rowId): DatabasePageRow 206031d4b49Sgerardnico { 207031d4b49Sgerardnico 208031d4b49Sgerardnico $databasePage = new DatabasePageRow(); 209031d4b49Sgerardnico try { 210031d4b49Sgerardnico $row = $databasePage->getDatabaseRowFromRowId($rowId); 211031d4b49Sgerardnico $databasePage->setRow($row); 212031d4b49Sgerardnico } catch (ExceptionNotFound|ExceptionSqliteNotAvailable $e) { 213031d4b49Sgerardnico // not found 214031d4b49Sgerardnico } 215031d4b49Sgerardnico return $databasePage; 216031d4b49Sgerardnico 217031d4b49Sgerardnico } 218031d4b49Sgerardnico 21904fd306cSNickeau /** 22004fd306cSNickeau * @param MarkupPath $page 22104fd306cSNickeau * @return DatabasePageRow 22204fd306cSNickeau * @throws ExceptionSqliteNotAvailable - if there is no sqlite available 22304fd306cSNickeau * @noinspection PhpDocRedundantThrowsInspection 22404fd306cSNickeau */ 22504fd306cSNickeau public static function getOrCreateFromPageObject(MarkupPath $page): DatabasePageRow 226c3437056SNickeau { 227c3437056SNickeau 228c3437056SNickeau $databasePage = new DatabasePageRow(); 22904fd306cSNickeau try { 230c3437056SNickeau $row = $databasePage->getDatabaseRowFromPage($page); 231c3437056SNickeau $databasePage->setRow($row); 232c3437056SNickeau return $databasePage; 23304fd306cSNickeau } catch (ExceptionNotExists $e) { 23404fd306cSNickeau // page copied on the local system 23504fd306cSNickeau try { 23604fd306cSNickeau ComboFs::createIfNotExists($page); 23704fd306cSNickeau $row = $databasePage->getDatabaseRowFromPage($page); 23804fd306cSNickeau $databasePage->setRow($row); 23904fd306cSNickeau return $databasePage; 24004fd306cSNickeau } catch (ExceptionNotExists $e) { 24104fd306cSNickeau throw ExceptionRuntimeInternal::withMessageAndError("The row should exists as we created it specifically", $e); 24204fd306cSNickeau } 243c3437056SNickeau } 244c3437056SNickeau 24504fd306cSNickeau } 24604fd306cSNickeau 24704fd306cSNickeau 24804fd306cSNickeau /** 24904fd306cSNickeau * 25004fd306cSNickeau */ 25104fd306cSNickeau public 25204fd306cSNickeau static function createFromPageIdAbbr(string $pageIdAbbr): DatabasePageRow 253c3437056SNickeau { 254c3437056SNickeau $databasePage = new DatabasePageRow(); 25504fd306cSNickeau try { 256c3437056SNickeau $row = $databasePage->getDatabaseRowFromAttribute(PageId::PAGE_ID_ABBR_ATTRIBUTE, $pageIdAbbr); 257c3437056SNickeau $databasePage->setRow($row); 25804fd306cSNickeau } catch (ExceptionNotFound $e) { 25904fd306cSNickeau // ok 260c3437056SNickeau } 261c3437056SNickeau return $databasePage; 262c3437056SNickeau 263c3437056SNickeau } 264c3437056SNickeau 265c3437056SNickeau /** 266c3437056SNickeau * @param $canonical 267c3437056SNickeau * @return DatabasePageRow 268c3437056SNickeau */ 26904fd306cSNickeau public 27004fd306cSNickeau static function createFromCanonical($canonical): DatabasePageRow 271c3437056SNickeau { 272c3437056SNickeau 27304fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($canonical); 274c3437056SNickeau $databasePage = new DatabasePageRow(); 27504fd306cSNickeau try { 276c3437056SNickeau $row = $databasePage->getDatabaseRowFromAttribute(Canonical::PROPERTY_NAME, $canonical); 277c3437056SNickeau $databasePage->setRow($row); 27804fd306cSNickeau } catch (ExceptionNotFound $e) { 27904fd306cSNickeau // ok 280c3437056SNickeau } 281c3437056SNickeau return $databasePage; 282c3437056SNickeau 283c3437056SNickeau 284c3437056SNickeau } 285c3437056SNickeau 28604fd306cSNickeau public 28704fd306cSNickeau static function createFromAlias($alias): DatabasePageRow 288c3437056SNickeau { 289c3437056SNickeau 29004fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($alias); 291c3437056SNickeau $databasePage = new DatabasePageRow(); 292c3437056SNickeau $row = $databasePage->getDatabaseRowFromAlias($alias); 293c3437056SNickeau if ($row != null) { 294c3437056SNickeau $databasePage->setRow($row); 29504fd306cSNickeau $page = $databasePage->getMarkupPath(); 29658317768Sgerardnico if ($page !== null) { 29758317768Sgerardnico // page may be null in production 29858317768Sgerardnico // PHP Fatal error: Uncaught Error: Call to a member function setBuildAliasPath() on null in 29958317768Sgerardnico // /opt/www/bytle/farmer.bytle.net/lib/plugins/combo/ComboStrap/DatabasePageRow.php:220 30058317768Sgerardnico $page->setBuildAliasPath($alias); 30158317768Sgerardnico } 302c3437056SNickeau } 303c3437056SNickeau return $databasePage; 304c3437056SNickeau 305c3437056SNickeau } 306c3437056SNickeau 30704fd306cSNickeau /** 30804fd306cSNickeau * @throws ExceptionNotFound 30904fd306cSNickeau */ 31004fd306cSNickeau public 31104fd306cSNickeau static function getFromDokuWikiId($id): DatabasePageRow 312c3437056SNickeau { 313c3437056SNickeau $databasePage = new DatabasePageRow(); 3144ebc3257Sgerardnico $databasePage->markupPath = MarkupPath::createMarkupFromId($id); 315c3437056SNickeau $row = $databasePage->getDatabaseRowFromDokuWikiId($id); 316c3437056SNickeau $databasePage->setRow($row); 317c3437056SNickeau return $databasePage; 318c3437056SNickeau } 319c3437056SNickeau 32004fd306cSNickeau public 32104fd306cSNickeau function getPageId() 322c3437056SNickeau { 323c3437056SNickeau return $this->getFromRow(PageId::PROPERTY_NAME); 324c3437056SNickeau } 325c3437056SNickeau 326c3437056SNickeau 327c3437056SNickeau public 328c3437056SNickeau function shouldReplicate(): bool 329c3437056SNickeau { 330c3437056SNickeau 33104fd306cSNickeau 3324cadd4f8SNickeau $dateReplication = $this->getReplicationDate(); 3334cadd4f8SNickeau if ($dateReplication === null) { 3344cadd4f8SNickeau return true; 3354cadd4f8SNickeau } 3364cadd4f8SNickeau 3374cadd4f8SNickeau /** 3384cadd4f8SNickeau * When the replication date is older than the actual document 3394cadd4f8SNickeau */ 34004fd306cSNickeau try { 34104fd306cSNickeau $modifiedTime = FileSystems::getModifiedTime($this->markupPath->getPathObject()); 3424cadd4f8SNickeau if ($modifiedTime > $dateReplication) { 3434cadd4f8SNickeau return true; 3444cadd4f8SNickeau } 34504fd306cSNickeau } catch (ExceptionNotFound $e) { 34604fd306cSNickeau return false; 34704fd306cSNickeau } 34804fd306cSNickeau 34904fd306cSNickeau 35004fd306cSNickeau $path = $this->markupPath->fetchAnalyticsPath(); 3514cadd4f8SNickeau 352c3437056SNickeau /** 353c3437056SNickeau * When the file does not exist 354c3437056SNickeau */ 35504fd306cSNickeau $exist = FileSystems::exists($path); 356c3437056SNickeau if (!$exist) { 357c3437056SNickeau return true; 358c3437056SNickeau } 359c3437056SNickeau 360c3437056SNickeau /** 3614cadd4f8SNickeau * When the analytics document is older 362c3437056SNickeau */ 36304fd306cSNickeau try { 36404fd306cSNickeau 36504fd306cSNickeau $modifiedTime = FileSystems::getModifiedTime($path); 366c3437056SNickeau if ($modifiedTime > $dateReplication) { 367c3437056SNickeau return true; 368c3437056SNickeau } 36904fd306cSNickeau } catch (ExceptionNotFound $e) { 37004fd306cSNickeau // 37104fd306cSNickeau } 372c3437056SNickeau 3734cadd4f8SNickeau 374c3437056SNickeau /** 375c3437056SNickeau * When the database version file is higher 376c3437056SNickeau */ 37704fd306cSNickeau $version = LocalPath::createFromPathString(__DIR__ . "/../db/latest.version"); 37804fd306cSNickeau try { 379c3437056SNickeau $versionModifiedTime = FileSystems::getModifiedTime($version); 38004fd306cSNickeau } catch (ExceptionNotFound $e) { 38104fd306cSNickeau return false; 38204fd306cSNickeau } 383c3437056SNickeau if ($versionModifiedTime > $dateReplication) { 384c3437056SNickeau return true; 385c3437056SNickeau } 386c3437056SNickeau 387c3437056SNickeau /** 388c3437056SNickeau * When the class date time is higher 389c3437056SNickeau */ 39004fd306cSNickeau $code = LocalPath::createFromPathString(__DIR__ . "/DatabasePageRow.php"); 39104fd306cSNickeau try { 392c3437056SNickeau $codeModified = FileSystems::getModifiedTime($code); 39304fd306cSNickeau } catch (ExceptionNotFound $e) { 39404fd306cSNickeau throw new ExceptionRuntime("The database file does not exist"); 39504fd306cSNickeau } 396c3437056SNickeau if ($codeModified > $dateReplication) { 397c3437056SNickeau return true; 398c3437056SNickeau } 399c3437056SNickeau 400c3437056SNickeau return false; 401c3437056SNickeau 402c3437056SNickeau } 403c3437056SNickeau 404c3437056SNickeau public 405c3437056SNickeau function delete() 406c3437056SNickeau { 407031d4b49Sgerardnico $rowId = $this->getRowId(); 40804fd306cSNickeau $request = $this->sqlite 409c3437056SNickeau ->createRequest() 410031d4b49Sgerardnico ->setQueryParametrized('delete from pages where rowid = ?', [$rowId]); 411c3437056SNickeau try { 412c3437056SNickeau $request->execute(); 41304fd306cSNickeau } catch (ExceptionCompile $e) { 414031d4b49Sgerardnico LogUtility::error("Something went wrong when deleting the page ({$this->markupPath}) from the database with the rowid $rowId", self::CANONICAL, $e); 415c3437056SNickeau } finally { 416c3437056SNickeau $request->close(); 417c3437056SNickeau } 418031d4b49Sgerardnico 419c3437056SNickeau $this->buildInitObjectFields(); 420c3437056SNickeau 421c3437056SNickeau } 422c3437056SNickeau 423c3437056SNickeau /** 42404fd306cSNickeau * @return Json the analytics array or null if not in db 425c3437056SNickeau */ 426c3437056SNickeau public 42704fd306cSNickeau function getAnalyticsData(): Json 428c3437056SNickeau { 429c3437056SNickeau 430c3437056SNickeau $jsonString = $this->getFromRow(self::ANALYTICS_ATTRIBUTE); 431c3437056SNickeau if ($jsonString === null) { 43204fd306cSNickeau // we put an empty json to not get any problem with the json database function 43304fd306cSNickeau // on an empty string / null (for sqlite) 43404fd306cSNickeau return Json::createEmpty(); 435c3437056SNickeau } 436c3437056SNickeau try { 437c3437056SNickeau return Json::createFromString($jsonString); 43804fd306cSNickeau } catch (ExceptionCompile $e) { 43904fd306cSNickeau throw ExceptionRuntimeInternal::withMessageAndError("Error while building back the analytics JSON object. {$e->getMessage()}", $e); 440c3437056SNickeau } 441c3437056SNickeau 442c3437056SNickeau } 443c3437056SNickeau 444c3437056SNickeau /** 445c3437056SNickeau * Return the database row 446c3437056SNickeau * 447c3437056SNickeau * 44804fd306cSNickeau * @throws ExceptionNotExists - if the row does not exists 449c3437056SNickeau */ 45004fd306cSNickeau public 45104fd306cSNickeau function getDatabaseRowFromPage(MarkupPath $markupPath): array 452c3437056SNickeau { 453c3437056SNickeau 45404fd306cSNickeau $this->setMarkupPath($markupPath); 455c3437056SNickeau 45604fd306cSNickeau /** 45704fd306cSNickeau * Generated identifier 45804fd306cSNickeau */ 45904fd306cSNickeau try { 46004fd306cSNickeau $pageId = $markupPath->getPageId(); 46104fd306cSNickeau return $this->getDatabaseRowFromPageId($pageId); 46204fd306cSNickeau } catch (ExceptionNotFound $e) { 46304fd306cSNickeau // no page id 464c3437056SNickeau } 465c3437056SNickeau 466c3437056SNickeau /** 46704fd306cSNickeau * Named identifier: path 468c3437056SNickeau */ 46904fd306cSNickeau try { 47004fd306cSNickeau $path = $markupPath->getPathObject(); 47104fd306cSNickeau return $this->getDatabaseRowFromPath($path); 47204fd306cSNickeau } catch (ExceptionNotFound $e) { 47304fd306cSNickeau // not found 47404fd306cSNickeau } 47504fd306cSNickeau 47604fd306cSNickeau /** 47704fd306cSNickeau * Named identifier: id (ie path) 47804fd306cSNickeau */ 47904fd306cSNickeau try { 48004fd306cSNickeau $id = $markupPath->getPathObject()->toWikiPath()->getWikiId(); 481c3437056SNickeau return $this->getDatabaseRowFromDokuWikiId($id); 48204fd306cSNickeau } catch (ExceptionCast|ExceptionNotFound $e) { 48304fd306cSNickeau } 48404fd306cSNickeau 48504fd306cSNickeau /** 48604fd306cSNickeau * Named identifier: canonical 48704fd306cSNickeau * (Note that canonical should become a soft link and therefore a path) 48804fd306cSNickeau */ 48904fd306cSNickeau try { 49004fd306cSNickeau $canonical = Canonical::createForPage($markupPath)->getValue(); 49104fd306cSNickeau return $this->getDatabaseRowFromCanonical($canonical->toAbsoluteId()); 49204fd306cSNickeau } catch (ExceptionNotFound $e) { 49304fd306cSNickeau // no canonical 49404fd306cSNickeau } 49504fd306cSNickeau 49604fd306cSNickeau // we send a not exist 49704fd306cSNickeau throw new ExceptionNotExists("No row could be found"); 498c3437056SNickeau 499c3437056SNickeau 500c3437056SNickeau } 501c3437056SNickeau 502c3437056SNickeau 5034cadd4f8SNickeau /** 5044cadd4f8SNickeau * @return DateTime|null 5054cadd4f8SNickeau */ 50604fd306cSNickeau public 50704fd306cSNickeau function getReplicationDate(): ?DateTime 508c3437056SNickeau { 50904fd306cSNickeau $dateString = $this->getFromRow(ReplicationDate::getPersistentName()); 510c3437056SNickeau if ($dateString === null) { 511c3437056SNickeau return null; 512c3437056SNickeau } 513c3437056SNickeau try { 514c3437056SNickeau return Iso8601Date::createFromString($dateString)->getDateTime(); 51504fd306cSNickeau } catch (ExceptionCompile $e) { 516c3437056SNickeau LogUtility::msg("Error while reading the replication date in the database. {$e->getMessage()}"); 517c3437056SNickeau return null; 518c3437056SNickeau } 519c3437056SNickeau 520c3437056SNickeau } 521c3437056SNickeau 522c3437056SNickeau /** 52304fd306cSNickeau * 52404fd306cSNickeau * @throws ExceptionBadState 52504fd306cSNickeau * @throws ExceptionSqliteNotAvailable 526c3437056SNickeau */ 52704fd306cSNickeau public 52804fd306cSNickeau function replicatePage(): void 529c3437056SNickeau { 530c3437056SNickeau 53104fd306cSNickeau if (!FileSystems::exists($this->markupPath)) { 53204fd306cSNickeau throw new ExceptionBadState("You can't replicate the page ($this->markupPath) because it does not exists."); 533c3437056SNickeau } 534c3437056SNickeau 535c3437056SNickeau /** 536c3437056SNickeau * Replication Date 537c3437056SNickeau */ 53804fd306cSNickeau $replicationDate = ReplicationDate::createFromPage($this->markupPath) 539c3437056SNickeau ->setWriteStore(MetadataDbStore::class) 5404cadd4f8SNickeau ->setValue(new DateTime()); 541c3437056SNickeau 542c3437056SNickeau /** 54304fd306cSNickeau * Same data as {@link MarkupPath::getMetadataForRendering()} 544c3437056SNickeau */ 545c3437056SNickeau $record = $this->getMetaRecord(); 546c3437056SNickeau $record[$replicationDate::getPersistentName()] = $replicationDate->toStoreValue(); 54704fd306cSNickeau $this->upsertAttributes($record); 548c3437056SNickeau 549c3437056SNickeau } 550c3437056SNickeau 551c3437056SNickeau 552c3437056SNickeau /** 55304fd306cSNickeau * 554c3437056SNickeau * 555c3437056SNickeau * Attribute that are scalar / modifiable in the database 556c3437056SNickeau * (not aliases or json data for instance) 55704fd306cSNickeau * 55804fd306cSNickeau * @throws ExceptionBadState 55904fd306cSNickeau * @throws ExceptionSqliteNotAvailable 560c3437056SNickeau */ 56104fd306cSNickeau public 56204fd306cSNickeau function replicateMetaAttributes(): void 563c3437056SNickeau { 564c3437056SNickeau 56504fd306cSNickeau $this->upsertAttributes($this->getMetaRecord()); 566c3437056SNickeau 567c3437056SNickeau } 568c3437056SNickeau 56904fd306cSNickeau /** 57004fd306cSNickeau * @throws ExceptionBadState - if the array is empty 57104fd306cSNickeau */ 57204fd306cSNickeau public 57304fd306cSNickeau function upsertAttributes(array $attributes): void 574c3437056SNickeau { 575c3437056SNickeau 576c3437056SNickeau if (empty($attributes)) { 57704fd306cSNickeau throw new ExceptionBadState("The page database attribute passed should not be empty"); 578c3437056SNickeau } 579c3437056SNickeau 580c3437056SNickeau $values = []; 581c3437056SNickeau $columnClauses = []; 582c3437056SNickeau foreach ($attributes as $key => $value) { 583c3437056SNickeau if (is_array($value)) { 58404fd306cSNickeau throw new ExceptionRuntime("The attribute ($key) has value that is an array (" . implode(", ", $value) . ")"); 585c3437056SNickeau } 586c3437056SNickeau $columnClauses[] = "$key = ?"; 587c3437056SNickeau $values[$key] = $value; 588c3437056SNickeau } 589c3437056SNickeau 590c3437056SNickeau /** 591c3437056SNickeau * Primary key has moved during the time 592c3437056SNickeau * It should be the UUID but not for older version 593c3437056SNickeau * 594c3437056SNickeau * If the primary key is null, no record was found 595c3437056SNickeau */ 596c3437056SNickeau $rowId = $this->getRowId(); 597c3437056SNickeau if ($rowId !== null) { 598c3437056SNickeau /** 599c3437056SNickeau * We just add the primary key 600c3437056SNickeau * otherwise as this is a associative 601c3437056SNickeau * array, we will miss a value for the update statement 602c3437056SNickeau */ 603c3437056SNickeau $values[] = $rowId; 604c3437056SNickeau 605c3437056SNickeau $updateStatement = "update PAGES SET " . implode(", ", $columnClauses) . " where ROWID = ?"; 606c3437056SNickeau $request = $this->sqlite 607c3437056SNickeau ->createRequest() 608c3437056SNickeau ->setQueryParametrized($updateStatement, $values); 609c3437056SNickeau $countChanges = 0; 610c3437056SNickeau try { 611c3437056SNickeau $countChanges = $request 612c3437056SNickeau ->execute() 613c3437056SNickeau ->getChangeCount(); 61404fd306cSNickeau } catch (ExceptionCompile $e) { 61504fd306cSNickeau throw new ExceptionBadState("There was a problem during the page attribute updates. : {$e->getMessage()}"); 616c3437056SNickeau } finally { 617c3437056SNickeau $request->close(); 618c3437056SNickeau } 619c3437056SNickeau if ($countChanges !== 1) { 62004fd306cSNickeau // internal error 62104fd306cSNickeau LogUtility::error("The database replication has not updated exactly 1 record but ($countChanges) record", \action_plugin_combo_indexer::CANONICAL); 622c3437056SNickeau } 623c3437056SNickeau 624c3437056SNickeau } else { 625c3437056SNickeau 626c3437056SNickeau /** 627c3437056SNickeau * Creation 628c3437056SNickeau */ 62904fd306cSNickeau if ($this->markupPath === null) { 63004fd306cSNickeau throw new ExceptionBadState("The page should be defined to create a page database row"); 631c3437056SNickeau } 63204fd306cSNickeau 63304fd306cSNickeau /** 63404fd306cSNickeau * If the user copy a frontmatter with the same page id abbr, we got a problem 63504fd306cSNickeau */ 636be61a7dfSgerardnico $pageIdAbbr = $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] ?? null; 63704fd306cSNickeau if ($pageIdAbbr == null) { 63804fd306cSNickeau $pageId = $values[PageId::getPersistentName()]; 63904fd306cSNickeau if ($pageId === null) { 64004fd306cSNickeau throw new ExceptionBadState("You can't insert a page in the database without a page id"); 64104fd306cSNickeau } 64204fd306cSNickeau $pageIdAbbr = PageId::getAbbreviated($pageId); 64304fd306cSNickeau $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $pageIdAbbr; 64404fd306cSNickeau } 64504fd306cSNickeau 64604fd306cSNickeau $databasePage = DatabasePageRow::createFromPageIdAbbr($pageIdAbbr); 64704fd306cSNickeau if ($databasePage->exists()) { 64804fd306cSNickeau $duplicatePage = $databasePage->getMarkupPath(); 64904fd306cSNickeau if ($duplicatePage->getPathObject()->toUriString() === $this->markupPath->toUriString()) { 65004fd306cSNickeau $message = "The page ($this->markupPath) is already in the database with the uid ($pageIdAbbr)."; 65104fd306cSNickeau } else { 65204fd306cSNickeau $message = "The page ($this->markupPath) cannot be replicated to the database because it has the same page id abbreviation ($pageIdAbbr) than the page ($duplicatePage)"; 65304fd306cSNickeau } 65404fd306cSNickeau throw new ExceptionBadState($message); 65504fd306cSNickeau } 65604fd306cSNickeau 65704fd306cSNickeau $values[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] = $this->markupPath->getPathObject()->getWikiId(); 65804fd306cSNickeau $values[PagePath::PROPERTY_NAME] = $this->markupPath->getPathObject()->toAbsolutePath()->toAbsoluteId(); 659c3437056SNickeau /** 660c3437056SNickeau * Default implements the auto-canonical feature 661c3437056SNickeau */ 66204fd306cSNickeau try { 66304fd306cSNickeau $values[Canonical::PROPERTY_NAME] = $this->markupPath->getCanonicalOrDefault(); 66404fd306cSNickeau } catch (ExceptionNotFound $e) { 66504fd306cSNickeau $values[Canonical::PROPERTY_NAME] = null; 66604fd306cSNickeau } 667c3437056SNickeau 668c3437056SNickeau /** 669c3437056SNickeau * Analytics 670c3437056SNickeau */ 671be61a7dfSgerardnico $analyticsAttributeValue = $values[self::ANALYTICS_ATTRIBUTE] ?? null; 672be61a7dfSgerardnico if (!isset($analyticsAttributeValue)) { 673c3437056SNickeau // otherwise we get an empty string 674c3437056SNickeau // and a json function will not work 675c3437056SNickeau $values[self::ANALYTICS_ATTRIBUTE] = Json::createEmpty()->toPrettyJsonString(); 676c3437056SNickeau } 677c3437056SNickeau 678c3437056SNickeau /** 679c3437056SNickeau * Page Id / Abbr are mandatory for url redirection 680c3437056SNickeau */ 681c3437056SNickeau $this->addPageIdAttributeIfNeeded($values); 682c3437056SNickeau 683c3437056SNickeau $request = $this->sqlite 684c3437056SNickeau ->createRequest() 685c3437056SNickeau ->setTableRow('PAGES', $values); 686c3437056SNickeau try { 687c3437056SNickeau /** 688c3437056SNickeau * rowid is used in {@link DatabasePageRow::exists()} 689c3437056SNickeau * to check if the page exists in the database 690c3437056SNickeau * We update it 691c3437056SNickeau */ 692c3437056SNickeau $this->row[self::ROWID] = $request 693c3437056SNickeau ->execute() 694c3437056SNickeau ->getInsertId(); 695c3437056SNickeau $this->row = array_merge($values, $this->row); 69604fd306cSNickeau } catch (ExceptionCompile $e) { 69704fd306cSNickeau throw new ExceptionBadState("There was a problem during the updateAttributes insert. : {$e->getMessage()}"); 698c3437056SNickeau } finally { 699c3437056SNickeau $request->close(); 700c3437056SNickeau } 701c3437056SNickeau 702c3437056SNickeau } 703c3437056SNickeau 704c3437056SNickeau } 705c3437056SNickeau 706c3437056SNickeau public 707c3437056SNickeau function getDescription() 708c3437056SNickeau { 709c3437056SNickeau return $this->getFromRow(PageDescription::DESCRIPTION_PROPERTY); 710c3437056SNickeau } 711c3437056SNickeau 712c3437056SNickeau 713c3437056SNickeau public 714c3437056SNickeau function getPageName() 715c3437056SNickeau { 716c3437056SNickeau return $this->getFromRow(ResourceName::PROPERTY_NAME); 717c3437056SNickeau } 718c3437056SNickeau 719c3437056SNickeau public 720c3437056SNickeau function exists(): bool 721c3437056SNickeau { 722c3437056SNickeau return $this->getFromRow(self::ROWID) !== null; 723c3437056SNickeau } 724c3437056SNickeau 725c3437056SNickeau /** 726c3437056SNickeau * Called when a page is moved 727c3437056SNickeau * @param $targetId 728c3437056SNickeau */ 729c3437056SNickeau public 730c3437056SNickeau function updatePathAndDokuwikiId($targetId) 731c3437056SNickeau { 732c3437056SNickeau if (!$this->exists()) { 73304fd306cSNickeau LogUtility::error("The `database` page ($this) does not exist and cannot be moved to ($targetId)"); 734c3437056SNickeau } 735c3437056SNickeau 736c3437056SNickeau $path = $targetId; 73704fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 738c3437056SNickeau $attributes = [ 739c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE => $targetId, 740c3437056SNickeau PagePath::PROPERTY_NAME => $path 741c3437056SNickeau ]; 742c3437056SNickeau 743c3437056SNickeau $this->upsertAttributes($attributes); 744c3437056SNickeau 745c3437056SNickeau } 746c3437056SNickeau 747c3437056SNickeau public 748c3437056SNickeau function __toString() 749c3437056SNickeau { 75004fd306cSNickeau return $this->markupPath->__toString(); 751c3437056SNickeau } 752c3437056SNickeau 753c3437056SNickeau 754c3437056SNickeau /** 755c3437056SNickeau * Redirect are now added during a move 756c3437056SNickeau * Not when a duplicate is found. 757c3437056SNickeau * With the advent of the page id, it should never occurs anymore 75804fd306cSNickeau * @param MarkupPath $page 759c3437056SNickeau * @deprecated 2012-10-28 760c3437056SNickeau */ 761c3437056SNickeau private 76204fd306cSNickeau function deleteIfExistsAndAddRedirectAlias(MarkupPath $page): void 763c3437056SNickeau { 764c3437056SNickeau 76504fd306cSNickeau if ($this->markupPath != null) { 766c3437056SNickeau $page->getDatabasePage()->deleteIfExist(); 767c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($page); 768c3437056SNickeau } 769c3437056SNickeau 770c3437056SNickeau } 771c3437056SNickeau 772c3437056SNickeau public 773c3437056SNickeau function getCanonical() 774c3437056SNickeau { 775c3437056SNickeau return $this->getFromRow(Canonical::PROPERTY_NAME); 776c3437056SNickeau } 777c3437056SNickeau 778c3437056SNickeau /** 779c3437056SNickeau * Set the field to their values 780c3437056SNickeau * @param $row 781c3437056SNickeau */ 782c3437056SNickeau public 783c3437056SNickeau function setRow($row) 784c3437056SNickeau { 785c3437056SNickeau if ($row === null) { 786c3437056SNickeau LogUtility::msg("A row should not be null"); 787c3437056SNickeau return; 788c3437056SNickeau } 789c3437056SNickeau if (!is_array($row)) { 790c3437056SNickeau LogUtility::msg("A row should be an array"); 791c3437056SNickeau return; 792c3437056SNickeau } 793c3437056SNickeau 794c3437056SNickeau /** 795c3437056SNickeau * All get function lookup the row 796c3437056SNickeau */ 797c3437056SNickeau $this->row = $row; 798c3437056SNickeau 799c3437056SNickeau 800c3437056SNickeau } 801c3437056SNickeau 802c3437056SNickeau private 803c3437056SNickeau function buildInitObjectFields() 804c3437056SNickeau { 805c3437056SNickeau $this->row = null; 806c3437056SNickeau 807c3437056SNickeau } 808c3437056SNickeau 809c3437056SNickeau public 810c3437056SNickeau function rebuild(): DatabasePageRow 811c3437056SNickeau { 812c3437056SNickeau 81304fd306cSNickeau if ($this->markupPath != null) { 81404fd306cSNickeau $this->markupPath->rebuild(); 81504fd306cSNickeau try { 81604fd306cSNickeau $row = $this->getDatabaseRowFromPage($this->markupPath); 817c3437056SNickeau $this->setRow($row); 81804fd306cSNickeau } catch (ExceptionNotExists $e) { 81904fd306cSNickeau // ok 820c3437056SNickeau } 821c3437056SNickeau } 822c3437056SNickeau return $this; 823c3437056SNickeau 824c3437056SNickeau } 825c3437056SNickeau 826c3437056SNickeau /** 827c3437056SNickeau * @return array - an array of the fix page metadata (ie not derived) 828c3437056SNickeau * Therefore quick to insert/update 829c3437056SNickeau * 830c3437056SNickeau */ 831c3437056SNickeau private 832c3437056SNickeau function getMetaRecord(): array 833c3437056SNickeau { 83404fd306cSNickeau $sourceStore = MetadataDokuWikiStore::getOrCreateFromResource($this->markupPath); 83504fd306cSNickeau $targetStore = MetadataDbStore::getOrCreateFromResource($this->markupPath); 836c3437056SNickeau 837c3437056SNickeau $record = array( 838c3437056SNickeau Canonical::PROPERTY_NAME, 839c3437056SNickeau PagePath::PROPERTY_NAME, 840c3437056SNickeau ResourceName::PROPERTY_NAME, 841c3437056SNickeau PageTitle::TITLE, 842c3437056SNickeau PageH1::PROPERTY_NAME, 843c3437056SNickeau PageDescription::PROPERTY_NAME, 84404fd306cSNickeau CreationDate::PROPERTY_NAME, 845c3437056SNickeau ModificationDate::PROPERTY_NAME, 846c3437056SNickeau PagePublicationDate::PROPERTY_NAME, 847c3437056SNickeau StartDate::PROPERTY_NAME, 848c3437056SNickeau EndDate::PROPERTY_NAME, 849c3437056SNickeau Region::PROPERTY_NAME, 850c3437056SNickeau Lang::PROPERTY_NAME, 851c3437056SNickeau PageType::PROPERTY_NAME, 852c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, 85304fd306cSNickeau PageLevel::PROPERTY_NAME 854c3437056SNickeau ); 855c3437056SNickeau $metaRecord = []; 856c3437056SNickeau foreach ($record as $name) { 85704fd306cSNickeau try { 85804fd306cSNickeau $metadata = Meta\Api\MetadataSystem::getForName($name); 85904fd306cSNickeau } catch (ExceptionNotFound $e) { 86004fd306cSNickeau LogUtility::internalError("The metadata ($name) is unknown", self::CANONICAL); 86104fd306cSNickeau continue; 862c3437056SNickeau } 863c3437056SNickeau $metaRecord[$name] = $metadata 86404fd306cSNickeau ->setResource($this->markupPath) 865c3437056SNickeau ->setReadStore($sourceStore) 866c3437056SNickeau ->buildFromReadStore() 867c3437056SNickeau ->setWriteStore($targetStore) 868c3437056SNickeau ->toStoreValueOrDefault(); // used by the template, the value is or default 86904fd306cSNickeau 870c3437056SNickeau } 871c3437056SNickeau 87204fd306cSNickeau try { 873c3437056SNickeau $this->addPageIdMeta($metaRecord); 87404fd306cSNickeau } catch (ExceptionNotExists $e) { 87504fd306cSNickeau // no page id for non-existent page ok 876c3437056SNickeau } 87704fd306cSNickeau 87804fd306cSNickeau // Is index 87904fd306cSNickeau $metaRecord[self::IS_INDEX_COLUMN] = ($this->markupPath->isIndexPage() === true ? 1 : 0); 88004fd306cSNickeau 881c3437056SNickeau return $metaRecord; 882c3437056SNickeau } 883c3437056SNickeau 884c3437056SNickeau public 885c3437056SNickeau function deleteIfExist(): DatabasePageRow 886c3437056SNickeau { 887c3437056SNickeau if ($this->exists()) { 888c3437056SNickeau $this->delete(); 889c3437056SNickeau } 890c3437056SNickeau return $this; 891c3437056SNickeau } 892c3437056SNickeau 893c3437056SNickeau public 894c3437056SNickeau function getRowId() 895c3437056SNickeau { 896c3437056SNickeau return $this->getFromRow(self::ROWID); 897c3437056SNickeau } 898c3437056SNickeau 89904fd306cSNickeau /** 90004fd306cSNickeau * @throws ExceptionNotFound 901031d4b49Sgerardnico * @throws ExceptionSqliteNotAvailable 90204fd306cSNickeau */ 903c3437056SNickeau private 90404fd306cSNickeau function getDatabaseRowFromPageId(string $pageIdValue) 905c3437056SNickeau { 906c3437056SNickeau 907c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 908c3437056SNickeau $query = $this->getParametrizedLookupQuery($pageIdAttribute); 909c3437056SNickeau $request = Sqlite::createOrGetSqlite() 910c3437056SNickeau ->createRequest() 91104fd306cSNickeau ->setQueryParametrized($query, [$pageIdValue]); 912c3437056SNickeau $rows = []; 913c3437056SNickeau try { 914c3437056SNickeau $rows = $request 915c3437056SNickeau ->execute() 916c3437056SNickeau ->getRows(); 91704fd306cSNickeau } catch (ExceptionCompile $e) { 91804fd306cSNickeau throw new ExceptionRuntimeInternal("Error while retrieving the object by id", self::CANONICAL, 1, $e); 919c3437056SNickeau } finally { 920c3437056SNickeau $request->close(); 921c3437056SNickeau } 922c3437056SNickeau 923c3437056SNickeau switch (sizeof($rows)) { 924c3437056SNickeau case 0: 92504fd306cSNickeau throw new ExceptionNotFound("No object by page id"); 926c3437056SNickeau case 1: 927c3437056SNickeau /** 928c3437056SNickeau * Page Id Collision detection 929c3437056SNickeau */ 93004fd306cSNickeau $rowId = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 93104fd306cSNickeau $this->checkCollision($rowId, $pageIdAttribute, $pageIdValue); 932c3437056SNickeau return $rows[0]; 933c3437056SNickeau default: 934c3437056SNickeau $existingPages = implode(", ", $rows); 93504fd306cSNickeau $message = "The pages ($existingPages) have all the same page id ($pageIdValue)"; 93604fd306cSNickeau throw new ExceptionRuntimeInternal($message, self::CANONICAL); 937c3437056SNickeau } 938c3437056SNickeau 939c3437056SNickeau } 940c3437056SNickeau 941c3437056SNickeau 942c3437056SNickeau private 94304fd306cSNickeau function getParametrizedLookupQuery(string $attributeName): string 944c3437056SNickeau { 945c3437056SNickeau $select = Sqlite::createSelectFromTableAndColumns("pages", self::PAGE_BUILD_ATTRIBUTES); 94604fd306cSNickeau return "$select where $attributeName = ?"; 947c3437056SNickeau } 948c3437056SNickeau 949c3437056SNickeau 95004fd306cSNickeau public 95104fd306cSNickeau function setMarkupPath(MarkupPath $page) 952c3437056SNickeau { 95304fd306cSNickeau $this->markupPath = $page; 954c3437056SNickeau return $this; 955c3437056SNickeau } 956c3437056SNickeau 95704fd306cSNickeau /** 95804fd306cSNickeau * @throws ExceptionNotFound 95904fd306cSNickeau */ 960c3437056SNickeau private 96104fd306cSNickeau function getDatabaseRowFromCanonical($canonicalValue) 962c3437056SNickeau { 96304fd306cSNickeau $canoncialName = Canonical::PROPERTY_NAME; 96404fd306cSNickeau $query = $this->getParametrizedLookupQuery($canoncialName); 965c3437056SNickeau $request = $this->sqlite 966c3437056SNickeau ->createRequest() 96704fd306cSNickeau ->setQueryParametrized($query, [$canonicalValue]); 968c3437056SNickeau $rows = []; 969c3437056SNickeau try { 970c3437056SNickeau $rows = $request 971c3437056SNickeau ->execute() 972c3437056SNickeau ->getRows(); 97304fd306cSNickeau } catch (ExceptionCompile $e) { 97404fd306cSNickeau throw new ExceptionRuntime("An exception has occurred with the page search from CANONICAL. " . $e->getMessage()); 975c3437056SNickeau } finally { 976c3437056SNickeau $request->close(); 977c3437056SNickeau } 978c3437056SNickeau 979c3437056SNickeau switch (sizeof($rows)) { 980c3437056SNickeau case 0: 98104fd306cSNickeau throw new ExceptionNotFound("No canonical row was found"); 982c3437056SNickeau case 1: 983c3437056SNickeau $id = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 98404fd306cSNickeau $this->checkCollision($id, $canoncialName, $canonicalValue); 985c3437056SNickeau return $rows[0]; 986c3437056SNickeau default: 987c3437056SNickeau $existingPages = []; 988c3437056SNickeau foreach ($rows as $row) { 989c3437056SNickeau $id = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 99004fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($id); 991c3437056SNickeau if (!$duplicatePage->exists()) { 992c3437056SNickeau 993c3437056SNickeau $this->deleteIfExistsAndAddRedirectAlias($duplicatePage); 994c3437056SNickeau 995c3437056SNickeau } else { 996c3437056SNickeau 997c3437056SNickeau /** 998c3437056SNickeau * Check if the error may come from the auto-canonical 999c3437056SNickeau * (Never ever save generated data) 1000c3437056SNickeau */ 100104fd306cSNickeau $canonicalLastNamesCount = SiteConfig::getConfValue(Canonical::CONF_CANONICAL_LAST_NAMES_COUNT, 0); 1002c3437056SNickeau if ($canonicalLastNamesCount > 0) { 100304fd306cSNickeau $this->markupPath->unsetMetadata($canoncialName); 100404fd306cSNickeau $duplicatePage->unsetMetadata($canoncialName); 1005c3437056SNickeau } 1006c3437056SNickeau 1007c3437056SNickeau $existingPages[] = $row; 1008c3437056SNickeau } 1009c3437056SNickeau } 101004fd306cSNickeau if (sizeof($existingPages) > 1) { 1011c3437056SNickeau $existingPages = implode(", ", $existingPages); 101204fd306cSNickeau $message = "The existing pages ($existingPages) have all the same canonical ($canonicalValue), return the first one"; 101304fd306cSNickeau LogUtility::error($message, self::CANONICAL); 1014c3437056SNickeau } 101504fd306cSNickeau return $existingPages[0]; 1016c3437056SNickeau } 1017c3437056SNickeau } 1018c3437056SNickeau 101904fd306cSNickeau /** 102004fd306cSNickeau * @throws ExceptionNotFound 102104fd306cSNickeau */ 1022c3437056SNickeau private 1023c3437056SNickeau function getDatabaseRowFromPath(string $path): ?array 1024c3437056SNickeau { 102504fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 1026c3437056SNickeau return $this->getDatabaseRowFromAttribute(PagePath::PROPERTY_NAME, $path); 1027c3437056SNickeau } 1028c3437056SNickeau 102904fd306cSNickeau /** 103004fd306cSNickeau * @throws ExceptionNotFound 103104fd306cSNickeau */ 1032c3437056SNickeau private 103304fd306cSNickeau function getDatabaseRowFromDokuWikiId(string $id): array 1034c3437056SNickeau { 1035c3437056SNickeau return $this->getDatabaseRowFromAttribute(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $id); 1036c3437056SNickeau } 1037c3437056SNickeau 103804fd306cSNickeau /** 103904fd306cSNickeau * @throws ExceptionNotFound 104004fd306cSNickeau */ 1041c3437056SNickeau public 1042031d4b49Sgerardnico function getDatabaseRowFromAttribute(string $attribute, string $attributeValue) 1043c3437056SNickeau { 1044c3437056SNickeau $query = $this->getParametrizedLookupQuery($attribute); 1045c3437056SNickeau $request = $this->sqlite 1046c3437056SNickeau ->createRequest() 1047031d4b49Sgerardnico ->setQueryParametrized($query, [$attributeValue]); 1048c3437056SNickeau $rows = []; 1049c3437056SNickeau try { 1050c3437056SNickeau $rows = $request 1051c3437056SNickeau ->execute() 1052c3437056SNickeau ->getRows(); 105304fd306cSNickeau } catch (ExceptionCompile $e) { 105404fd306cSNickeau $message = "Internal Error: An exception has occurred with the page search from a PATH: " . $e->getMessage(); 105504fd306cSNickeau LogUtility::log2file($message); 105604fd306cSNickeau throw new ExceptionNotFound($message); 1057c3437056SNickeau } finally { 1058c3437056SNickeau $request->close(); 1059c3437056SNickeau } 1060c3437056SNickeau 1061031d4b49Sgerardnico $rowCount = sizeof($rows); 1062031d4b49Sgerardnico switch ($rowCount) { 1063c3437056SNickeau case 0: 106404fd306cSNickeau throw new ExceptionNotFound("No database row found for the page"); 1065c3437056SNickeau case 1: 1066031d4b49Sgerardnico $attributeValue = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 1067031d4b49Sgerardnico try { 1068*11d09b86Sgerardnico if ($this->markupPath != null && $attributeValue !== $this->markupPath->getWikiId()) { 1069031d4b49Sgerardnico $duplicatePage = MarkupPath::createMarkupFromId($attributeValue); 1070c3437056SNickeau if (!$duplicatePage->exists()) { 1071c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 1072c3437056SNickeau } else { 1073031d4b49Sgerardnico LogUtility::error("The page ($this->markupPath) and the page ($attributeValue) have the same $attribute ($attributeValue)"); 1074c3437056SNickeau } 1075c3437056SNickeau } 1076*11d09b86Sgerardnico } catch (ExceptionBadArgument $e) { 1077*11d09b86Sgerardnico throw new ExceptionRuntimeInternal("The wiki id should be available"); 1078*11d09b86Sgerardnico } 1079c3437056SNickeau return $rows[0]; 1080c3437056SNickeau default: 1081031d4b49Sgerardnico LogUtility::warning("Error: More than 1 rows ($rowCount) found for attribute ($attribute) with the value ($attributeValue)"); 1082031d4b49Sgerardnico 1083031d4b49Sgerardnico /** 1084031d4b49Sgerardnico * Trying to delete the bad one 1085031d4b49Sgerardnico */ 1086c3437056SNickeau $existingPages = []; 1087c3437056SNickeau foreach ($rows as $row) { 1088c3437056SNickeau 1089031d4b49Sgerardnico $rowId = $row[self::ROWID] ?? null; 1090031d4b49Sgerardnico if ($rowId === null) { 1091031d4b49Sgerardnico LogUtility::error("A row id should be present"); 1092c3437056SNickeau $existingPages[] = $row; 1093031d4b49Sgerardnico continue; 1094c3437056SNickeau } 1095031d4b49Sgerardnico // page id throw for several id 1096031d4b49Sgerardnico $duplicateRow = DatabasePageRow::createFromRowId($rowId); 1097031d4b49Sgerardnico $duplicateMarkupPath = $duplicateRow->getMarkupPath(); 1098031d4b49Sgerardnico if (!FileSystems::exists($duplicateMarkupPath)) { 1099031d4b49Sgerardnico LogUtility::warning("The duplicate page ($duplicateMarkupPath) does not exists. Delete Row ({$rowId})"); 1100031d4b49Sgerardnico $duplicateRow->delete(); 1101031d4b49Sgerardnico $this->addRedirectAliasWhileBuildingRow($duplicateMarkupPath); 1102031d4b49Sgerardnico continue; 1103031d4b49Sgerardnico } 1104031d4b49Sgerardnico $existingPages[] = $row; 1105c3437056SNickeau } 1106c3437056SNickeau if (sizeof($existingPages) === 1) { 1107c3437056SNickeau return $existingPages[0]; 1108c3437056SNickeau } else { 110904fd306cSNickeau $existingPageIds = array_map( 111004fd306cSNickeau function ($row) { 111104fd306cSNickeau return $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 111204fd306cSNickeau }, 111304fd306cSNickeau $existingPages); 111404fd306cSNickeau $existingPages = implode(", ", $existingPageIds); 1115031d4b49Sgerardnico throw new ExceptionNotFound("The existing pages ($existingPages) have all the same attribute $attribute with the value ($attributeValue)", LogUtility::LVL_MSG_ERROR); 1116c3437056SNickeau } 1117c3437056SNickeau } 1118c3437056SNickeau } 1119c3437056SNickeau 1120c3437056SNickeau public 112104fd306cSNickeau function getMarkupPath(): ?MarkupPath 1122c3437056SNickeau { 112370bbd7f1Sgerardnico if ($this->row === null) { 112470bbd7f1Sgerardnico return null; 112570bbd7f1Sgerardnico } 1126c3437056SNickeau if ( 112704fd306cSNickeau $this->markupPath === null 1128c3437056SNickeau && $this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] !== null 1129c3437056SNickeau ) { 113004fd306cSNickeau $this->markupPath = MarkupPath::createMarkupFromId($this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]); 1131c3437056SNickeau } 113204fd306cSNickeau return $this->markupPath; 1133c3437056SNickeau } 1134c3437056SNickeau 1135c3437056SNickeau private 1136c3437056SNickeau function getDatabaseRowFromAlias($alias): ?array 1137c3437056SNickeau { 1138c3437056SNickeau 1139c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 1140c3437056SNickeau $buildFields = self::PAGE_BUILD_ATTRIBUTES; 1141c3437056SNickeau $fields = array_reduce($buildFields, function ($carry, $element) { 1142c3437056SNickeau if ($carry !== null) { 1143c3437056SNickeau return "$carry, p.{$element}"; 1144c3437056SNickeau } else { 1145c3437056SNickeau return "p.{$element}"; 1146c3437056SNickeau } 1147c3437056SNickeau }, null); 114804fd306cSNickeau /** @noinspection SqlResolve */ 1149c3437056SNickeau $query = "select {$fields} from PAGES p, PAGE_ALIASES pa where p.{$pageIdAttribute} = pa.{$pageIdAttribute} and pa.PATH = ? "; 1150c3437056SNickeau $request = $this->sqlite 1151c3437056SNickeau ->createRequest() 1152c3437056SNickeau ->setQueryParametrized($query, [$alias]); 1153c3437056SNickeau $rows = []; 1154c3437056SNickeau try { 1155c3437056SNickeau $rows = $request 1156c3437056SNickeau ->execute() 1157c3437056SNickeau ->getRows(); 115804fd306cSNickeau } catch (ExceptionCompile $e) { 1159c3437056SNickeau LogUtility::msg("An exception has occurred with the alias selection query. {$e->getMessage()}"); 1160c3437056SNickeau return null; 1161c3437056SNickeau } finally { 1162c3437056SNickeau $request->close(); 1163c3437056SNickeau } 1164c3437056SNickeau switch (sizeof($rows)) { 1165c3437056SNickeau case 0: 1166c3437056SNickeau return null; 1167c3437056SNickeau case 1: 1168c3437056SNickeau return $rows[0]; 1169c3437056SNickeau default: 1170c3437056SNickeau $id = $rows[0]['ID']; 1171c3437056SNickeau $pages = implode(",", 1172c3437056SNickeau array_map( 1173c3437056SNickeau function ($row) { 1174c3437056SNickeau return $row['ID']; 1175c3437056SNickeau }, 1176c3437056SNickeau $rows 1177c3437056SNickeau ) 1178c3437056SNickeau ); 1179c3437056SNickeau 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); 1180c3437056SNickeau return $rows[0]; 1181c3437056SNickeau } 1182c3437056SNickeau } 1183c3437056SNickeau 1184c3437056SNickeau 1185c3437056SNickeau /** 1186c3437056SNickeau * Utility function 118704fd306cSNickeau * @param MarkupPath $pageAlias 1188c3437056SNickeau */ 1189c3437056SNickeau private 119004fd306cSNickeau function addRedirectAliasWhileBuildingRow(MarkupPath $pageAlias) 1191c3437056SNickeau { 1192c3437056SNickeau 119304fd306cSNickeau $aliasPath = $pageAlias->getPathObject()->toAbsoluteId(); 1194031d4b49Sgerardnico LogUtility::warning("Add alias ($aliasPath) for page ({$this->markupPath})"); 1195c3437056SNickeau try { 119604fd306cSNickeau Aliases::createForPage($this->markupPath) 1197c3437056SNickeau ->addAlias($aliasPath) 1198c3437056SNickeau ->sendToWriteStore(); 119904fd306cSNickeau } catch (ExceptionCompile $e) { 1200c3437056SNickeau // we don't throw while getting 120104fd306cSNickeau LogUtility::msg("Unable to add the alias ($aliasPath) for the page ($this->markupPath)"); 1202c3437056SNickeau } 1203c3437056SNickeau 1204c3437056SNickeau } 1205c3437056SNickeau 1206c3437056SNickeau private 1207c3437056SNickeau function addPageIdAttributeIfNeeded(array &$values) 1208c3437056SNickeau { 1209c3437056SNickeau if (!isset($values[PageId::getPersistentName()])) { 121004fd306cSNickeau $values[PageId::getPersistentName()] = $this->markupPath->getPageId(); 1211c3437056SNickeau } 1212c3437056SNickeau if (!isset($values[PageId::PAGE_ID_ABBR_ATTRIBUTE])) { 121304fd306cSNickeau $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $this->markupPath->getPageIdAbbr(); 1214c3437056SNickeau } 1215c3437056SNickeau } 1216c3437056SNickeau 1217c3437056SNickeau public 1218c3437056SNickeau function getFromRow(string $attribute) 1219c3437056SNickeau { 1220c3437056SNickeau if ($this->row === null) { 1221c3437056SNickeau return null; 1222c3437056SNickeau } 1223c3437056SNickeau 1224c3437056SNickeau if (!array_key_exists($attribute, $this->row)) { 1225c3437056SNickeau /** 1226c3437056SNickeau * An attribute should be added to {@link DatabasePageRow::PAGE_BUILD_ATTRIBUTES} 1227c3437056SNickeau * or in the table 1228c3437056SNickeau */ 122904fd306cSNickeau throw new ExceptionRuntime("The metadata ($attribute) was not found in the returned database row.", $this->getCanonical()); 1230c3437056SNickeau } 1231c3437056SNickeau 1232c3437056SNickeau $value = $this->row[$attribute]; 1233c3437056SNickeau 1234c3437056SNickeau if ($value !== null) { 1235c3437056SNickeau return $value; 1236c3437056SNickeau } 1237c3437056SNickeau 1238c3437056SNickeau // don't know why but the sqlite plugin returns them uppercase 1239c3437056SNickeau // rowid is returned lowercase from the sqlite plugin 1240c3437056SNickeau $upperAttribute = strtoupper($attribute); 124170bbd7f1Sgerardnico return $this->row[$upperAttribute] ?? null; 1242c3437056SNickeau 1243c3437056SNickeau } 1244c3437056SNickeau 1245c3437056SNickeau 12464cadd4f8SNickeau /** 124704fd306cSNickeau * @throws ExceptionCompile 12484cadd4f8SNickeau */ 124904fd306cSNickeau public 125004fd306cSNickeau function replicateAnalytics() 1251c3437056SNickeau { 1252c3437056SNickeau 1253c3437056SNickeau try { 125404fd306cSNickeau $fetchPath = $this->markupPath->fetchAnalyticsPath(); 125504fd306cSNickeau $analyticsJson = Json::createFromPath($fetchPath); 125604fd306cSNickeau } catch (ExceptionCompile $e) { 125704fd306cSNickeau if (PluginUtility::isDevOrTest()) { 125804fd306cSNickeau throw $e; 125904fd306cSNickeau } 126004fd306cSNickeau throw new ExceptionCompile("Unable to get the analytics document", self::CANONICAL, 0, $e); 1261c3437056SNickeau } 1262c3437056SNickeau 1263c3437056SNickeau /** 1264c3437056SNickeau * Replication Date 1265c3437056SNickeau */ 126604fd306cSNickeau $replicationDateMeta = ReplicationDate::createFromPage($this->markupPath) 1267c3437056SNickeau ->setWriteStore(MetadataDbStore::class) 12684cadd4f8SNickeau ->setValue(new DateTime()); 1269c3437056SNickeau 1270c3437056SNickeau /** 1271c3437056SNickeau * Analytics 1272c3437056SNickeau */ 1273c3437056SNickeau $analyticsJsonAsString = $analyticsJson->toPrettyJsonString(); 1274c3437056SNickeau $analyticsJsonAsArray = $analyticsJson->toArray(); 1275c3437056SNickeau 1276c3437056SNickeau /** 1277c3437056SNickeau * Record 1278c3437056SNickeau */ 1279c3437056SNickeau $record[self::ANALYTICS_ATTRIBUTE] = $analyticsJsonAsString; 128004fd306cSNickeau $record['IS_LOW_QUALITY'] = ($this->markupPath->isLowQualityPage() === true ? 1 : 0); 128104fd306cSNickeau $record['WORD_COUNT'] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][renderer_plugin_combo_analytics::WORD_COUNT]; 128204fd306cSNickeau $record[BacklinkCount::getPersistentName()] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][BacklinkCount::getPersistentName()]; 1283c3437056SNickeau $record[$replicationDateMeta::getPersistentName()] = $replicationDateMeta->toStoreValue(); 1284c3437056SNickeau $this->upsertAttributes($record); 1285c3437056SNickeau } 1286c3437056SNickeau 128704fd306cSNickeau private 128804fd306cSNickeau function checkCollision($wikiIdInDatabase, $attribute, $value) 128904fd306cSNickeau { 129004fd306cSNickeau if ($this->markupPath === null) { 129104fd306cSNickeau return; 129204fd306cSNickeau } 129304fd306cSNickeau try { 129404fd306cSNickeau $markupWikiId = $this->markupPath->toWikiPath()->getWikiId(); 129504fd306cSNickeau } catch (ExceptionCast $e) { 129604fd306cSNickeau return; 129704fd306cSNickeau } 129804fd306cSNickeau if ($wikiIdInDatabase !== $markupWikiId) { 129904fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($wikiIdInDatabase); 130004fd306cSNickeau if (!FileSystems::exists($duplicatePage)) { 130104fd306cSNickeau // Move 130204fd306cSNickeau LogUtility::info("The non-existing duplicate page ($wikiIdInDatabase) has been added as redirect alias for the page ($this->markupPath)", self::CANONICAL); 130304fd306cSNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 130404fd306cSNickeau } else { 130504fd306cSNickeau // This can happens if two page were created not on the same website 130604fd306cSNickeau // of if the sqlite database was deleted and rebuilt. 130704fd306cSNickeau // The chance is really, really low 130804fd306cSNickeau $errorMessage = "The page ($this->markupPath) and the page ($wikiIdInDatabase) have the same $attribute value ($value)"; 130904fd306cSNickeau throw new ExceptionRuntimeInternal($errorMessage, self::CANONICAL); 131004fd306cSNickeau // What to do ? 131104fd306cSNickeau // The database does not allow two page id with the same value 131204fd306cSNickeau // If it happens, ugh, ugh, ..., a replication process between website may be. 131304fd306cSNickeau } 131404fd306cSNickeau } 131504fd306cSNickeau } 131604fd306cSNickeau 1317031d4b49Sgerardnico /** 1318031d4b49Sgerardnico * @throws ExceptionSqliteNotAvailable 1319031d4b49Sgerardnico * @throws ExceptionNotFound 1320031d4b49Sgerardnico */ 1321031d4b49Sgerardnico private function getDatabaseRowFromRowId(string $rowId) 1322031d4b49Sgerardnico { 1323031d4b49Sgerardnico 1324031d4b49Sgerardnico $query = $this->getParametrizedLookupQuery(self::ROWID); 1325031d4b49Sgerardnico $request = Sqlite::createOrGetSqlite() 1326031d4b49Sgerardnico ->createRequest() 1327031d4b49Sgerardnico ->setQueryParametrized($query, [$rowId]); 1328031d4b49Sgerardnico $rows = []; 1329031d4b49Sgerardnico try { 1330031d4b49Sgerardnico $rows = $request 1331031d4b49Sgerardnico ->execute() 1332031d4b49Sgerardnico ->getRows(); 1333031d4b49Sgerardnico } catch (ExceptionCompile $e) { 1334031d4b49Sgerardnico throw new ExceptionRuntimeInternal("Error while retrieving the object by rowid ($rowId)", self::CANONICAL, 1, $e); 1335031d4b49Sgerardnico } finally { 1336031d4b49Sgerardnico $request->close(); 1337031d4b49Sgerardnico } 1338031d4b49Sgerardnico 1339031d4b49Sgerardnico $rowCount = sizeof($rows); 1340031d4b49Sgerardnico switch ($rowCount) { 1341031d4b49Sgerardnico case 0: 1342031d4b49Sgerardnico throw new ExceptionNotFound("No object by row id ($rowId)"); 1343031d4b49Sgerardnico case 1: 1344031d4b49Sgerardnico return $rows[0]; 1345031d4b49Sgerardnico default: 1346031d4b49Sgerardnico throw new ExceptionRuntimeInternal("Too much record ($rowCount) for the rowid ($rowId)", self::CANONICAL); 1347031d4b49Sgerardnico } 1348031d4b49Sgerardnico 1349031d4b49Sgerardnico } 1350031d4b49Sgerardnico 1351c3437056SNickeau 1352c3437056SNickeau} 1353