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) { 6388dfa0a7aSNico $pageId = $values[PageId::getPersistentName()] ?? null; 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 657*c0705265SNico try { 658*c0705265SNico $wikiPath = $this->markupPath->toWikiPath(); 659*c0705265SNico } catch (ExceptionCast $e) { 660*c0705265SNico // should not happen but yeah 661*c0705265SNico throw new ExceptionBadState("The markup path {$this->markupPath} could not be transformed as wiki path"); 662*c0705265SNico } 663*c0705265SNico $values[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] = $wikiPath->getWikiId(); 664*c0705265SNico $values[PagePath::PROPERTY_NAME] = $wikiPath->toAbsoluteId(); 665c3437056SNickeau /** 666c3437056SNickeau * Default implements the auto-canonical feature 667c3437056SNickeau */ 66804fd306cSNickeau try { 6692d486314SNico $values[Canonical::PROPERTY_NAME] = Canonical::createForPage($this->markupPath)->getValueOrDefault()->toAbsoluteId(); 67004fd306cSNickeau } catch (ExceptionNotFound $e) { 67104fd306cSNickeau $values[Canonical::PROPERTY_NAME] = null; 67204fd306cSNickeau } 673c3437056SNickeau 674c3437056SNickeau /** 675c3437056SNickeau * Analytics 676c3437056SNickeau */ 677be61a7dfSgerardnico $analyticsAttributeValue = $values[self::ANALYTICS_ATTRIBUTE] ?? null; 678be61a7dfSgerardnico if (!isset($analyticsAttributeValue)) { 679c3437056SNickeau // otherwise we get an empty string 680c3437056SNickeau // and a json function will not work 681c3437056SNickeau $values[self::ANALYTICS_ATTRIBUTE] = Json::createEmpty()->toPrettyJsonString(); 682c3437056SNickeau } 683c3437056SNickeau 684c3437056SNickeau /** 685c3437056SNickeau * Page Id / Abbr are mandatory for url redirection 686c3437056SNickeau */ 687c3437056SNickeau $this->addPageIdAttributeIfNeeded($values); 688c3437056SNickeau 689c3437056SNickeau $request = $this->sqlite 690c3437056SNickeau ->createRequest() 691c3437056SNickeau ->setTableRow('PAGES', $values); 692c3437056SNickeau try { 693c3437056SNickeau /** 694c3437056SNickeau * rowid is used in {@link DatabasePageRow::exists()} 695c3437056SNickeau * to check if the page exists in the database 696c3437056SNickeau * We update it 697c3437056SNickeau */ 698c3437056SNickeau $this->row[self::ROWID] = $request 699c3437056SNickeau ->execute() 700c3437056SNickeau ->getInsertId(); 701c3437056SNickeau $this->row = array_merge($values, $this->row); 70204fd306cSNickeau } catch (ExceptionCompile $e) { 70304fd306cSNickeau throw new ExceptionBadState("There was a problem during the updateAttributes insert. : {$e->getMessage()}"); 704c3437056SNickeau } finally { 705c3437056SNickeau $request->close(); 706c3437056SNickeau } 707c3437056SNickeau 708c3437056SNickeau } 709c3437056SNickeau 710c3437056SNickeau } 711c3437056SNickeau 712c3437056SNickeau public 713c3437056SNickeau function getDescription() 714c3437056SNickeau { 715c3437056SNickeau return $this->getFromRow(PageDescription::DESCRIPTION_PROPERTY); 716c3437056SNickeau } 717c3437056SNickeau 718c3437056SNickeau 719c3437056SNickeau public 720c3437056SNickeau function getPageName() 721c3437056SNickeau { 722c3437056SNickeau return $this->getFromRow(ResourceName::PROPERTY_NAME); 723c3437056SNickeau } 724c3437056SNickeau 725c3437056SNickeau public 726c3437056SNickeau function exists(): bool 727c3437056SNickeau { 728c3437056SNickeau return $this->getFromRow(self::ROWID) !== null; 729c3437056SNickeau } 730c3437056SNickeau 731c3437056SNickeau /** 732c3437056SNickeau * Called when a page is moved 733c3437056SNickeau * @param $targetId 734c3437056SNickeau */ 735c3437056SNickeau public 736c3437056SNickeau function updatePathAndDokuwikiId($targetId) 737c3437056SNickeau { 738c3437056SNickeau if (!$this->exists()) { 73904fd306cSNickeau LogUtility::error("The `database` page ($this) does not exist and cannot be moved to ($targetId)"); 740c3437056SNickeau } 741c3437056SNickeau 742c3437056SNickeau $path = $targetId; 74304fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 744c3437056SNickeau $attributes = [ 745c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE => $targetId, 746c3437056SNickeau PagePath::PROPERTY_NAME => $path 747c3437056SNickeau ]; 748c3437056SNickeau 749c3437056SNickeau $this->upsertAttributes($attributes); 750c3437056SNickeau 751c3437056SNickeau } 752c3437056SNickeau 753c3437056SNickeau public 754c3437056SNickeau function __toString() 755c3437056SNickeau { 75604fd306cSNickeau return $this->markupPath->__toString(); 757c3437056SNickeau } 758c3437056SNickeau 759c3437056SNickeau 760c3437056SNickeau /** 761c3437056SNickeau * Redirect are now added during a move 762c3437056SNickeau * Not when a duplicate is found. 763c3437056SNickeau * With the advent of the page id, it should never occurs anymore 76404fd306cSNickeau * @param MarkupPath $page 765c3437056SNickeau * @deprecated 2012-10-28 766c3437056SNickeau */ 767c3437056SNickeau private 76804fd306cSNickeau function deleteIfExistsAndAddRedirectAlias(MarkupPath $page): void 769c3437056SNickeau { 770c3437056SNickeau 77104fd306cSNickeau if ($this->markupPath != null) { 772c3437056SNickeau $page->getDatabasePage()->deleteIfExist(); 773c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($page); 774c3437056SNickeau } 775c3437056SNickeau 776c3437056SNickeau } 777c3437056SNickeau 778c3437056SNickeau public 779c3437056SNickeau function getCanonical() 780c3437056SNickeau { 781c3437056SNickeau return $this->getFromRow(Canonical::PROPERTY_NAME); 782c3437056SNickeau } 783c3437056SNickeau 784c3437056SNickeau /** 785c3437056SNickeau * Set the field to their values 786c3437056SNickeau * @param $row 787c3437056SNickeau */ 788c3437056SNickeau public 789c3437056SNickeau function setRow($row) 790c3437056SNickeau { 791c3437056SNickeau if ($row === null) { 792c3437056SNickeau LogUtility::msg("A row should not be null"); 793c3437056SNickeau return; 794c3437056SNickeau } 795c3437056SNickeau if (!is_array($row)) { 796c3437056SNickeau LogUtility::msg("A row should be an array"); 797c3437056SNickeau return; 798c3437056SNickeau } 799c3437056SNickeau 800c3437056SNickeau /** 801c3437056SNickeau * All get function lookup the row 802c3437056SNickeau */ 803c3437056SNickeau $this->row = $row; 804c3437056SNickeau 805c3437056SNickeau 806c3437056SNickeau } 807c3437056SNickeau 808c3437056SNickeau private 809c3437056SNickeau function buildInitObjectFields() 810c3437056SNickeau { 811c3437056SNickeau $this->row = null; 812c3437056SNickeau 813c3437056SNickeau } 814c3437056SNickeau 815c3437056SNickeau public 816c3437056SNickeau function rebuild(): DatabasePageRow 817c3437056SNickeau { 818c3437056SNickeau 81904fd306cSNickeau if ($this->markupPath != null) { 82004fd306cSNickeau $this->markupPath->rebuild(); 82104fd306cSNickeau try { 82204fd306cSNickeau $row = $this->getDatabaseRowFromPage($this->markupPath); 823c3437056SNickeau $this->setRow($row); 82404fd306cSNickeau } catch (ExceptionNotExists $e) { 82504fd306cSNickeau // ok 826c3437056SNickeau } 827c3437056SNickeau } 828c3437056SNickeau return $this; 829c3437056SNickeau 830c3437056SNickeau } 831c3437056SNickeau 832c3437056SNickeau /** 833c3437056SNickeau * @return array - an array of the fix page metadata (ie not derived) 834c3437056SNickeau * Therefore quick to insert/update 835c3437056SNickeau * 836c3437056SNickeau */ 837c3437056SNickeau private 838c3437056SNickeau function getMetaRecord(): array 839c3437056SNickeau { 84004fd306cSNickeau $sourceStore = MetadataDokuWikiStore::getOrCreateFromResource($this->markupPath); 84104fd306cSNickeau $targetStore = MetadataDbStore::getOrCreateFromResource($this->markupPath); 842c3437056SNickeau 843c3437056SNickeau $record = array( 844c3437056SNickeau Canonical::PROPERTY_NAME, 845c3437056SNickeau PagePath::PROPERTY_NAME, 846c3437056SNickeau ResourceName::PROPERTY_NAME, 847c3437056SNickeau PageTitle::TITLE, 848c3437056SNickeau PageH1::PROPERTY_NAME, 849c3437056SNickeau PageDescription::PROPERTY_NAME, 85004fd306cSNickeau CreationDate::PROPERTY_NAME, 851c3437056SNickeau ModificationDate::PROPERTY_NAME, 852c3437056SNickeau PagePublicationDate::PROPERTY_NAME, 853c3437056SNickeau StartDate::PROPERTY_NAME, 854c3437056SNickeau EndDate::PROPERTY_NAME, 855c3437056SNickeau Region::PROPERTY_NAME, 856c3437056SNickeau Lang::PROPERTY_NAME, 857c3437056SNickeau PageType::PROPERTY_NAME, 858c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, 85904fd306cSNickeau PageLevel::PROPERTY_NAME 860c3437056SNickeau ); 861c3437056SNickeau $metaRecord = []; 862c3437056SNickeau foreach ($record as $name) { 86304fd306cSNickeau try { 86404fd306cSNickeau $metadata = Meta\Api\MetadataSystem::getForName($name); 86504fd306cSNickeau } catch (ExceptionNotFound $e) { 86604fd306cSNickeau LogUtility::internalError("The metadata ($name) is unknown", self::CANONICAL); 86704fd306cSNickeau continue; 868c3437056SNickeau } 869c3437056SNickeau $metaRecord[$name] = $metadata 87004fd306cSNickeau ->setResource($this->markupPath) 871c3437056SNickeau ->setReadStore($sourceStore) 872c3437056SNickeau ->buildFromReadStore() 873c3437056SNickeau ->setWriteStore($targetStore) 874c3437056SNickeau ->toStoreValueOrDefault(); // used by the template, the value is or default 87504fd306cSNickeau 876c3437056SNickeau } 877c3437056SNickeau 87804fd306cSNickeau try { 879c3437056SNickeau $this->addPageIdMeta($metaRecord); 88004fd306cSNickeau } catch (ExceptionNotExists $e) { 88104fd306cSNickeau // no page id for non-existent page ok 882c3437056SNickeau } 88304fd306cSNickeau 88404fd306cSNickeau // Is index 88504fd306cSNickeau $metaRecord[self::IS_INDEX_COLUMN] = ($this->markupPath->isIndexPage() === true ? 1 : 0); 88604fd306cSNickeau 887c3437056SNickeau return $metaRecord; 888c3437056SNickeau } 889c3437056SNickeau 890c3437056SNickeau public 891c3437056SNickeau function deleteIfExist(): DatabasePageRow 892c3437056SNickeau { 893c3437056SNickeau if ($this->exists()) { 894c3437056SNickeau $this->delete(); 895c3437056SNickeau } 896c3437056SNickeau return $this; 897c3437056SNickeau } 898c3437056SNickeau 899c3437056SNickeau public 900c3437056SNickeau function getRowId() 901c3437056SNickeau { 902c3437056SNickeau return $this->getFromRow(self::ROWID); 903c3437056SNickeau } 904c3437056SNickeau 90504fd306cSNickeau /** 90604fd306cSNickeau * @throws ExceptionNotFound 907031d4b49Sgerardnico * @throws ExceptionSqliteNotAvailable 90804fd306cSNickeau */ 909c3437056SNickeau private 91004fd306cSNickeau function getDatabaseRowFromPageId(string $pageIdValue) 911c3437056SNickeau { 912c3437056SNickeau 913c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 914c3437056SNickeau $query = $this->getParametrizedLookupQuery($pageIdAttribute); 915c3437056SNickeau $request = Sqlite::createOrGetSqlite() 916c3437056SNickeau ->createRequest() 91704fd306cSNickeau ->setQueryParametrized($query, [$pageIdValue]); 918c3437056SNickeau $rows = []; 919c3437056SNickeau try { 920c3437056SNickeau $rows = $request 921c3437056SNickeau ->execute() 922c3437056SNickeau ->getRows(); 92304fd306cSNickeau } catch (ExceptionCompile $e) { 92404fd306cSNickeau throw new ExceptionRuntimeInternal("Error while retrieving the object by id", self::CANONICAL, 1, $e); 925c3437056SNickeau } finally { 926c3437056SNickeau $request->close(); 927c3437056SNickeau } 928c3437056SNickeau 929c3437056SNickeau switch (sizeof($rows)) { 930c3437056SNickeau case 0: 93104fd306cSNickeau throw new ExceptionNotFound("No object by page id"); 932c3437056SNickeau case 1: 933c3437056SNickeau /** 934c3437056SNickeau * Page Id Collision detection 935c3437056SNickeau */ 93604fd306cSNickeau $rowId = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 93704fd306cSNickeau $this->checkCollision($rowId, $pageIdAttribute, $pageIdValue); 938c3437056SNickeau return $rows[0]; 939c3437056SNickeau default: 940c3437056SNickeau $existingPages = implode(", ", $rows); 94104fd306cSNickeau $message = "The pages ($existingPages) have all the same page id ($pageIdValue)"; 94204fd306cSNickeau throw new ExceptionRuntimeInternal($message, self::CANONICAL); 943c3437056SNickeau } 944c3437056SNickeau 945c3437056SNickeau } 946c3437056SNickeau 947c3437056SNickeau 948c3437056SNickeau private 94904fd306cSNickeau function getParametrizedLookupQuery(string $attributeName): string 950c3437056SNickeau { 951c3437056SNickeau $select = Sqlite::createSelectFromTableAndColumns("pages", self::PAGE_BUILD_ATTRIBUTES); 95204fd306cSNickeau return "$select where $attributeName = ?"; 953c3437056SNickeau } 954c3437056SNickeau 955c3437056SNickeau 95604fd306cSNickeau public 95704fd306cSNickeau function setMarkupPath(MarkupPath $page) 958c3437056SNickeau { 95904fd306cSNickeau $this->markupPath = $page; 960c3437056SNickeau return $this; 961c3437056SNickeau } 962c3437056SNickeau 96304fd306cSNickeau /** 96404fd306cSNickeau * @throws ExceptionNotFound 96504fd306cSNickeau */ 966c3437056SNickeau private 96704fd306cSNickeau function getDatabaseRowFromCanonical($canonicalValue) 968c3437056SNickeau { 969*c0705265SNico $canonicalName = Canonical::PROPERTY_NAME; 970*c0705265SNico $query = $this->getParametrizedLookupQuery($canonicalName); 971c3437056SNickeau $request = $this->sqlite 972c3437056SNickeau ->createRequest() 97304fd306cSNickeau ->setQueryParametrized($query, [$canonicalValue]); 974c3437056SNickeau $rows = []; 975c3437056SNickeau try { 976c3437056SNickeau $rows = $request 977c3437056SNickeau ->execute() 978c3437056SNickeau ->getRows(); 97904fd306cSNickeau } catch (ExceptionCompile $e) { 98004fd306cSNickeau throw new ExceptionRuntime("An exception has occurred with the page search from CANONICAL. " . $e->getMessage()); 981c3437056SNickeau } finally { 982c3437056SNickeau $request->close(); 983c3437056SNickeau } 984c3437056SNickeau 985c3437056SNickeau switch (sizeof($rows)) { 986c3437056SNickeau case 0: 98704fd306cSNickeau throw new ExceptionNotFound("No canonical row was found"); 988c3437056SNickeau case 1: 989c3437056SNickeau $id = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 990*c0705265SNico $this->checkCollision($id, $canonicalName, $canonicalValue); 991c3437056SNickeau return $rows[0]; 992c3437056SNickeau default: 993c3437056SNickeau $existingPages = []; 994c3437056SNickeau foreach ($rows as $row) { 995c3437056SNickeau $id = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 99604fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($id); 997c3437056SNickeau if (!$duplicatePage->exists()) { 998c3437056SNickeau 999c3437056SNickeau $this->deleteIfExistsAndAddRedirectAlias($duplicatePage); 1000c3437056SNickeau 1001c3437056SNickeau } else { 1002c3437056SNickeau 1003c3437056SNickeau /** 1004c3437056SNickeau * Check if the error may come from the auto-canonical 1005c3437056SNickeau * (Never ever save generated data) 1006c3437056SNickeau */ 100704fd306cSNickeau $canonicalLastNamesCount = SiteConfig::getConfValue(Canonical::CONF_CANONICAL_LAST_NAMES_COUNT, 0); 1008c3437056SNickeau if ($canonicalLastNamesCount > 0) { 1009*c0705265SNico $this->markupPath->unsetMetadata($canonicalName); 1010*c0705265SNico $duplicatePage->unsetMetadata($canonicalName); 1011c3437056SNickeau } 1012c3437056SNickeau 1013c3437056SNickeau $existingPages[] = $row; 1014c3437056SNickeau } 1015c3437056SNickeau } 101604fd306cSNickeau if (sizeof($existingPages) > 1) { 1017c3437056SNickeau $existingPages = implode(", ", $existingPages); 101804fd306cSNickeau $message = "The existing pages ($existingPages) have all the same canonical ($canonicalValue), return the first one"; 101904fd306cSNickeau LogUtility::error($message, self::CANONICAL); 1020c3437056SNickeau } 102104fd306cSNickeau return $existingPages[0]; 1022c3437056SNickeau } 1023c3437056SNickeau } 1024c3437056SNickeau 102504fd306cSNickeau /** 102604fd306cSNickeau * @throws ExceptionNotFound 102704fd306cSNickeau */ 1028c3437056SNickeau private 1029c3437056SNickeau function getDatabaseRowFromPath(string $path): ?array 1030c3437056SNickeau { 103104fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 1032c3437056SNickeau return $this->getDatabaseRowFromAttribute(PagePath::PROPERTY_NAME, $path); 1033c3437056SNickeau } 1034c3437056SNickeau 103504fd306cSNickeau /** 103604fd306cSNickeau * @throws ExceptionNotFound 103704fd306cSNickeau */ 1038c3437056SNickeau private 103904fd306cSNickeau function getDatabaseRowFromDokuWikiId(string $id): array 1040c3437056SNickeau { 1041c3437056SNickeau return $this->getDatabaseRowFromAttribute(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $id); 1042c3437056SNickeau } 1043c3437056SNickeau 104404fd306cSNickeau /** 104504fd306cSNickeau * @throws ExceptionNotFound 104604fd306cSNickeau */ 1047c3437056SNickeau public 1048031d4b49Sgerardnico function getDatabaseRowFromAttribute(string $attribute, string $attributeValue) 1049c3437056SNickeau { 1050c3437056SNickeau $query = $this->getParametrizedLookupQuery($attribute); 1051c3437056SNickeau $request = $this->sqlite 1052c3437056SNickeau ->createRequest() 1053031d4b49Sgerardnico ->setQueryParametrized($query, [$attributeValue]); 1054c3437056SNickeau $rows = []; 1055c3437056SNickeau try { 1056c3437056SNickeau $rows = $request 1057c3437056SNickeau ->execute() 1058c3437056SNickeau ->getRows(); 105904fd306cSNickeau } catch (ExceptionCompile $e) { 106004fd306cSNickeau $message = "Internal Error: An exception has occurred with the page search from a PATH: " . $e->getMessage(); 106104fd306cSNickeau LogUtility::log2file($message); 106204fd306cSNickeau throw new ExceptionNotFound($message); 1063c3437056SNickeau } finally { 1064c3437056SNickeau $request->close(); 1065c3437056SNickeau } 1066c3437056SNickeau 1067031d4b49Sgerardnico $rowCount = sizeof($rows); 1068031d4b49Sgerardnico switch ($rowCount) { 1069c3437056SNickeau case 0: 107004fd306cSNickeau throw new ExceptionNotFound("No database row found for the page"); 1071c3437056SNickeau case 1: 1072031d4b49Sgerardnico $attributeValue = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 1073031d4b49Sgerardnico try { 107411d09b86Sgerardnico if ($this->markupPath != null && $attributeValue !== $this->markupPath->getWikiId()) { 1075031d4b49Sgerardnico $duplicatePage = MarkupPath::createMarkupFromId($attributeValue); 1076c3437056SNickeau if (!$duplicatePage->exists()) { 1077c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 1078c3437056SNickeau } else { 1079031d4b49Sgerardnico LogUtility::error("The page ($this->markupPath) and the page ($attributeValue) have the same $attribute ($attributeValue)"); 1080c3437056SNickeau } 1081c3437056SNickeau } 108211d09b86Sgerardnico } catch (ExceptionBadArgument $e) { 108311d09b86Sgerardnico throw new ExceptionRuntimeInternal("The wiki id should be available"); 108411d09b86Sgerardnico } 1085c3437056SNickeau return $rows[0]; 1086c3437056SNickeau default: 1087031d4b49Sgerardnico LogUtility::warning("Error: More than 1 rows ($rowCount) found for attribute ($attribute) with the value ($attributeValue)"); 1088031d4b49Sgerardnico 1089031d4b49Sgerardnico /** 1090031d4b49Sgerardnico * Trying to delete the bad one 1091031d4b49Sgerardnico */ 1092c3437056SNickeau $existingPages = []; 1093c3437056SNickeau foreach ($rows as $row) { 1094c3437056SNickeau 1095031d4b49Sgerardnico $rowId = $row[self::ROWID] ?? null; 1096031d4b49Sgerardnico if ($rowId === null) { 1097031d4b49Sgerardnico LogUtility::error("A row id should be present"); 1098c3437056SNickeau $existingPages[] = $row; 1099031d4b49Sgerardnico continue; 1100c3437056SNickeau } 1101031d4b49Sgerardnico // page id throw for several id 1102031d4b49Sgerardnico $duplicateRow = DatabasePageRow::createFromRowId($rowId); 1103031d4b49Sgerardnico $duplicateMarkupPath = $duplicateRow->getMarkupPath(); 1104031d4b49Sgerardnico if (!FileSystems::exists($duplicateMarkupPath)) { 1105031d4b49Sgerardnico LogUtility::warning("The duplicate page ($duplicateMarkupPath) does not exists. Delete Row ({$rowId})"); 1106031d4b49Sgerardnico $duplicateRow->delete(); 1107031d4b49Sgerardnico $this->addRedirectAliasWhileBuildingRow($duplicateMarkupPath); 1108031d4b49Sgerardnico continue; 1109031d4b49Sgerardnico } 1110031d4b49Sgerardnico $existingPages[] = $row; 1111c3437056SNickeau } 1112c3437056SNickeau if (sizeof($existingPages) === 1) { 1113c3437056SNickeau return $existingPages[0]; 1114c3437056SNickeau } else { 111504fd306cSNickeau $existingPageIds = array_map( 111604fd306cSNickeau function ($row) { 111704fd306cSNickeau return $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 111804fd306cSNickeau }, 111904fd306cSNickeau $existingPages); 112004fd306cSNickeau $existingPages = implode(", ", $existingPageIds); 1121031d4b49Sgerardnico throw new ExceptionNotFound("The existing pages ($existingPages) have all the same attribute $attribute with the value ($attributeValue)", LogUtility::LVL_MSG_ERROR); 1122c3437056SNickeau } 1123c3437056SNickeau } 1124c3437056SNickeau } 1125c3437056SNickeau 1126c3437056SNickeau public 112704fd306cSNickeau function getMarkupPath(): ?MarkupPath 1128c3437056SNickeau { 112970bbd7f1Sgerardnico if ($this->row === null) { 113070bbd7f1Sgerardnico return null; 113170bbd7f1Sgerardnico } 1132c3437056SNickeau if ( 113304fd306cSNickeau $this->markupPath === null 1134c3437056SNickeau && $this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] !== null 1135c3437056SNickeau ) { 113604fd306cSNickeau $this->markupPath = MarkupPath::createMarkupFromId($this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]); 1137c3437056SNickeau } 113804fd306cSNickeau return $this->markupPath; 1139c3437056SNickeau } 1140c3437056SNickeau 1141c3437056SNickeau private 1142c3437056SNickeau function getDatabaseRowFromAlias($alias): ?array 1143c3437056SNickeau { 1144c3437056SNickeau 1145c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 1146c3437056SNickeau $buildFields = self::PAGE_BUILD_ATTRIBUTES; 1147c3437056SNickeau $fields = array_reduce($buildFields, function ($carry, $element) { 1148c3437056SNickeau if ($carry !== null) { 1149c3437056SNickeau return "$carry, p.{$element}"; 1150c3437056SNickeau } else { 1151c3437056SNickeau return "p.{$element}"; 1152c3437056SNickeau } 1153c3437056SNickeau }, null); 115404fd306cSNickeau /** @noinspection SqlResolve */ 1155c3437056SNickeau $query = "select {$fields} from PAGES p, PAGE_ALIASES pa where p.{$pageIdAttribute} = pa.{$pageIdAttribute} and pa.PATH = ? "; 1156c3437056SNickeau $request = $this->sqlite 1157c3437056SNickeau ->createRequest() 1158c3437056SNickeau ->setQueryParametrized($query, [$alias]); 1159c3437056SNickeau $rows = []; 1160c3437056SNickeau try { 1161c3437056SNickeau $rows = $request 1162c3437056SNickeau ->execute() 1163c3437056SNickeau ->getRows(); 116404fd306cSNickeau } catch (ExceptionCompile $e) { 1165c3437056SNickeau LogUtility::msg("An exception has occurred with the alias selection query. {$e->getMessage()}"); 1166c3437056SNickeau return null; 1167c3437056SNickeau } finally { 1168c3437056SNickeau $request->close(); 1169c3437056SNickeau } 1170c3437056SNickeau switch (sizeof($rows)) { 1171c3437056SNickeau case 0: 1172c3437056SNickeau return null; 1173c3437056SNickeau case 1: 1174c3437056SNickeau return $rows[0]; 1175c3437056SNickeau default: 1176c3437056SNickeau $id = $rows[0]['ID']; 1177c3437056SNickeau $pages = implode(",", 1178c3437056SNickeau array_map( 1179c3437056SNickeau function ($row) { 1180c3437056SNickeau return $row['ID']; 1181c3437056SNickeau }, 1182c3437056SNickeau $rows 1183c3437056SNickeau ) 1184c3437056SNickeau ); 1185c3437056SNickeau 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); 1186c3437056SNickeau return $rows[0]; 1187c3437056SNickeau } 1188c3437056SNickeau } 1189c3437056SNickeau 1190c3437056SNickeau 1191c3437056SNickeau /** 1192c3437056SNickeau * Utility function 119304fd306cSNickeau * @param MarkupPath $pageAlias 1194c3437056SNickeau */ 1195c3437056SNickeau private 119604fd306cSNickeau function addRedirectAliasWhileBuildingRow(MarkupPath $pageAlias) 1197c3437056SNickeau { 1198c3437056SNickeau 119904fd306cSNickeau $aliasPath = $pageAlias->getPathObject()->toAbsoluteId(); 1200d899a2a6Sgerardnico LogUtility::info("Add alias ($aliasPath) for page ({$this->markupPath})"); 1201c3437056SNickeau try { 120204fd306cSNickeau Aliases::createForPage($this->markupPath) 1203c3437056SNickeau ->addAlias($aliasPath) 1204c3437056SNickeau ->sendToWriteStore(); 120504fd306cSNickeau } catch (ExceptionCompile $e) { 1206c3437056SNickeau // we don't throw while getting 120704fd306cSNickeau LogUtility::msg("Unable to add the alias ($aliasPath) for the page ($this->markupPath)"); 1208c3437056SNickeau } 1209c3437056SNickeau 1210c3437056SNickeau } 1211c3437056SNickeau 1212c3437056SNickeau private 1213c3437056SNickeau function addPageIdAttributeIfNeeded(array &$values) 1214c3437056SNickeau { 1215c3437056SNickeau if (!isset($values[PageId::getPersistentName()])) { 121604fd306cSNickeau $values[PageId::getPersistentName()] = $this->markupPath->getPageId(); 1217c3437056SNickeau } 1218c3437056SNickeau if (!isset($values[PageId::PAGE_ID_ABBR_ATTRIBUTE])) { 121904fd306cSNickeau $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $this->markupPath->getPageIdAbbr(); 1220c3437056SNickeau } 1221c3437056SNickeau } 1222c3437056SNickeau 1223c3437056SNickeau public 1224c3437056SNickeau function getFromRow(string $attribute) 1225c3437056SNickeau { 1226c3437056SNickeau if ($this->row === null) { 1227c3437056SNickeau return null; 1228c3437056SNickeau } 1229c3437056SNickeau 1230c3437056SNickeau if (!array_key_exists($attribute, $this->row)) { 1231c3437056SNickeau /** 1232c3437056SNickeau * An attribute should be added to {@link DatabasePageRow::PAGE_BUILD_ATTRIBUTES} 1233c3437056SNickeau * or in the table 1234c3437056SNickeau */ 123504fd306cSNickeau throw new ExceptionRuntime("The metadata ($attribute) was not found in the returned database row.", $this->getCanonical()); 1236c3437056SNickeau } 1237c3437056SNickeau 1238c3437056SNickeau $value = $this->row[$attribute]; 1239c3437056SNickeau 1240c3437056SNickeau if ($value !== null) { 1241c3437056SNickeau return $value; 1242c3437056SNickeau } 1243c3437056SNickeau 1244c3437056SNickeau // don't know why but the sqlite plugin returns them uppercase 1245c3437056SNickeau // rowid is returned lowercase from the sqlite plugin 1246c3437056SNickeau $upperAttribute = strtoupper($attribute); 124770bbd7f1Sgerardnico return $this->row[$upperAttribute] ?? null; 1248c3437056SNickeau 1249c3437056SNickeau } 1250c3437056SNickeau 1251c3437056SNickeau 12524cadd4f8SNickeau /** 125304fd306cSNickeau * @throws ExceptionCompile 12544cadd4f8SNickeau */ 125504fd306cSNickeau public 125604fd306cSNickeau function replicateAnalytics() 1257c3437056SNickeau { 1258c3437056SNickeau 1259c3437056SNickeau try { 126004fd306cSNickeau $fetchPath = $this->markupPath->fetchAnalyticsPath(); 126104fd306cSNickeau $analyticsJson = Json::createFromPath($fetchPath); 126204fd306cSNickeau } catch (ExceptionCompile $e) { 126304fd306cSNickeau if (PluginUtility::isDevOrTest()) { 126404fd306cSNickeau throw $e; 126504fd306cSNickeau } 126604fd306cSNickeau throw new ExceptionCompile("Unable to get the analytics document", self::CANONICAL, 0, $e); 1267c3437056SNickeau } 1268c3437056SNickeau 1269c3437056SNickeau /** 1270c3437056SNickeau * Replication Date 1271c3437056SNickeau */ 127204fd306cSNickeau $replicationDateMeta = ReplicationDate::createFromPage($this->markupPath) 1273c3437056SNickeau ->setWriteStore(MetadataDbStore::class) 12744cadd4f8SNickeau ->setValue(new DateTime()); 1275c3437056SNickeau 1276c3437056SNickeau /** 1277c3437056SNickeau * Analytics 1278c3437056SNickeau */ 1279c3437056SNickeau $analyticsJsonAsString = $analyticsJson->toPrettyJsonString(); 1280c3437056SNickeau $analyticsJsonAsArray = $analyticsJson->toArray(); 1281c3437056SNickeau 1282c3437056SNickeau /** 1283c3437056SNickeau * Record 1284c3437056SNickeau */ 1285c3437056SNickeau $record[self::ANALYTICS_ATTRIBUTE] = $analyticsJsonAsString; 128604fd306cSNickeau $record['IS_LOW_QUALITY'] = ($this->markupPath->isLowQualityPage() === true ? 1 : 0); 128704fd306cSNickeau $record['WORD_COUNT'] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][renderer_plugin_combo_analytics::WORD_COUNT]; 128804fd306cSNickeau $record[BacklinkCount::getPersistentName()] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][BacklinkCount::getPersistentName()]; 1289c3437056SNickeau $record[$replicationDateMeta::getPersistentName()] = $replicationDateMeta->toStoreValue(); 1290c3437056SNickeau $this->upsertAttributes($record); 1291c3437056SNickeau } 1292c3437056SNickeau 129304fd306cSNickeau private 1294*c0705265SNico function checkCollision($wikiIdInDatabase, $attribute, $value): void 129504fd306cSNickeau { 129604fd306cSNickeau if ($this->markupPath === null) { 129704fd306cSNickeau return; 129804fd306cSNickeau } 129904fd306cSNickeau try { 130004fd306cSNickeau $markupWikiId = $this->markupPath->toWikiPath()->getWikiId(); 130104fd306cSNickeau } catch (ExceptionCast $e) { 130204fd306cSNickeau return; 130304fd306cSNickeau } 130404fd306cSNickeau if ($wikiIdInDatabase !== $markupWikiId) { 130504fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($wikiIdInDatabase); 130604fd306cSNickeau if (!FileSystems::exists($duplicatePage)) { 130704fd306cSNickeau // Move 130804fd306cSNickeau LogUtility::info("The non-existing duplicate page ($wikiIdInDatabase) has been added as redirect alias for the page ($this->markupPath)", self::CANONICAL); 130904fd306cSNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 131004fd306cSNickeau } else { 131104fd306cSNickeau // This can happens if two page were created not on the same website 131204fd306cSNickeau // of if the sqlite database was deleted and rebuilt. 131304fd306cSNickeau // The chance is really, really low 131404fd306cSNickeau $errorMessage = "The page ($this->markupPath) and the page ($wikiIdInDatabase) have the same $attribute value ($value)"; 1315*c0705265SNico LogUtility::error($errorMessage); 131604fd306cSNickeau // What to do ? 131704fd306cSNickeau // The database does not allow two page id with the same value 131804fd306cSNickeau // If it happens, ugh, ugh, ..., a replication process between website may be. 131904fd306cSNickeau } 132004fd306cSNickeau } 132104fd306cSNickeau } 132204fd306cSNickeau 1323031d4b49Sgerardnico /** 1324031d4b49Sgerardnico * @throws ExceptionSqliteNotAvailable 1325031d4b49Sgerardnico * @throws ExceptionNotFound 1326031d4b49Sgerardnico */ 1327031d4b49Sgerardnico private function getDatabaseRowFromRowId(string $rowId) 1328031d4b49Sgerardnico { 1329031d4b49Sgerardnico 1330031d4b49Sgerardnico $query = $this->getParametrizedLookupQuery(self::ROWID); 1331031d4b49Sgerardnico $request = Sqlite::createOrGetSqlite() 1332031d4b49Sgerardnico ->createRequest() 1333031d4b49Sgerardnico ->setQueryParametrized($query, [$rowId]); 1334031d4b49Sgerardnico $rows = []; 1335031d4b49Sgerardnico try { 1336031d4b49Sgerardnico $rows = $request 1337031d4b49Sgerardnico ->execute() 1338031d4b49Sgerardnico ->getRows(); 1339031d4b49Sgerardnico } catch (ExceptionCompile $e) { 1340031d4b49Sgerardnico throw new ExceptionRuntimeInternal("Error while retrieving the object by rowid ($rowId)", self::CANONICAL, 1, $e); 1341031d4b49Sgerardnico } finally { 1342031d4b49Sgerardnico $request->close(); 1343031d4b49Sgerardnico } 1344031d4b49Sgerardnico 1345031d4b49Sgerardnico $rowCount = sizeof($rows); 1346031d4b49Sgerardnico switch ($rowCount) { 1347031d4b49Sgerardnico case 0: 1348031d4b49Sgerardnico throw new ExceptionNotFound("No object by row id ($rowId)"); 1349031d4b49Sgerardnico case 1: 1350031d4b49Sgerardnico return $rows[0]; 1351031d4b49Sgerardnico default: 1352031d4b49Sgerardnico throw new ExceptionRuntimeInternal("Too much record ($rowCount) for the rowid ($rowId)", self::CANONICAL); 1353031d4b49Sgerardnico } 1354031d4b49Sgerardnico 1355031d4b49Sgerardnico } 1356031d4b49Sgerardnico 1357c3437056SNickeau 1358c3437056SNickeau} 1359