1c3437056SNickeau<?php 2c3437056SNickeau 3c3437056SNickeau 4c3437056SNickeaunamespace ComboStrap; 5c3437056SNickeau 6*04fd306cSNickeauuse ComboStrap\Meta\Api\Metadata; 7*04fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStore; 8*04fd306cSNickeauuse ComboStrap\Meta\Field\Aliases; 9*04fd306cSNickeauuse ComboStrap\Meta\Field\BacklinkCount; 10*04fd306cSNickeauuse ComboStrap\Meta\Field\PageH1; 11*04fd306cSNickeauuse ComboStrap\Meta\Field\Region; 12*04fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDbStore; 13*04fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDokuWikiStore; 144cadd4f8SNickeauuse DateTime; 15*04fd306cSNickeauuse 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, 47*04fd306cSNickeau 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, 56*04fd306cSNickeau 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 68*04fd306cSNickeau const IS_HOME_COLUMN = "is_home"; 69*04fd306cSNickeau const IS_INDEX_COLUMN = "is_index"; 70*04fd306cSNickeau 71c3437056SNickeau /** 72*04fd306cSNickeau * @var MarkupPath 73c3437056SNickeau */ 74*04fd306cSNickeau 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. 87*04fd306cSNickeau * @throws ExceptionSqliteNotAvailable 88c3437056SNickeau */ 89c3437056SNickeau public function __construct() 90c3437056SNickeau { 91c3437056SNickeau /** 92c3437056SNickeau * Persist on the DB 93c3437056SNickeau */ 94c3437056SNickeau $this->sqlite = Sqlite::createOrGetSqlite(); 95c3437056SNickeau 96c3437056SNickeau 97c3437056SNickeau } 98c3437056SNickeau 99*04fd306cSNickeau public static function getFromPageObject(MarkupPath $page): DatabasePageRow 100*04fd306cSNickeau { 101*04fd306cSNickeau $databasePage = new DatabasePageRow(); 102*04fd306cSNickeau try { 103*04fd306cSNickeau $row = $databasePage->getDatabaseRowFromPage($page); 104*04fd306cSNickeau $databasePage->setRow($row); 105*04fd306cSNickeau return $databasePage; 106*04fd306cSNickeau } catch (ExceptionNotExists $e) { 107*04fd306cSNickeau // 108*04fd306cSNickeau } 109*04fd306cSNickeau return $databasePage; 110*04fd306cSNickeau } 111*04fd306cSNickeau 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 * 123*04fd306cSNickeau * @throws ExceptionCompile 124c3437056SNickeau */ 125c3437056SNickeau public function replicate(): DatabasePageRow 126c3437056SNickeau { 127c3437056SNickeau 128*04fd306cSNickeau if (!FileSystems::exists($this->markupPath)) { 129*04fd306cSNickeau 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 ]; 145*04fd306cSNickeau $fsStore = MetadataDokuWikiStore::getOrCreateFromResource($this->markupPath); 146*04fd306cSNickeau $dbStore = MetadataDbStore::getOrCreateFromResource($this->markupPath); 147c3437056SNickeau foreach ($tabularMetadataToSync as $tabular) { 148c3437056SNickeau $tabular 149*04fd306cSNickeau ->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 /** 169*04fd306cSNickeau * @throws ExceptionCompile 170c3437056SNickeau */ 171*04fd306cSNickeau public function replicateAndRebuild(): DatabasePageRow 172c3437056SNickeau { 173c3437056SNickeau $this->replicate(); 174c3437056SNickeau $this->rebuild(); 175c3437056SNickeau return $this; 176c3437056SNickeau } 177c3437056SNickeau 178*04fd306cSNickeau /** 179*04fd306cSNickeau * @throws ExceptionNotExists - no page id to add 180*04fd306cSNickeau */ 181c3437056SNickeau private function addPageIdMeta(array &$metaRecord) 182c3437056SNickeau { 183*04fd306cSNickeau try { 184*04fd306cSNickeau $pageId = $this->markupPath->getPageId(); 185*04fd306cSNickeau } catch (ExceptionNotFound $e) { 186*04fd306cSNickeau $pageId = PageId::generateAndStorePageId($this->markupPath); 187*04fd306cSNickeau } 188*04fd306cSNickeau $metaRecord[PageId::PROPERTY_NAME] = $pageId; 189*04fd306cSNickeau $metaRecord[PageId::PAGE_ID_ABBR_ATTRIBUTE] = PageId::getAbbreviated($pageId); 190c3437056SNickeau } 191c3437056SNickeau 192c3437056SNickeau public static function createFromPageId(string $pageId): DatabasePageRow 193c3437056SNickeau { 194c3437056SNickeau $databasePage = new DatabasePageRow(); 195*04fd306cSNickeau try { 196c3437056SNickeau $row = $databasePage->getDatabaseRowFromPageId($pageId); 197c3437056SNickeau $databasePage->setRow($row); 198*04fd306cSNickeau } catch (ExceptionNotFound|ExceptionSqliteNotAvailable $e) { 199*04fd306cSNickeau // not found 200c3437056SNickeau } 201*04fd306cSNickeau 202c3437056SNickeau return $databasePage; 203c3437056SNickeau } 204c3437056SNickeau 205*04fd306cSNickeau /** 206*04fd306cSNickeau * @param MarkupPath $page 207*04fd306cSNickeau * @return DatabasePageRow 208*04fd306cSNickeau * @throws ExceptionSqliteNotAvailable - if there is no sqlite available 209*04fd306cSNickeau * @noinspection PhpDocRedundantThrowsInspection 210*04fd306cSNickeau */ 211*04fd306cSNickeau public static function getOrCreateFromPageObject(MarkupPath $page): DatabasePageRow 212c3437056SNickeau { 213c3437056SNickeau 214c3437056SNickeau $databasePage = new DatabasePageRow(); 215*04fd306cSNickeau try { 216c3437056SNickeau $row = $databasePage->getDatabaseRowFromPage($page); 217c3437056SNickeau $databasePage->setRow($row); 218c3437056SNickeau return $databasePage; 219*04fd306cSNickeau } catch (ExceptionNotExists $e) { 220*04fd306cSNickeau // page copied on the local system 221*04fd306cSNickeau try { 222*04fd306cSNickeau ComboFs::createIfNotExists($page); 223*04fd306cSNickeau $row = $databasePage->getDatabaseRowFromPage($page); 224*04fd306cSNickeau $databasePage->setRow($row); 225*04fd306cSNickeau return $databasePage; 226*04fd306cSNickeau } catch (ExceptionNotExists $e) { 227*04fd306cSNickeau throw ExceptionRuntimeInternal::withMessageAndError("The row should exists as we created it specifically", $e); 228*04fd306cSNickeau } 229c3437056SNickeau } 230c3437056SNickeau 231*04fd306cSNickeau } 232*04fd306cSNickeau 233*04fd306cSNickeau 234*04fd306cSNickeau /** 235*04fd306cSNickeau * 236*04fd306cSNickeau */ 237*04fd306cSNickeau public 238*04fd306cSNickeau static function createFromPageIdAbbr(string $pageIdAbbr): DatabasePageRow 239c3437056SNickeau { 240c3437056SNickeau $databasePage = new DatabasePageRow(); 241*04fd306cSNickeau try { 242c3437056SNickeau $row = $databasePage->getDatabaseRowFromAttribute(PageId::PAGE_ID_ABBR_ATTRIBUTE, $pageIdAbbr); 243c3437056SNickeau $databasePage->setRow($row); 244*04fd306cSNickeau } catch (ExceptionNotFound $e) { 245*04fd306cSNickeau // ok 246c3437056SNickeau } 247c3437056SNickeau return $databasePage; 248c3437056SNickeau 249c3437056SNickeau } 250c3437056SNickeau 251c3437056SNickeau /** 252c3437056SNickeau * @param $canonical 253c3437056SNickeau * @return DatabasePageRow 254c3437056SNickeau */ 255*04fd306cSNickeau public 256*04fd306cSNickeau static function createFromCanonical($canonical): DatabasePageRow 257c3437056SNickeau { 258c3437056SNickeau 259*04fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($canonical); 260c3437056SNickeau $databasePage = new DatabasePageRow(); 261*04fd306cSNickeau try { 262c3437056SNickeau $row = $databasePage->getDatabaseRowFromAttribute(Canonical::PROPERTY_NAME, $canonical); 263c3437056SNickeau $databasePage->setRow($row); 264*04fd306cSNickeau } catch (ExceptionNotFound $e) { 265*04fd306cSNickeau // ok 266c3437056SNickeau } 267c3437056SNickeau return $databasePage; 268c3437056SNickeau 269c3437056SNickeau 270c3437056SNickeau } 271c3437056SNickeau 272*04fd306cSNickeau public 273*04fd306cSNickeau static function createFromAlias($alias): DatabasePageRow 274c3437056SNickeau { 275c3437056SNickeau 276*04fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($alias); 277c3437056SNickeau $databasePage = new DatabasePageRow(); 278c3437056SNickeau $row = $databasePage->getDatabaseRowFromAlias($alias); 279c3437056SNickeau if ($row != null) { 280c3437056SNickeau $databasePage->setRow($row); 281*04fd306cSNickeau $page = $databasePage->getMarkupPath(); 28258317768Sgerardnico if ($page !== null) { 28358317768Sgerardnico // page may be null in production 28458317768Sgerardnico // PHP Fatal error: Uncaught Error: Call to a member function setBuildAliasPath() on null in 28558317768Sgerardnico // /opt/www/bytle/farmer.bytle.net/lib/plugins/combo/ComboStrap/DatabasePageRow.php:220 28658317768Sgerardnico $page->setBuildAliasPath($alias); 28758317768Sgerardnico } 288c3437056SNickeau } 289c3437056SNickeau return $databasePage; 290c3437056SNickeau 291c3437056SNickeau } 292c3437056SNickeau 293*04fd306cSNickeau /** 294*04fd306cSNickeau * @throws ExceptionNotFound 295*04fd306cSNickeau */ 296*04fd306cSNickeau public 297*04fd306cSNickeau static function getFromDokuWikiId($id): DatabasePageRow 298c3437056SNickeau { 299c3437056SNickeau $databasePage = new DatabasePageRow(); 300c3437056SNickeau $row = $databasePage->getDatabaseRowFromDokuWikiId($id); 301c3437056SNickeau $databasePage->setRow($row); 302c3437056SNickeau return $databasePage; 303c3437056SNickeau } 304c3437056SNickeau 305*04fd306cSNickeau public 306*04fd306cSNickeau function getPageId() 307c3437056SNickeau { 308c3437056SNickeau return $this->getFromRow(PageId::PROPERTY_NAME); 309c3437056SNickeau } 310c3437056SNickeau 311c3437056SNickeau 312c3437056SNickeau public 313c3437056SNickeau function shouldReplicate(): bool 314c3437056SNickeau { 315c3437056SNickeau 316*04fd306cSNickeau 3174cadd4f8SNickeau $dateReplication = $this->getReplicationDate(); 3184cadd4f8SNickeau if ($dateReplication === null) { 3194cadd4f8SNickeau return true; 3204cadd4f8SNickeau } 3214cadd4f8SNickeau 3224cadd4f8SNickeau /** 3234cadd4f8SNickeau * When the replication date is older than the actual document 3244cadd4f8SNickeau */ 325*04fd306cSNickeau try { 326*04fd306cSNickeau $modifiedTime = FileSystems::getModifiedTime($this->markupPath->getPathObject()); 3274cadd4f8SNickeau if ($modifiedTime > $dateReplication) { 3284cadd4f8SNickeau return true; 3294cadd4f8SNickeau } 330*04fd306cSNickeau } catch (ExceptionNotFound $e) { 331*04fd306cSNickeau return false; 332*04fd306cSNickeau } 333*04fd306cSNickeau 334*04fd306cSNickeau 335*04fd306cSNickeau $path = $this->markupPath->fetchAnalyticsPath(); 3364cadd4f8SNickeau 337c3437056SNickeau /** 338c3437056SNickeau * When the file does not exist 339c3437056SNickeau */ 340*04fd306cSNickeau $exist = FileSystems::exists($path); 341c3437056SNickeau if (!$exist) { 342c3437056SNickeau return true; 343c3437056SNickeau } 344c3437056SNickeau 345c3437056SNickeau /** 3464cadd4f8SNickeau * When the analytics document is older 347c3437056SNickeau */ 348*04fd306cSNickeau try { 349*04fd306cSNickeau 350*04fd306cSNickeau $modifiedTime = FileSystems::getModifiedTime($path); 351c3437056SNickeau if ($modifiedTime > $dateReplication) { 352c3437056SNickeau return true; 353c3437056SNickeau } 354*04fd306cSNickeau } catch (ExceptionNotFound $e) { 355*04fd306cSNickeau // 356*04fd306cSNickeau } 357c3437056SNickeau 3584cadd4f8SNickeau 359c3437056SNickeau /** 360c3437056SNickeau * When the database version file is higher 361c3437056SNickeau */ 362*04fd306cSNickeau $version = LocalPath::createFromPathString(__DIR__ . "/../db/latest.version"); 363*04fd306cSNickeau try { 364c3437056SNickeau $versionModifiedTime = FileSystems::getModifiedTime($version); 365*04fd306cSNickeau } catch (ExceptionNotFound $e) { 366*04fd306cSNickeau return false; 367*04fd306cSNickeau } 368c3437056SNickeau if ($versionModifiedTime > $dateReplication) { 369c3437056SNickeau return true; 370c3437056SNickeau } 371c3437056SNickeau 372c3437056SNickeau /** 373c3437056SNickeau * When the class date time is higher 374c3437056SNickeau */ 375*04fd306cSNickeau $code = LocalPath::createFromPathString(__DIR__ . "/DatabasePageRow.php"); 376*04fd306cSNickeau try { 377c3437056SNickeau $codeModified = FileSystems::getModifiedTime($code); 378*04fd306cSNickeau } catch (ExceptionNotFound $e) { 379*04fd306cSNickeau throw new ExceptionRuntime("The database file does not exist"); 380*04fd306cSNickeau } 381c3437056SNickeau if ($codeModified > $dateReplication) { 382c3437056SNickeau return true; 383c3437056SNickeau } 384c3437056SNickeau 385c3437056SNickeau return false; 386c3437056SNickeau 387c3437056SNickeau } 388c3437056SNickeau 389c3437056SNickeau public 390c3437056SNickeau function delete() 391c3437056SNickeau { 392c3437056SNickeau 393*04fd306cSNickeau $request = $this->sqlite 394c3437056SNickeau ->createRequest() 395*04fd306cSNickeau ->setQueryParametrized('delete from pages where id = ?', [$this->markupPath->getWikiId()]); 396c3437056SNickeau try { 397c3437056SNickeau $request->execute(); 398*04fd306cSNickeau } catch (ExceptionCompile $e) { 399*04fd306cSNickeau LogUtility::msg("Something went wrong when deleting the page ({$this->markupPath}) from the database"); 400c3437056SNickeau } finally { 401c3437056SNickeau $request->close(); 402c3437056SNickeau } 403c3437056SNickeau $this->buildInitObjectFields(); 404c3437056SNickeau 405c3437056SNickeau } 406c3437056SNickeau 407c3437056SNickeau /** 408*04fd306cSNickeau * @return Json the analytics array or null if not in db 409c3437056SNickeau */ 410c3437056SNickeau public 411*04fd306cSNickeau function getAnalyticsData(): Json 412c3437056SNickeau { 413c3437056SNickeau 414c3437056SNickeau $jsonString = $this->getFromRow(self::ANALYTICS_ATTRIBUTE); 415c3437056SNickeau if ($jsonString === null) { 416*04fd306cSNickeau // we put an empty json to not get any problem with the json database function 417*04fd306cSNickeau // on an empty string / null (for sqlite) 418*04fd306cSNickeau return Json::createEmpty(); 419c3437056SNickeau } 420c3437056SNickeau try { 421c3437056SNickeau return Json::createFromString($jsonString); 422*04fd306cSNickeau } catch (ExceptionCompile $e) { 423*04fd306cSNickeau throw ExceptionRuntimeInternal::withMessageAndError("Error while building back the analytics JSON object. {$e->getMessage()}", $e); 424c3437056SNickeau } 425c3437056SNickeau 426c3437056SNickeau } 427c3437056SNickeau 428c3437056SNickeau /** 429c3437056SNickeau * Return the database row 430c3437056SNickeau * 431c3437056SNickeau * 432*04fd306cSNickeau * @throws ExceptionNotExists - if the row does not exists 433c3437056SNickeau */ 434*04fd306cSNickeau public 435*04fd306cSNickeau function getDatabaseRowFromPage(MarkupPath $markupPath): array 436c3437056SNickeau { 437c3437056SNickeau 438*04fd306cSNickeau $this->setMarkupPath($markupPath); 439c3437056SNickeau 440*04fd306cSNickeau /** 441*04fd306cSNickeau * Generated identifier 442*04fd306cSNickeau */ 443*04fd306cSNickeau try { 444*04fd306cSNickeau $pageId = $markupPath->getPageId(); 445*04fd306cSNickeau return $this->getDatabaseRowFromPageId($pageId); 446*04fd306cSNickeau } catch (ExceptionNotFound $e) { 447*04fd306cSNickeau // no page id 448c3437056SNickeau } 449c3437056SNickeau 450c3437056SNickeau /** 451*04fd306cSNickeau * Named identifier: path 452c3437056SNickeau */ 453*04fd306cSNickeau try { 454*04fd306cSNickeau $path = $markupPath->getPathObject(); 455*04fd306cSNickeau return $this->getDatabaseRowFromPath($path); 456*04fd306cSNickeau } catch (ExceptionNotFound $e) { 457*04fd306cSNickeau // not found 458*04fd306cSNickeau } 459*04fd306cSNickeau 460*04fd306cSNickeau /** 461*04fd306cSNickeau * Named identifier: id (ie path) 462*04fd306cSNickeau */ 463*04fd306cSNickeau try { 464*04fd306cSNickeau $id = $markupPath->getPathObject()->toWikiPath()->getWikiId(); 465c3437056SNickeau return $this->getDatabaseRowFromDokuWikiId($id); 466*04fd306cSNickeau } catch (ExceptionCast|ExceptionNotFound $e) { 467*04fd306cSNickeau } 468*04fd306cSNickeau 469*04fd306cSNickeau /** 470*04fd306cSNickeau * Named identifier: canonical 471*04fd306cSNickeau * (Note that canonical should become a soft link and therefore a path) 472*04fd306cSNickeau */ 473*04fd306cSNickeau try { 474*04fd306cSNickeau $canonical = Canonical::createForPage($markupPath)->getValue(); 475*04fd306cSNickeau return $this->getDatabaseRowFromCanonical($canonical->toAbsoluteId()); 476*04fd306cSNickeau } catch (ExceptionNotFound $e) { 477*04fd306cSNickeau // no canonical 478*04fd306cSNickeau } 479*04fd306cSNickeau 480*04fd306cSNickeau // we send a not exist 481*04fd306cSNickeau throw new ExceptionNotExists("No row could be found"); 482c3437056SNickeau 483c3437056SNickeau 484c3437056SNickeau } 485c3437056SNickeau 486c3437056SNickeau 4874cadd4f8SNickeau /** 4884cadd4f8SNickeau * @return DateTime|null 4894cadd4f8SNickeau */ 490*04fd306cSNickeau public 491*04fd306cSNickeau function getReplicationDate(): ?DateTime 492c3437056SNickeau { 493*04fd306cSNickeau $dateString = $this->getFromRow(ReplicationDate::getPersistentName()); 494c3437056SNickeau if ($dateString === null) { 495c3437056SNickeau return null; 496c3437056SNickeau } 497c3437056SNickeau try { 498c3437056SNickeau return Iso8601Date::createFromString($dateString)->getDateTime(); 499*04fd306cSNickeau } catch (ExceptionCompile $e) { 500c3437056SNickeau LogUtility::msg("Error while reading the replication date in the database. {$e->getMessage()}"); 501c3437056SNickeau return null; 502c3437056SNickeau } 503c3437056SNickeau 504c3437056SNickeau } 505c3437056SNickeau 506c3437056SNickeau /** 507*04fd306cSNickeau * 508*04fd306cSNickeau * @throws ExceptionBadState 509*04fd306cSNickeau * @throws ExceptionSqliteNotAvailable 510c3437056SNickeau */ 511*04fd306cSNickeau public 512*04fd306cSNickeau function replicatePage(): void 513c3437056SNickeau { 514c3437056SNickeau 515*04fd306cSNickeau if (!FileSystems::exists($this->markupPath)) { 516*04fd306cSNickeau throw new ExceptionBadState("You can't replicate the page ($this->markupPath) because it does not exists."); 517c3437056SNickeau } 518c3437056SNickeau 519c3437056SNickeau /** 520c3437056SNickeau * Replication Date 521c3437056SNickeau */ 522*04fd306cSNickeau $replicationDate = ReplicationDate::createFromPage($this->markupPath) 523c3437056SNickeau ->setWriteStore(MetadataDbStore::class) 5244cadd4f8SNickeau ->setValue(new DateTime()); 525c3437056SNickeau 526c3437056SNickeau /** 527*04fd306cSNickeau * Same data as {@link MarkupPath::getMetadataForRendering()} 528c3437056SNickeau */ 529c3437056SNickeau $record = $this->getMetaRecord(); 530c3437056SNickeau $record[$replicationDate::getPersistentName()] = $replicationDate->toStoreValue(); 531*04fd306cSNickeau $this->upsertAttributes($record); 532c3437056SNickeau 533c3437056SNickeau } 534c3437056SNickeau 535c3437056SNickeau 536c3437056SNickeau /** 537*04fd306cSNickeau * 538c3437056SNickeau * 539c3437056SNickeau * Attribute that are scalar / modifiable in the database 540c3437056SNickeau * (not aliases or json data for instance) 541*04fd306cSNickeau * 542*04fd306cSNickeau * @throws ExceptionBadState 543*04fd306cSNickeau * @throws ExceptionSqliteNotAvailable 544c3437056SNickeau */ 545*04fd306cSNickeau public 546*04fd306cSNickeau function replicateMetaAttributes(): void 547c3437056SNickeau { 548c3437056SNickeau 549*04fd306cSNickeau $this->upsertAttributes($this->getMetaRecord()); 550c3437056SNickeau 551c3437056SNickeau } 552c3437056SNickeau 553*04fd306cSNickeau /** 554*04fd306cSNickeau * @throws ExceptionBadState - if the array is empty 555*04fd306cSNickeau */ 556*04fd306cSNickeau public 557*04fd306cSNickeau function upsertAttributes(array $attributes): void 558c3437056SNickeau { 559c3437056SNickeau 560c3437056SNickeau if (empty($attributes)) { 561*04fd306cSNickeau throw new ExceptionBadState("The page database attribute passed should not be empty"); 562c3437056SNickeau } 563c3437056SNickeau 564c3437056SNickeau $values = []; 565c3437056SNickeau $columnClauses = []; 566c3437056SNickeau foreach ($attributes as $key => $value) { 567c3437056SNickeau if (is_array($value)) { 568*04fd306cSNickeau throw new ExceptionRuntime("The attribute ($key) has value that is an array (" . implode(", ", $value) . ")"); 569c3437056SNickeau } 570c3437056SNickeau $columnClauses[] = "$key = ?"; 571c3437056SNickeau $values[$key] = $value; 572c3437056SNickeau } 573c3437056SNickeau 574c3437056SNickeau /** 575c3437056SNickeau * Primary key has moved during the time 576c3437056SNickeau * It should be the UUID but not for older version 577c3437056SNickeau * 578c3437056SNickeau * If the primary key is null, no record was found 579c3437056SNickeau */ 580c3437056SNickeau $rowId = $this->getRowId(); 581c3437056SNickeau if ($rowId !== null) { 582c3437056SNickeau /** 583c3437056SNickeau * We just add the primary key 584c3437056SNickeau * otherwise as this is a associative 585c3437056SNickeau * array, we will miss a value for the update statement 586c3437056SNickeau */ 587c3437056SNickeau $values[] = $rowId; 588c3437056SNickeau 589c3437056SNickeau $updateStatement = "update PAGES SET " . implode(", ", $columnClauses) . " where ROWID = ?"; 590c3437056SNickeau $request = $this->sqlite 591c3437056SNickeau ->createRequest() 592c3437056SNickeau ->setQueryParametrized($updateStatement, $values); 593c3437056SNickeau $countChanges = 0; 594c3437056SNickeau try { 595c3437056SNickeau $countChanges = $request 596c3437056SNickeau ->execute() 597c3437056SNickeau ->getChangeCount(); 598*04fd306cSNickeau } catch (ExceptionCompile $e) { 599*04fd306cSNickeau throw new ExceptionBadState("There was a problem during the page attribute updates. : {$e->getMessage()}"); 600c3437056SNickeau } finally { 601c3437056SNickeau $request->close(); 602c3437056SNickeau } 603c3437056SNickeau if ($countChanges !== 1) { 604*04fd306cSNickeau // internal error 605*04fd306cSNickeau LogUtility::error("The database replication has not updated exactly 1 record but ($countChanges) record", \action_plugin_combo_indexer::CANONICAL); 606c3437056SNickeau } 607c3437056SNickeau 608c3437056SNickeau } else { 609c3437056SNickeau 610c3437056SNickeau /** 611c3437056SNickeau * Creation 612c3437056SNickeau */ 613*04fd306cSNickeau if ($this->markupPath === null) { 614*04fd306cSNickeau throw new ExceptionBadState("The page should be defined to create a page database row"); 615c3437056SNickeau } 616*04fd306cSNickeau 617*04fd306cSNickeau /** 618*04fd306cSNickeau * If the user copy a frontmatter with the same page id abbr, we got a problem 619*04fd306cSNickeau */ 620*04fd306cSNickeau $pageIdAbbr = $values[PageId::PAGE_ID_ABBR_ATTRIBUTE]; 621*04fd306cSNickeau if ($pageIdAbbr == null) { 622*04fd306cSNickeau $pageId = $values[PageId::getPersistentName()]; 623*04fd306cSNickeau if ($pageId === null) { 624*04fd306cSNickeau throw new ExceptionBadState("You can't insert a page in the database without a page id"); 625*04fd306cSNickeau } 626*04fd306cSNickeau $pageIdAbbr = PageId::getAbbreviated($pageId); 627*04fd306cSNickeau $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $pageIdAbbr; 628*04fd306cSNickeau } 629*04fd306cSNickeau 630*04fd306cSNickeau $databasePage = DatabasePageRow::createFromPageIdAbbr($pageIdAbbr); 631*04fd306cSNickeau if ($databasePage->exists()) { 632*04fd306cSNickeau $duplicatePage = $databasePage->getMarkupPath(); 633*04fd306cSNickeau if ($duplicatePage->getPathObject()->toUriString() === $this->markupPath->toUriString()) { 634*04fd306cSNickeau $message = "The page ($this->markupPath) is already in the database with the uid ($pageIdAbbr)."; 635*04fd306cSNickeau } else { 636*04fd306cSNickeau $message = "The page ($this->markupPath) cannot be replicated to the database because it has the same page id abbreviation ($pageIdAbbr) than the page ($duplicatePage)"; 637*04fd306cSNickeau } 638*04fd306cSNickeau throw new ExceptionBadState($message); 639*04fd306cSNickeau } 640*04fd306cSNickeau 641*04fd306cSNickeau $values[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] = $this->markupPath->getPathObject()->getWikiId(); 642*04fd306cSNickeau $values[PagePath::PROPERTY_NAME] = $this->markupPath->getPathObject()->toAbsolutePath()->toAbsoluteId(); 643c3437056SNickeau /** 644c3437056SNickeau * Default implements the auto-canonical feature 645c3437056SNickeau */ 646*04fd306cSNickeau try { 647*04fd306cSNickeau $values[Canonical::PROPERTY_NAME] = $this->markupPath->getCanonicalOrDefault(); 648*04fd306cSNickeau } catch (ExceptionNotFound $e) { 649*04fd306cSNickeau $values[Canonical::PROPERTY_NAME] = null; 650*04fd306cSNickeau } 651c3437056SNickeau 652c3437056SNickeau /** 653c3437056SNickeau * Analytics 654c3437056SNickeau */ 655c3437056SNickeau if (!isset($values[self::ANALYTICS_ATTRIBUTE])) { 656c3437056SNickeau // otherwise we get an empty string 657c3437056SNickeau // and a json function will not work 658c3437056SNickeau $values[self::ANALYTICS_ATTRIBUTE] = Json::createEmpty()->toPrettyJsonString(); 659c3437056SNickeau } 660c3437056SNickeau 661c3437056SNickeau /** 662c3437056SNickeau * Page Id / Abbr are mandatory for url redirection 663c3437056SNickeau */ 664c3437056SNickeau $this->addPageIdAttributeIfNeeded($values); 665c3437056SNickeau 666c3437056SNickeau $request = $this->sqlite 667c3437056SNickeau ->createRequest() 668c3437056SNickeau ->setTableRow('PAGES', $values); 669c3437056SNickeau try { 670c3437056SNickeau /** 671c3437056SNickeau * rowid is used in {@link DatabasePageRow::exists()} 672c3437056SNickeau * to check if the page exists in the database 673c3437056SNickeau * We update it 674c3437056SNickeau */ 675c3437056SNickeau $this->row[self::ROWID] = $request 676c3437056SNickeau ->execute() 677c3437056SNickeau ->getInsertId(); 678c3437056SNickeau $this->row = array_merge($values, $this->row); 679*04fd306cSNickeau } catch (ExceptionCompile $e) { 680*04fd306cSNickeau throw new ExceptionBadState("There was a problem during the updateAttributes insert. : {$e->getMessage()}"); 681c3437056SNickeau } finally { 682c3437056SNickeau $request->close(); 683c3437056SNickeau } 684c3437056SNickeau 685c3437056SNickeau } 686c3437056SNickeau 687c3437056SNickeau } 688c3437056SNickeau 689c3437056SNickeau public 690c3437056SNickeau function getDescription() 691c3437056SNickeau { 692c3437056SNickeau return $this->getFromRow(PageDescription::DESCRIPTION_PROPERTY); 693c3437056SNickeau } 694c3437056SNickeau 695c3437056SNickeau 696c3437056SNickeau public 697c3437056SNickeau function getPageName() 698c3437056SNickeau { 699c3437056SNickeau return $this->getFromRow(ResourceName::PROPERTY_NAME); 700c3437056SNickeau } 701c3437056SNickeau 702c3437056SNickeau public 703c3437056SNickeau function exists(): bool 704c3437056SNickeau { 705c3437056SNickeau return $this->getFromRow(self::ROWID) !== null; 706c3437056SNickeau } 707c3437056SNickeau 708c3437056SNickeau /** 709c3437056SNickeau * Called when a page is moved 710c3437056SNickeau * @param $targetId 711c3437056SNickeau */ 712c3437056SNickeau public 713c3437056SNickeau function updatePathAndDokuwikiId($targetId) 714c3437056SNickeau { 715c3437056SNickeau if (!$this->exists()) { 716*04fd306cSNickeau LogUtility::error("The `database` page ($this) does not exist and cannot be moved to ($targetId)"); 717c3437056SNickeau } 718c3437056SNickeau 719c3437056SNickeau $path = $targetId; 720*04fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 721c3437056SNickeau $attributes = [ 722c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE => $targetId, 723c3437056SNickeau PagePath::PROPERTY_NAME => $path 724c3437056SNickeau ]; 725c3437056SNickeau 726c3437056SNickeau $this->upsertAttributes($attributes); 727c3437056SNickeau 728c3437056SNickeau } 729c3437056SNickeau 730c3437056SNickeau public 731c3437056SNickeau function __toString() 732c3437056SNickeau { 733*04fd306cSNickeau return $this->markupPath->__toString(); 734c3437056SNickeau } 735c3437056SNickeau 736c3437056SNickeau 737c3437056SNickeau /** 738c3437056SNickeau * Redirect are now added during a move 739c3437056SNickeau * Not when a duplicate is found. 740c3437056SNickeau * With the advent of the page id, it should never occurs anymore 741*04fd306cSNickeau * @param MarkupPath $page 742c3437056SNickeau * @deprecated 2012-10-28 743c3437056SNickeau */ 744c3437056SNickeau private 745*04fd306cSNickeau function deleteIfExistsAndAddRedirectAlias(MarkupPath $page): void 746c3437056SNickeau { 747c3437056SNickeau 748*04fd306cSNickeau if ($this->markupPath != null) { 749c3437056SNickeau $page->getDatabasePage()->deleteIfExist(); 750c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($page); 751c3437056SNickeau } 752c3437056SNickeau 753c3437056SNickeau } 754c3437056SNickeau 755c3437056SNickeau public 756c3437056SNickeau function getCanonical() 757c3437056SNickeau { 758c3437056SNickeau return $this->getFromRow(Canonical::PROPERTY_NAME); 759c3437056SNickeau } 760c3437056SNickeau 761c3437056SNickeau /** 762c3437056SNickeau * Set the field to their values 763c3437056SNickeau * @param $row 764c3437056SNickeau */ 765c3437056SNickeau public 766c3437056SNickeau function setRow($row) 767c3437056SNickeau { 768c3437056SNickeau if ($row === null) { 769c3437056SNickeau LogUtility::msg("A row should not be null"); 770c3437056SNickeau return; 771c3437056SNickeau } 772c3437056SNickeau if (!is_array($row)) { 773c3437056SNickeau LogUtility::msg("A row should be an array"); 774c3437056SNickeau return; 775c3437056SNickeau } 776c3437056SNickeau 777c3437056SNickeau /** 778c3437056SNickeau * All get function lookup the row 779c3437056SNickeau */ 780c3437056SNickeau $this->row = $row; 781c3437056SNickeau 782c3437056SNickeau 783c3437056SNickeau } 784c3437056SNickeau 785c3437056SNickeau private 786c3437056SNickeau function buildInitObjectFields() 787c3437056SNickeau { 788c3437056SNickeau $this->row = null; 789c3437056SNickeau 790c3437056SNickeau } 791c3437056SNickeau 792c3437056SNickeau public 793c3437056SNickeau function rebuild(): DatabasePageRow 794c3437056SNickeau { 795c3437056SNickeau 796*04fd306cSNickeau if ($this->markupPath != null) { 797*04fd306cSNickeau $this->markupPath->rebuild(); 798*04fd306cSNickeau try { 799*04fd306cSNickeau $row = $this->getDatabaseRowFromPage($this->markupPath); 800c3437056SNickeau $this->setRow($row); 801*04fd306cSNickeau } catch (ExceptionNotExists $e) { 802*04fd306cSNickeau // ok 803c3437056SNickeau } 804c3437056SNickeau } 805c3437056SNickeau return $this; 806c3437056SNickeau 807c3437056SNickeau } 808c3437056SNickeau 809c3437056SNickeau /** 810c3437056SNickeau * @return array - an array of the fix page metadata (ie not derived) 811c3437056SNickeau * Therefore quick to insert/update 812c3437056SNickeau * 813c3437056SNickeau */ 814c3437056SNickeau private 815c3437056SNickeau function getMetaRecord(): array 816c3437056SNickeau { 817*04fd306cSNickeau $sourceStore = MetadataDokuWikiStore::getOrCreateFromResource($this->markupPath); 818*04fd306cSNickeau $targetStore = MetadataDbStore::getOrCreateFromResource($this->markupPath); 819c3437056SNickeau 820c3437056SNickeau $record = array( 821c3437056SNickeau Canonical::PROPERTY_NAME, 822c3437056SNickeau PagePath::PROPERTY_NAME, 823c3437056SNickeau ResourceName::PROPERTY_NAME, 824c3437056SNickeau PageTitle::TITLE, 825c3437056SNickeau PageH1::PROPERTY_NAME, 826c3437056SNickeau PageDescription::PROPERTY_NAME, 827*04fd306cSNickeau CreationDate::PROPERTY_NAME, 828c3437056SNickeau ModificationDate::PROPERTY_NAME, 829c3437056SNickeau PagePublicationDate::PROPERTY_NAME, 830c3437056SNickeau StartDate::PROPERTY_NAME, 831c3437056SNickeau EndDate::PROPERTY_NAME, 832c3437056SNickeau Region::PROPERTY_NAME, 833c3437056SNickeau Lang::PROPERTY_NAME, 834c3437056SNickeau PageType::PROPERTY_NAME, 835c3437056SNickeau DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, 836*04fd306cSNickeau PageLevel::PROPERTY_NAME 837c3437056SNickeau ); 838c3437056SNickeau $metaRecord = []; 839c3437056SNickeau foreach ($record as $name) { 840*04fd306cSNickeau try { 841*04fd306cSNickeau $metadata = Meta\Api\MetadataSystem::getForName($name); 842*04fd306cSNickeau } catch (ExceptionNotFound $e) { 843*04fd306cSNickeau LogUtility::internalError("The metadata ($name) is unknown", self::CANONICAL); 844*04fd306cSNickeau continue; 845c3437056SNickeau } 846c3437056SNickeau $metaRecord[$name] = $metadata 847*04fd306cSNickeau ->setResource($this->markupPath) 848c3437056SNickeau ->setReadStore($sourceStore) 849c3437056SNickeau ->buildFromReadStore() 850c3437056SNickeau ->setWriteStore($targetStore) 851c3437056SNickeau ->toStoreValueOrDefault(); // used by the template, the value is or default 852*04fd306cSNickeau 853c3437056SNickeau } 854c3437056SNickeau 855*04fd306cSNickeau try { 856c3437056SNickeau $this->addPageIdMeta($metaRecord); 857*04fd306cSNickeau } catch (ExceptionNotExists $e) { 858*04fd306cSNickeau // no page id for non-existent page ok 859c3437056SNickeau } 860*04fd306cSNickeau 861*04fd306cSNickeau // Is index 862*04fd306cSNickeau $metaRecord[self::IS_INDEX_COLUMN] = ($this->markupPath->isIndexPage() === true ? 1 : 0); 863*04fd306cSNickeau 864c3437056SNickeau return $metaRecord; 865c3437056SNickeau } 866c3437056SNickeau 867c3437056SNickeau public 868c3437056SNickeau function deleteIfExist(): DatabasePageRow 869c3437056SNickeau { 870c3437056SNickeau if ($this->exists()) { 871c3437056SNickeau $this->delete(); 872c3437056SNickeau } 873c3437056SNickeau return $this; 874c3437056SNickeau } 875c3437056SNickeau 876c3437056SNickeau public 877c3437056SNickeau function getRowId() 878c3437056SNickeau { 879c3437056SNickeau return $this->getFromRow(self::ROWID); 880c3437056SNickeau } 881c3437056SNickeau 882*04fd306cSNickeau /** 883*04fd306cSNickeau * @throws ExceptionNotFound 884*04fd306cSNickeau */ 885c3437056SNickeau private 886*04fd306cSNickeau function getDatabaseRowFromPageId(string $pageIdValue) 887c3437056SNickeau { 888c3437056SNickeau 889c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 890c3437056SNickeau $query = $this->getParametrizedLookupQuery($pageIdAttribute); 891c3437056SNickeau $request = Sqlite::createOrGetSqlite() 892c3437056SNickeau ->createRequest() 893*04fd306cSNickeau ->setQueryParametrized($query, [$pageIdValue]); 894c3437056SNickeau $rows = []; 895c3437056SNickeau try { 896c3437056SNickeau $rows = $request 897c3437056SNickeau ->execute() 898c3437056SNickeau ->getRows(); 899*04fd306cSNickeau } catch (ExceptionCompile $e) { 900*04fd306cSNickeau throw new ExceptionRuntimeInternal("Error while retrieving the object by id", self::CANONICAL, 1, $e); 901c3437056SNickeau } finally { 902c3437056SNickeau $request->close(); 903c3437056SNickeau } 904c3437056SNickeau 905c3437056SNickeau switch (sizeof($rows)) { 906c3437056SNickeau case 0: 907*04fd306cSNickeau throw new ExceptionNotFound("No object by page id"); 908c3437056SNickeau case 1: 909c3437056SNickeau /** 910c3437056SNickeau * Page Id Collision detection 911c3437056SNickeau */ 912*04fd306cSNickeau $rowId = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 913*04fd306cSNickeau $this->checkCollision($rowId, $pageIdAttribute, $pageIdValue); 914c3437056SNickeau return $rows[0]; 915c3437056SNickeau default: 916c3437056SNickeau $existingPages = implode(", ", $rows); 917*04fd306cSNickeau $message = "The pages ($existingPages) have all the same page id ($pageIdValue)"; 918*04fd306cSNickeau throw new ExceptionRuntimeInternal($message, self::CANONICAL); 919c3437056SNickeau } 920c3437056SNickeau 921c3437056SNickeau } 922c3437056SNickeau 923c3437056SNickeau 924c3437056SNickeau private 925*04fd306cSNickeau function getParametrizedLookupQuery(string $attributeName): string 926c3437056SNickeau { 927c3437056SNickeau $select = Sqlite::createSelectFromTableAndColumns("pages", self::PAGE_BUILD_ATTRIBUTES); 928*04fd306cSNickeau return "$select where $attributeName = ?"; 929c3437056SNickeau } 930c3437056SNickeau 931c3437056SNickeau 932*04fd306cSNickeau public 933*04fd306cSNickeau function setMarkupPath(MarkupPath $page) 934c3437056SNickeau { 935*04fd306cSNickeau $this->markupPath = $page; 936c3437056SNickeau return $this; 937c3437056SNickeau } 938c3437056SNickeau 939*04fd306cSNickeau /** 940*04fd306cSNickeau * @throws ExceptionNotFound 941*04fd306cSNickeau */ 942c3437056SNickeau private 943*04fd306cSNickeau function getDatabaseRowFromCanonical($canonicalValue) 944c3437056SNickeau { 945*04fd306cSNickeau $canoncialName = Canonical::PROPERTY_NAME; 946*04fd306cSNickeau $query = $this->getParametrizedLookupQuery($canoncialName); 947c3437056SNickeau $request = $this->sqlite 948c3437056SNickeau ->createRequest() 949*04fd306cSNickeau ->setQueryParametrized($query, [$canonicalValue]); 950c3437056SNickeau $rows = []; 951c3437056SNickeau try { 952c3437056SNickeau $rows = $request 953c3437056SNickeau ->execute() 954c3437056SNickeau ->getRows(); 955*04fd306cSNickeau } catch (ExceptionCompile $e) { 956*04fd306cSNickeau throw new ExceptionRuntime("An exception has occurred with the page search from CANONICAL. " . $e->getMessage()); 957c3437056SNickeau } finally { 958c3437056SNickeau $request->close(); 959c3437056SNickeau } 960c3437056SNickeau 961c3437056SNickeau switch (sizeof($rows)) { 962c3437056SNickeau case 0: 963*04fd306cSNickeau throw new ExceptionNotFound("No canonical row was found"); 964c3437056SNickeau case 1: 965c3437056SNickeau $id = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 966*04fd306cSNickeau $this->checkCollision($id, $canoncialName, $canonicalValue); 967c3437056SNickeau return $rows[0]; 968c3437056SNickeau default: 969c3437056SNickeau $existingPages = []; 970c3437056SNickeau foreach ($rows as $row) { 971c3437056SNickeau $id = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 972*04fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($id); 973c3437056SNickeau if (!$duplicatePage->exists()) { 974c3437056SNickeau 975c3437056SNickeau $this->deleteIfExistsAndAddRedirectAlias($duplicatePage); 976c3437056SNickeau 977c3437056SNickeau } else { 978c3437056SNickeau 979c3437056SNickeau /** 980c3437056SNickeau * Check if the error may come from the auto-canonical 981c3437056SNickeau * (Never ever save generated data) 982c3437056SNickeau */ 983*04fd306cSNickeau $canonicalLastNamesCount = SiteConfig::getConfValue(Canonical::CONF_CANONICAL_LAST_NAMES_COUNT, 0); 984c3437056SNickeau if ($canonicalLastNamesCount > 0) { 985*04fd306cSNickeau $this->markupPath->unsetMetadata($canoncialName); 986*04fd306cSNickeau $duplicatePage->unsetMetadata($canoncialName); 987c3437056SNickeau } 988c3437056SNickeau 989c3437056SNickeau $existingPages[] = $row; 990c3437056SNickeau } 991c3437056SNickeau } 992*04fd306cSNickeau if (sizeof($existingPages) > 1) { 993c3437056SNickeau $existingPages = implode(", ", $existingPages); 994*04fd306cSNickeau $message = "The existing pages ($existingPages) have all the same canonical ($canonicalValue), return the first one"; 995*04fd306cSNickeau LogUtility::error($message, self::CANONICAL); 996c3437056SNickeau } 997*04fd306cSNickeau return $existingPages[0]; 998c3437056SNickeau } 999c3437056SNickeau } 1000c3437056SNickeau 1001*04fd306cSNickeau /** 1002*04fd306cSNickeau * @throws ExceptionNotFound 1003*04fd306cSNickeau */ 1004c3437056SNickeau private 1005c3437056SNickeau function getDatabaseRowFromPath(string $path): ?array 1006c3437056SNickeau { 1007*04fd306cSNickeau WikiPath::addRootSeparatorIfNotPresent($path); 1008c3437056SNickeau return $this->getDatabaseRowFromAttribute(PagePath::PROPERTY_NAME, $path); 1009c3437056SNickeau } 1010c3437056SNickeau 1011*04fd306cSNickeau /** 1012*04fd306cSNickeau * @throws ExceptionNotFound 1013*04fd306cSNickeau */ 1014c3437056SNickeau private 1015*04fd306cSNickeau function getDatabaseRowFromDokuWikiId(string $id): array 1016c3437056SNickeau { 1017c3437056SNickeau return $this->getDatabaseRowFromAttribute(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $id); 1018c3437056SNickeau } 1019c3437056SNickeau 1020*04fd306cSNickeau /** 1021*04fd306cSNickeau * @throws ExceptionNotFound 1022*04fd306cSNickeau */ 1023c3437056SNickeau public 1024c3437056SNickeau function getDatabaseRowFromAttribute(string $attribute, string $value) 1025c3437056SNickeau { 1026c3437056SNickeau $query = $this->getParametrizedLookupQuery($attribute); 1027c3437056SNickeau $request = $this->sqlite 1028c3437056SNickeau ->createRequest() 1029c3437056SNickeau ->setQueryParametrized($query, [$value]); 1030c3437056SNickeau $rows = []; 1031c3437056SNickeau try { 1032c3437056SNickeau $rows = $request 1033c3437056SNickeau ->execute() 1034c3437056SNickeau ->getRows(); 1035*04fd306cSNickeau } catch (ExceptionCompile $e) { 1036*04fd306cSNickeau $message = "Internal Error: An exception has occurred with the page search from a PATH: " . $e->getMessage(); 1037*04fd306cSNickeau LogUtility::log2file($message); 1038*04fd306cSNickeau throw new ExceptionNotFound($message); 1039c3437056SNickeau } finally { 1040c3437056SNickeau $request->close(); 1041c3437056SNickeau } 1042c3437056SNickeau 1043c3437056SNickeau switch (sizeof($rows)) { 1044c3437056SNickeau case 0: 1045*04fd306cSNickeau throw new ExceptionNotFound("No database row found for the page"); 1046c3437056SNickeau case 1: 1047c3437056SNickeau $value = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 1048*04fd306cSNickeau if ($this->markupPath != null && $value !== $this->markupPath->getWikiId()) { 1049*04fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($value); 1050c3437056SNickeau if (!$duplicatePage->exists()) { 1051c3437056SNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 1052c3437056SNickeau } else { 1053*04fd306cSNickeau LogUtility::msg("The page ($this->markupPath) and the page ($value) have the same $attribute ($value)", LogUtility::LVL_MSG_ERROR); 1054c3437056SNickeau } 1055c3437056SNickeau } 1056c3437056SNickeau return $rows[0]; 1057c3437056SNickeau default: 1058c3437056SNickeau $existingPages = []; 1059c3437056SNickeau foreach ($rows as $row) { 1060c3437056SNickeau $value = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 1061*04fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($value); 1062c3437056SNickeau if (!$duplicatePage->exists()) { 1063c3437056SNickeau 1064c3437056SNickeau $this->deleteIfExistsAndAddRedirectAlias($duplicatePage); 1065c3437056SNickeau 1066c3437056SNickeau } else { 1067c3437056SNickeau $existingPages[] = $row; 1068c3437056SNickeau } 1069c3437056SNickeau } 1070c3437056SNickeau if (sizeof($existingPages) === 1) { 1071c3437056SNickeau return $existingPages[0]; 1072c3437056SNickeau } else { 1073*04fd306cSNickeau $existingPageIds = array_map( 1074*04fd306cSNickeau function ($row) { 1075*04fd306cSNickeau return $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]; 1076*04fd306cSNickeau }, 1077*04fd306cSNickeau $existingPages); 1078*04fd306cSNickeau $existingPages = implode(", ", $existingPageIds); 1079*04fd306cSNickeau throw new ExceptionNotFound("The existing pages ($existingPages) have all the same attribute $attribute with the value ($value)", LogUtility::LVL_MSG_ERROR); 1080c3437056SNickeau } 1081c3437056SNickeau } 1082c3437056SNickeau } 1083c3437056SNickeau 1084c3437056SNickeau public 1085*04fd306cSNickeau function getMarkupPath(): ?MarkupPath 1086c3437056SNickeau { 1087c3437056SNickeau if ( 1088*04fd306cSNickeau $this->markupPath === null 1089c3437056SNickeau && $this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] !== null 1090c3437056SNickeau ) { 1091*04fd306cSNickeau $this->markupPath = MarkupPath::createMarkupFromId($this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]); 1092c3437056SNickeau } 1093*04fd306cSNickeau return $this->markupPath; 1094c3437056SNickeau } 1095c3437056SNickeau 1096c3437056SNickeau private 1097c3437056SNickeau function getDatabaseRowFromAlias($alias): ?array 1098c3437056SNickeau { 1099c3437056SNickeau 1100c3437056SNickeau $pageIdAttribute = PageId::PROPERTY_NAME; 1101c3437056SNickeau $buildFields = self::PAGE_BUILD_ATTRIBUTES; 1102c3437056SNickeau $fields = array_reduce($buildFields, function ($carry, $element) { 1103c3437056SNickeau if ($carry !== null) { 1104c3437056SNickeau return "$carry, p.{$element}"; 1105c3437056SNickeau } else { 1106c3437056SNickeau return "p.{$element}"; 1107c3437056SNickeau } 1108c3437056SNickeau }, null); 1109*04fd306cSNickeau /** @noinspection SqlResolve */ 1110c3437056SNickeau $query = "select {$fields} from PAGES p, PAGE_ALIASES pa where p.{$pageIdAttribute} = pa.{$pageIdAttribute} and pa.PATH = ? "; 1111c3437056SNickeau $request = $this->sqlite 1112c3437056SNickeau ->createRequest() 1113c3437056SNickeau ->setQueryParametrized($query, [$alias]); 1114c3437056SNickeau $rows = []; 1115c3437056SNickeau try { 1116c3437056SNickeau $rows = $request 1117c3437056SNickeau ->execute() 1118c3437056SNickeau ->getRows(); 1119*04fd306cSNickeau } catch (ExceptionCompile $e) { 1120c3437056SNickeau LogUtility::msg("An exception has occurred with the alias selection query. {$e->getMessage()}"); 1121c3437056SNickeau return null; 1122c3437056SNickeau } finally { 1123c3437056SNickeau $request->close(); 1124c3437056SNickeau } 1125c3437056SNickeau switch (sizeof($rows)) { 1126c3437056SNickeau case 0: 1127c3437056SNickeau return null; 1128c3437056SNickeau case 1: 1129c3437056SNickeau return $rows[0]; 1130c3437056SNickeau default: 1131c3437056SNickeau $id = $rows[0]['ID']; 1132c3437056SNickeau $pages = implode(",", 1133c3437056SNickeau array_map( 1134c3437056SNickeau function ($row) { 1135c3437056SNickeau return $row['ID']; 1136c3437056SNickeau }, 1137c3437056SNickeau $rows 1138c3437056SNickeau ) 1139c3437056SNickeau ); 1140c3437056SNickeau 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); 1141c3437056SNickeau return $rows[0]; 1142c3437056SNickeau } 1143c3437056SNickeau } 1144c3437056SNickeau 1145c3437056SNickeau 1146c3437056SNickeau /** 1147c3437056SNickeau * Utility function 1148*04fd306cSNickeau * @param MarkupPath $pageAlias 1149c3437056SNickeau */ 1150c3437056SNickeau private 1151*04fd306cSNickeau function addRedirectAliasWhileBuildingRow(MarkupPath $pageAlias) 1152c3437056SNickeau { 1153c3437056SNickeau 1154*04fd306cSNickeau $aliasPath = $pageAlias->getPathObject()->toAbsoluteId(); 1155c3437056SNickeau try { 1156*04fd306cSNickeau Aliases::createForPage($this->markupPath) 1157c3437056SNickeau ->addAlias($aliasPath) 1158c3437056SNickeau ->sendToWriteStore(); 1159*04fd306cSNickeau } catch (ExceptionCompile $e) { 1160c3437056SNickeau // we don't throw while getting 1161*04fd306cSNickeau LogUtility::msg("Unable to add the alias ($aliasPath) for the page ($this->markupPath)"); 1162c3437056SNickeau } 1163c3437056SNickeau 1164c3437056SNickeau } 1165c3437056SNickeau 1166c3437056SNickeau private 1167c3437056SNickeau function addPageIdAttributeIfNeeded(array &$values) 1168c3437056SNickeau { 1169c3437056SNickeau if (!isset($values[PageId::getPersistentName()])) { 1170*04fd306cSNickeau $values[PageId::getPersistentName()] = $this->markupPath->getPageId(); 1171c3437056SNickeau } 1172c3437056SNickeau if (!isset($values[PageId::PAGE_ID_ABBR_ATTRIBUTE])) { 1173*04fd306cSNickeau $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $this->markupPath->getPageIdAbbr(); 1174c3437056SNickeau } 1175c3437056SNickeau } 1176c3437056SNickeau 1177c3437056SNickeau public 1178c3437056SNickeau function getFromRow(string $attribute) 1179c3437056SNickeau { 1180c3437056SNickeau if ($this->row === null) { 1181c3437056SNickeau return null; 1182c3437056SNickeau } 1183c3437056SNickeau 1184c3437056SNickeau if (!array_key_exists($attribute, $this->row)) { 1185c3437056SNickeau /** 1186c3437056SNickeau * An attribute should be added to {@link DatabasePageRow::PAGE_BUILD_ATTRIBUTES} 1187c3437056SNickeau * or in the table 1188c3437056SNickeau */ 1189*04fd306cSNickeau throw new ExceptionRuntime("The metadata ($attribute) was not found in the returned database row.", $this->getCanonical()); 1190c3437056SNickeau } 1191c3437056SNickeau 1192c3437056SNickeau $value = $this->row[$attribute]; 1193c3437056SNickeau 1194c3437056SNickeau if ($value !== null) { 1195c3437056SNickeau return $value; 1196c3437056SNickeau } 1197c3437056SNickeau 1198c3437056SNickeau // don't know why but the sqlite plugin returns them uppercase 1199c3437056SNickeau // rowid is returned lowercase from the sqlite plugin 1200c3437056SNickeau $upperAttribute = strtoupper($attribute); 1201c3437056SNickeau return $this->row[$upperAttribute]; 1202c3437056SNickeau 1203c3437056SNickeau } 1204c3437056SNickeau 1205c3437056SNickeau 12064cadd4f8SNickeau /** 1207*04fd306cSNickeau * @throws ExceptionCompile 12084cadd4f8SNickeau */ 1209*04fd306cSNickeau public 1210*04fd306cSNickeau function replicateAnalytics() 1211c3437056SNickeau { 1212c3437056SNickeau 1213c3437056SNickeau try { 1214*04fd306cSNickeau $fetchPath = $this->markupPath->fetchAnalyticsPath(); 1215*04fd306cSNickeau $analyticsJson = Json::createFromPath($fetchPath); 1216*04fd306cSNickeau } catch (ExceptionCompile $e) { 1217*04fd306cSNickeau if (PluginUtility::isDevOrTest()) { 1218*04fd306cSNickeau throw $e; 1219*04fd306cSNickeau } 1220*04fd306cSNickeau throw new ExceptionCompile("Unable to get the analytics document", self::CANONICAL, 0, $e); 1221c3437056SNickeau } 1222c3437056SNickeau 1223c3437056SNickeau /** 1224c3437056SNickeau * Replication Date 1225c3437056SNickeau */ 1226*04fd306cSNickeau $replicationDateMeta = ReplicationDate::createFromPage($this->markupPath) 1227c3437056SNickeau ->setWriteStore(MetadataDbStore::class) 12284cadd4f8SNickeau ->setValue(new DateTime()); 1229c3437056SNickeau 1230c3437056SNickeau /** 1231c3437056SNickeau * Analytics 1232c3437056SNickeau */ 1233c3437056SNickeau $analyticsJsonAsString = $analyticsJson->toPrettyJsonString(); 1234c3437056SNickeau $analyticsJsonAsArray = $analyticsJson->toArray(); 1235c3437056SNickeau 1236c3437056SNickeau /** 1237c3437056SNickeau * Record 1238c3437056SNickeau */ 1239c3437056SNickeau $record[self::ANALYTICS_ATTRIBUTE] = $analyticsJsonAsString; 1240*04fd306cSNickeau $record['IS_LOW_QUALITY'] = ($this->markupPath->isLowQualityPage() === true ? 1 : 0); 1241*04fd306cSNickeau $record['WORD_COUNT'] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][renderer_plugin_combo_analytics::WORD_COUNT]; 1242*04fd306cSNickeau $record[BacklinkCount::getPersistentName()] = $analyticsJsonAsArray[renderer_plugin_combo_analytics::STATISTICS][BacklinkCount::getPersistentName()]; 1243c3437056SNickeau $record[$replicationDateMeta::getPersistentName()] = $replicationDateMeta->toStoreValue(); 1244c3437056SNickeau $this->upsertAttributes($record); 1245c3437056SNickeau } 1246c3437056SNickeau 1247*04fd306cSNickeau private 1248*04fd306cSNickeau function checkCollision($wikiIdInDatabase, $attribute, $value) 1249*04fd306cSNickeau { 1250*04fd306cSNickeau if ($this->markupPath === null) { 1251*04fd306cSNickeau return; 1252*04fd306cSNickeau } 1253*04fd306cSNickeau try { 1254*04fd306cSNickeau $markupWikiId = $this->markupPath->toWikiPath()->getWikiId(); 1255*04fd306cSNickeau } catch (ExceptionCast $e) { 1256*04fd306cSNickeau return; 1257*04fd306cSNickeau } 1258*04fd306cSNickeau if ($wikiIdInDatabase !== $markupWikiId) { 1259*04fd306cSNickeau $duplicatePage = MarkupPath::createMarkupFromId($wikiIdInDatabase); 1260*04fd306cSNickeau if (!FileSystems::exists($duplicatePage)) { 1261*04fd306cSNickeau // Move 1262*04fd306cSNickeau LogUtility::info("The non-existing duplicate page ($wikiIdInDatabase) has been added as redirect alias for the page ($this->markupPath)", self::CANONICAL); 1263*04fd306cSNickeau $this->addRedirectAliasWhileBuildingRow($duplicatePage); 1264*04fd306cSNickeau } else { 1265*04fd306cSNickeau // This can happens if two page were created not on the same website 1266*04fd306cSNickeau // of if the sqlite database was deleted and rebuilt. 1267*04fd306cSNickeau // The chance is really, really low 1268*04fd306cSNickeau $errorMessage = "The page ($this->markupPath) and the page ($wikiIdInDatabase) have the same $attribute value ($value)"; 1269*04fd306cSNickeau throw new ExceptionRuntimeInternal($errorMessage, self::CANONICAL); 1270*04fd306cSNickeau // What to do ? 1271*04fd306cSNickeau // The database does not allow two page id with the same value 1272*04fd306cSNickeau // If it happens, ugh, ugh, ..., a replication process between website may be. 1273*04fd306cSNickeau } 1274*04fd306cSNickeau } 1275*04fd306cSNickeau } 1276*04fd306cSNickeau 1277c3437056SNickeau 1278c3437056SNickeau} 1279