xref: /plugin/combo/ComboStrap/DatabasePageRow.php (revision 4cadd4f8c541149bdda95f080e38a6d4e3a640ca)
1c3437056SNickeau<?php
2c3437056SNickeau
3c3437056SNickeau
4c3437056SNickeaunamespace ComboStrap;
5c3437056SNickeau
6*4cadd4f8SNickeauuse DateTime;
7c3437056SNickeauuse ModificationDate;
8c3437056SNickeau
9c3437056SNickeau/**
10c3437056SNickeau * The class that manage the replication
11c3437056SNickeau * Class Replicate
12c3437056SNickeau * @package ComboStrap
13c3437056SNickeau *
14c3437056SNickeau * The database can also be seen as a {@link MetadataStore}
15c3437056SNickeau * and an {@link Index}
16c3437056SNickeau */
17c3437056SNickeauclass DatabasePageRow
18c3437056SNickeau{
19c3437056SNickeau
20c3437056SNickeau
21c3437056SNickeau    /**
22c3437056SNickeau     * The list of attributes that are set
23c3437056SNickeau     * at build time
24c3437056SNickeau     * used in the build functions such as {@link DatabasePageRow::getDatabaseRowFromPage()}
25c3437056SNickeau     * to build the sql
26c3437056SNickeau     */
27c3437056SNickeau    private const PAGE_BUILD_ATTRIBUTES =
28c3437056SNickeau        [
29c3437056SNickeau            self::ROWID,
30c3437056SNickeau            DokuwikiId::DOKUWIKI_ID_ATTRIBUTE,
31c3437056SNickeau            self::ANALYTICS_ATTRIBUTE,
32c3437056SNickeau            PageDescription::PROPERTY_NAME,
33c3437056SNickeau            Canonical::PROPERTY_NAME,
34c3437056SNickeau            ResourceName::PROPERTY_NAME,
35c3437056SNickeau            PageTitle::TITLE,
36c3437056SNickeau            PageH1::PROPERTY_NAME,
37c3437056SNickeau            PagePublicationDate::PROPERTY_NAME,
38c3437056SNickeau            ModificationDate::PROPERTY_NAME,
39c3437056SNickeau            PageCreationDate::PROPERTY_NAME,
40c3437056SNickeau            PagePath::PROPERTY_NAME,
41c3437056SNickeau            StartDate::PROPERTY_NAME,
42c3437056SNickeau            EndDate::PROPERTY_NAME,
43c3437056SNickeau            Region::PROPERTY_NAME,
44c3437056SNickeau            Lang::PROPERTY_NAME,
45c3437056SNickeau            PageType::PROPERTY_NAME,
46c3437056SNickeau            PageId::PROPERTY_NAME,
47c3437056SNickeau            PageId::PAGE_ID_ABBR_ATTRIBUTE,
48c3437056SNickeau            \ReplicationDate::PROPERTY_NAME,
49c3437056SNickeau            BacklinkCount::PROPERTY_NAME
50c3437056SNickeau        ];
51c3437056SNickeau    const ANALYTICS_ATTRIBUTE = "analytics";
52c3437056SNickeau
53c3437056SNickeau    /**
54c3437056SNickeau     * For whatever reason, the row id is lowercase
55c3437056SNickeau     */
56c3437056SNickeau    const ROWID = "rowid";
57c3437056SNickeau
58c3437056SNickeau    const CANONICAL = MetadataDbStore::CANONICAL;
59c3437056SNickeau
60c3437056SNickeau    /**
61c3437056SNickeau     * @var Page
62c3437056SNickeau     */
63c3437056SNickeau    private $page;
64c3437056SNickeau    /**
65c3437056SNickeau     * @var Sqlite|null
66c3437056SNickeau     */
67c3437056SNickeau    private $sqlite;
68c3437056SNickeau
69c3437056SNickeau    /**
70c3437056SNickeau     * @var array
71c3437056SNickeau     */
72c3437056SNickeau    private $row;
73c3437056SNickeau
74c3437056SNickeau    /**
75c3437056SNickeau     * Replicate constructor.
76c3437056SNickeau     */
77c3437056SNickeau    public function __construct()
78c3437056SNickeau    {
79c3437056SNickeau
80c3437056SNickeau        /**
81c3437056SNickeau         * Persist on the DB
82c3437056SNickeau         */
83c3437056SNickeau        $this->sqlite = Sqlite::createOrGetSqlite();
84c3437056SNickeau
85c3437056SNickeau
86c3437056SNickeau    }
87c3437056SNickeau
88c3437056SNickeau    /**
89c3437056SNickeau     * Delete the cache,
90c3437056SNickeau     * Process the analytics
91c3437056SNickeau     * Save it in the Db
92c3437056SNickeau     * Delete from the page to refresh if any
93c3437056SNickeau     *
94c3437056SNickeau     * If you want the analytics:
95c3437056SNickeau     *   * from the cache use {@link self::getAnalyticsFromFs()}
96c3437056SNickeau     *   * from the db use {@link self::getAnalyticsFromDb()}
97c3437056SNickeau     *
98c3437056SNickeau     *
99c3437056SNickeau     * @throws ExceptionCombo
100c3437056SNickeau     */
101c3437056SNickeau    public function replicate(): DatabasePageRow
102c3437056SNickeau    {
103c3437056SNickeau        if ($this->sqlite === null) {
104c3437056SNickeau            throw new ExceptionCombo("Sqlite is mandatory for database replication");
105c3437056SNickeau        }
106c3437056SNickeau
107c3437056SNickeau        if (!$this->page->exists()) {
108c3437056SNickeau            throw new ExceptionCombo("You can't replicate the non-existing page ($this->page) on the file system");
109c3437056SNickeau        }
110c3437056SNickeau
111c3437056SNickeau
112c3437056SNickeau        /**
113c3437056SNickeau         * Page Replication should appears
114c3437056SNickeau         */
115c3437056SNickeau        $this->replicatePage();
116c3437056SNickeau
117c3437056SNickeau        /**
118c3437056SNickeau         * @var Metadata $tabularMetadataToSync
119c3437056SNickeau         */
120c3437056SNickeau        $tabularMetadataToSync = [
121c3437056SNickeau            (new References()),
122c3437056SNickeau            (new Aliases())
123c3437056SNickeau        ];
124c3437056SNickeau        $fsStore = MetadataDokuWikiStore::getOrCreateFromResource($this->page);
125c3437056SNickeau        $dbStore = MetadataDbStore::getOrCreateFromResource($this->page);
126c3437056SNickeau        foreach ($tabularMetadataToSync as $tabular) {
127c3437056SNickeau            $tabular
128c3437056SNickeau                ->setResource($this->page)
129c3437056SNickeau                ->setReadStore($fsStore)
130c3437056SNickeau                ->buildFromReadStore()
131c3437056SNickeau                ->setWriteStore($dbStore)
132c3437056SNickeau                ->persist();
133c3437056SNickeau        }
134c3437056SNickeau
135c3437056SNickeau        /**
136c3437056SNickeau         * Analytics (derived)
137c3437056SNickeau         * Should appear at the end of the replication because it is based
138c3437056SNickeau         * on the previous replication (ie backlink count)
139c3437056SNickeau         */
140c3437056SNickeau        $this->replicateAnalytics();
141c3437056SNickeau
142c3437056SNickeau
143c3437056SNickeau        return $this;
144c3437056SNickeau
145c3437056SNickeau    }
146c3437056SNickeau
147c3437056SNickeau    /**
148c3437056SNickeau     * @throws ExceptionCombo
149c3437056SNickeau     */
150c3437056SNickeau    public function replicateAndRebuild()
151c3437056SNickeau    {
152c3437056SNickeau        $this->replicate();
153c3437056SNickeau        $this->rebuild();
154c3437056SNickeau        return $this;
155c3437056SNickeau    }
156c3437056SNickeau
157c3437056SNickeau    private function addPageIdMeta(array &$metaRecord)
158c3437056SNickeau    {
159c3437056SNickeau        $metaRecord[PageId::PROPERTY_NAME] = $this->page->getPageId();
160c3437056SNickeau        $metaRecord[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $this->page->getPageIdAbbr();
161c3437056SNickeau    }
162c3437056SNickeau
163c3437056SNickeau    public static function createFromPageId(string $pageId): DatabasePageRow
164c3437056SNickeau    {
165c3437056SNickeau        $databasePage = new DatabasePageRow();
166c3437056SNickeau        $row = $databasePage->getDatabaseRowFromPageId($pageId);
167c3437056SNickeau        if ($row != null) {
168c3437056SNickeau            $databasePage->setRow($row);
169c3437056SNickeau        }
170c3437056SNickeau        return $databasePage;
171c3437056SNickeau    }
172c3437056SNickeau
173c3437056SNickeau    public static function createFromPageObject(Page $page): DatabasePageRow
174c3437056SNickeau    {
175c3437056SNickeau
176c3437056SNickeau        $databasePage = new DatabasePageRow();
177c3437056SNickeau        $row = $databasePage->getDatabaseRowFromPage($page);
178c3437056SNickeau        if ($row !== null) {
179c3437056SNickeau            $databasePage->setRow($row);
180c3437056SNickeau        }
181c3437056SNickeau        return $databasePage;
182c3437056SNickeau    }
183c3437056SNickeau
184c3437056SNickeau    public static function createFromPageIdAbbr(string $pageIdAbbr): DatabasePageRow
185c3437056SNickeau    {
186c3437056SNickeau        $databasePage = new DatabasePageRow();
187c3437056SNickeau        $row = $databasePage->getDatabaseRowFromAttribute(PageId::PAGE_ID_ABBR_ATTRIBUTE, $pageIdAbbr);
188c3437056SNickeau        if ($row != null) {
189c3437056SNickeau            $databasePage->setRow($row);
190c3437056SNickeau        }
191c3437056SNickeau        return $databasePage;
192c3437056SNickeau
193c3437056SNickeau    }
194c3437056SNickeau
195c3437056SNickeau    /**
196c3437056SNickeau     * @param $canonical
197c3437056SNickeau     * @return DatabasePageRow
198c3437056SNickeau     */
199c3437056SNickeau    public static function createFromCanonical($canonical): DatabasePageRow
200c3437056SNickeau    {
201c3437056SNickeau
202c3437056SNickeau        DokuPath::addRootSeparatorIfNotPresent($canonical);
203c3437056SNickeau        $databasePage = new DatabasePageRow();
204c3437056SNickeau        $row = $databasePage->getDatabaseRowFromAttribute(Canonical::PROPERTY_NAME, $canonical);
205c3437056SNickeau        if ($row != null) {
206c3437056SNickeau            $databasePage->setRow($row);
207c3437056SNickeau        }
208c3437056SNickeau        return $databasePage;
209c3437056SNickeau
210c3437056SNickeau
211c3437056SNickeau    }
212c3437056SNickeau
213c3437056SNickeau    public static function createFromAlias($alias): DatabasePageRow
214c3437056SNickeau    {
215c3437056SNickeau
216c3437056SNickeau        DokuPath::addRootSeparatorIfNotPresent($alias);
217c3437056SNickeau        $databasePage = new DatabasePageRow();
218c3437056SNickeau        $row = $databasePage->getDatabaseRowFromAlias($alias);
219c3437056SNickeau        if ($row != null) {
220c3437056SNickeau            $databasePage->setRow($row);
22158317768Sgerardnico            $page = $databasePage->getPage();
22258317768Sgerardnico            if ($page !== null) {
22358317768Sgerardnico                // page may be null in production
22458317768Sgerardnico                // PHP Fatal error:  Uncaught Error: Call to a member function setBuildAliasPath() on null in
22558317768Sgerardnico                // /opt/www/bytle/farmer.bytle.net/lib/plugins/combo/ComboStrap/DatabasePageRow.php:220
22658317768Sgerardnico                $page->setBuildAliasPath($alias);
22758317768Sgerardnico            }
228c3437056SNickeau        }
229c3437056SNickeau        return $databasePage;
230c3437056SNickeau
231c3437056SNickeau    }
232c3437056SNickeau
233c3437056SNickeau    public static function createFromDokuWikiId($id): DatabasePageRow
234c3437056SNickeau    {
235c3437056SNickeau        $databasePage = new DatabasePageRow();
236c3437056SNickeau        $row = $databasePage->getDatabaseRowFromDokuWikiId($id);
237c3437056SNickeau        if ($row !== null) {
238c3437056SNickeau            $databasePage->setRow($row);
239c3437056SNickeau        }
240c3437056SNickeau        return $databasePage;
241c3437056SNickeau    }
242c3437056SNickeau
243c3437056SNickeau    public function getPageId()
244c3437056SNickeau    {
245c3437056SNickeau        return $this->getFromRow(PageId::PROPERTY_NAME);
246c3437056SNickeau    }
247c3437056SNickeau
248c3437056SNickeau
249c3437056SNickeau    public
250c3437056SNickeau    function shouldReplicate(): bool
251c3437056SNickeau    {
252c3437056SNickeau
253*4cadd4f8SNickeau        $dateReplication = $this->getReplicationDate();
254*4cadd4f8SNickeau        if ($dateReplication === null) {
255*4cadd4f8SNickeau            return true;
256*4cadd4f8SNickeau        }
257*4cadd4f8SNickeau
258*4cadd4f8SNickeau        /**
259*4cadd4f8SNickeau         * When the replication date is older than the actual document
260*4cadd4f8SNickeau         */
261*4cadd4f8SNickeau        $modifiedTime = FileSystems::getModifiedTime($this->page->getPath());
262*4cadd4f8SNickeau        if ($modifiedTime > $dateReplication) {
263*4cadd4f8SNickeau            return true;
264*4cadd4f8SNickeau        }
265*4cadd4f8SNickeau
266c3437056SNickeau        /**
267c3437056SNickeau         * When the file does not exist
268c3437056SNickeau         */
269c3437056SNickeau        $exist = FileSystems::exists($this->page->getAnalyticsDocument()->getCachePath());
270c3437056SNickeau        if (!$exist) {
271c3437056SNickeau            return true;
272c3437056SNickeau        }
273c3437056SNickeau
274c3437056SNickeau        /**
275*4cadd4f8SNickeau         * When the analytics document is older
276c3437056SNickeau         */
277c3437056SNickeau        $modifiedTime = FileSystems::getModifiedTime($this->page->getAnalyticsDocument()->getCachePath());
278c3437056SNickeau        if ($modifiedTime > $dateReplication) {
279c3437056SNickeau            return true;
280c3437056SNickeau        }
281c3437056SNickeau
282*4cadd4f8SNickeau
283c3437056SNickeau        /**
284c3437056SNickeau         * When the database version file is higher
285c3437056SNickeau         */
286c3437056SNickeau        $version = LocalPath::createFromPath(__DIR__ . "/../db/latest.version");
287c3437056SNickeau        $versionModifiedTime = FileSystems::getModifiedTime($version);
288c3437056SNickeau        if ($versionModifiedTime > $dateReplication) {
289c3437056SNickeau            return true;
290c3437056SNickeau        }
291c3437056SNickeau
292c3437056SNickeau        /**
293c3437056SNickeau         * When the class date time is higher
294c3437056SNickeau         */
295c3437056SNickeau        $code = LocalPath::createFromPath(__DIR__ . "/DatabasePageRow.php");
296c3437056SNickeau        $codeModified = FileSystems::getModifiedTime($code);
297c3437056SNickeau        if ($codeModified > $dateReplication) {
298c3437056SNickeau            return true;
299c3437056SNickeau        }
300c3437056SNickeau
301c3437056SNickeau        return false;
302c3437056SNickeau
303c3437056SNickeau    }
304c3437056SNickeau
305c3437056SNickeau    public
306c3437056SNickeau    function delete()
307c3437056SNickeau    {
308c3437056SNickeau
309c3437056SNickeau        $request = Sqlite::createOrGetSqlite()
310c3437056SNickeau            ->createRequest()
311c3437056SNickeau            ->setQueryParametrized('delete from pages where id = ?', [$this->page->getDokuwikiId()]);
312c3437056SNickeau        try {
313c3437056SNickeau            $request->execute();
314c3437056SNickeau        } catch (ExceptionCombo $e) {
315c3437056SNickeau            LogUtility::msg("Something went wrong when deleting the page ({$this->page}) from the database");
316c3437056SNickeau        } finally {
317c3437056SNickeau            $request->close();
318c3437056SNickeau        }
319c3437056SNickeau        $this->buildInitObjectFields();
320c3437056SNickeau
321c3437056SNickeau    }
322c3437056SNickeau
323c3437056SNickeau    /**
324c3437056SNickeau     * @return Json|null the analytics array or null if not in db
325c3437056SNickeau     */
326c3437056SNickeau    public
327c3437056SNickeau    function getAnalyticsData(): ?Json
328c3437056SNickeau    {
329c3437056SNickeau
330c3437056SNickeau        $jsonString = $this->getFromRow(self::ANALYTICS_ATTRIBUTE);
331c3437056SNickeau        if ($jsonString === null) {
332c3437056SNickeau            return null;
333c3437056SNickeau        }
334c3437056SNickeau        try {
335c3437056SNickeau            return Json::createFromString($jsonString);
336c3437056SNickeau        } catch (ExceptionCombo $e) {
337c3437056SNickeau            LogUtility::msg("Error while building back the analytics JSON object. {$e->getMessage()}");
338c3437056SNickeau            return null;
339c3437056SNickeau        }
340c3437056SNickeau
341c3437056SNickeau    }
342c3437056SNickeau
343c3437056SNickeau    /**
344c3437056SNickeau     * Return the database row
345c3437056SNickeau     *
346c3437056SNickeau     *
347c3437056SNickeau     */
348c3437056SNickeau    private
349c3437056SNickeau    function getDatabaseRowFromPage(Page $page): ?array
350c3437056SNickeau    {
351c3437056SNickeau
352c3437056SNickeau        $this->setPage($page);
353c3437056SNickeau
354c3437056SNickeau        if ($this->sqlite === null) return null;
355c3437056SNickeau
356c3437056SNickeau        // Do we have a page attached to this page id
357c3437056SNickeau        $pageId = $page->getPageId();
358c3437056SNickeau        if ($pageId !== null) {
359c3437056SNickeau            $row = $this->getDatabaseRowFromPageId($pageId);
360c3437056SNickeau            if ($row !== null) {
361c3437056SNickeau                return $row;
362c3437056SNickeau            }
363c3437056SNickeau        }
364c3437056SNickeau
365c3437056SNickeau        // Do we have a page attached to the canonical
366c3437056SNickeau        $canonical = $page->getCanonical();
367c3437056SNickeau        if ($canonical != null) {
368c3437056SNickeau            $row = $this->getDatabaseRowFromCanonical($canonical);
369c3437056SNickeau            if ($row !== null) {
370c3437056SNickeau                return $row;
371c3437056SNickeau            }
372c3437056SNickeau        }
373c3437056SNickeau
374c3437056SNickeau        // Do we have a page attached to the path
375c3437056SNickeau        $path = $page->getPath();
376c3437056SNickeau        $row = $this->getDatabaseRowFromPath($path);
377c3437056SNickeau        if ($row !== null) { // the path may no yet updated in the db
378c3437056SNickeau            return $row;
379c3437056SNickeau        }
380c3437056SNickeau
381c3437056SNickeau        /**
382c3437056SNickeau         * Do we have a page attached to this ID
383c3437056SNickeau         */
384c3437056SNickeau        $id = $page->getPath()->getDokuwikiId();
385c3437056SNickeau        return $this->getDatabaseRowFromDokuWikiId($id);
386c3437056SNickeau
387c3437056SNickeau
388c3437056SNickeau    }
389c3437056SNickeau
390c3437056SNickeau
391*4cadd4f8SNickeau    /**
392*4cadd4f8SNickeau     * @return DateTime|null
393*4cadd4f8SNickeau     */
394*4cadd4f8SNickeau    public function getReplicationDate(): ?DateTime
395c3437056SNickeau    {
396c3437056SNickeau        $dateString = $this->getFromRow(\ReplicationDate::getPersistentName());
397c3437056SNickeau        if ($dateString === null) {
398c3437056SNickeau            return null;
399c3437056SNickeau        }
400c3437056SNickeau        try {
401c3437056SNickeau            return Iso8601Date::createFromString($dateString)->getDateTime();
402c3437056SNickeau        } catch (ExceptionCombo $e) {
403c3437056SNickeau            LogUtility::msg("Error while reading the replication date in the database. {$e->getMessage()}");
404c3437056SNickeau            return null;
405c3437056SNickeau        }
406c3437056SNickeau
407c3437056SNickeau    }
408c3437056SNickeau
409c3437056SNickeau    /**
410c3437056SNickeau     * @return bool
411c3437056SNickeau     * @throws ExceptionCombo
412c3437056SNickeau     */
413c3437056SNickeau    public function replicatePage(): bool
414c3437056SNickeau    {
415c3437056SNickeau
416c3437056SNickeau        if (!$this->page->exists()) {
417c3437056SNickeau            throw new ExceptionCombo("You can't replicate the page ($this->page) because it does not exists.");
418c3437056SNickeau        }
419c3437056SNickeau
420c3437056SNickeau        /**
421c3437056SNickeau         * Replication Date
422c3437056SNickeau         */
423c3437056SNickeau        $replicationDate = \ReplicationDate::createFromPage($this->page)
424c3437056SNickeau            ->setWriteStore(MetadataDbStore::class)
425*4cadd4f8SNickeau            ->setValue(new DateTime());
426c3437056SNickeau
427c3437056SNickeau        /**
428c3437056SNickeau         * Convenient variable
429c3437056SNickeau         */
430c3437056SNickeau        $page = $this->page;
431c3437056SNickeau
432c3437056SNickeau
433c3437056SNickeau        /**
434c3437056SNickeau         * Same data as {@link Page::getMetadataForRendering()}
435c3437056SNickeau         */
436c3437056SNickeau        $record = $this->getMetaRecord();
437c3437056SNickeau        $record['IS_HOME'] = ($page->isHomePage() === true ? 1 : 0);
438c3437056SNickeau        $record[$replicationDate::getPersistentName()] = $replicationDate->toStoreValue();
439c3437056SNickeau
440c3437056SNickeau        return $this->upsertAttributes($record);
441c3437056SNickeau
442c3437056SNickeau    }
443c3437056SNickeau
444c3437056SNickeau
445c3437056SNickeau    /**
446c3437056SNickeau     * @return bool when an update as occurred
447c3437056SNickeau     *
448c3437056SNickeau     * Attribute that are scalar / modifiable in the database
449c3437056SNickeau     * (not aliases or json data for instance)
450c3437056SNickeau     */
451c3437056SNickeau    public function replicateMetaAttributes(): bool
452c3437056SNickeau    {
453c3437056SNickeau
454c3437056SNickeau        return $this->upsertAttributes($this->getMetaRecord());
455c3437056SNickeau
456c3437056SNickeau    }
457c3437056SNickeau
458c3437056SNickeau    public function upsertAttributes(array $attributes): bool
459c3437056SNickeau    {
460c3437056SNickeau
461c3437056SNickeau        if ($this->sqlite === null) {
462c3437056SNickeau            return false;
463c3437056SNickeau        }
464c3437056SNickeau
465c3437056SNickeau        if (empty($attributes)) {
466c3437056SNickeau            LogUtility::msg("The page database attribute passed should not be empty");
467c3437056SNickeau            return false;
468c3437056SNickeau        }
469c3437056SNickeau
470c3437056SNickeau        $values = [];
471c3437056SNickeau        $columnClauses = [];
472c3437056SNickeau        foreach ($attributes as $key => $value) {
473c3437056SNickeau            if (is_array($value)) {
474c3437056SNickeau                throw new ExceptionComboRuntime("The attribute ($key) has value that is an array (" . implode(", ", $value) . ")");
475c3437056SNickeau            }
476c3437056SNickeau            $columnClauses[] = "$key = ?";
477c3437056SNickeau            $values[$key] = $value;
478c3437056SNickeau        }
479c3437056SNickeau
480c3437056SNickeau        /**
481c3437056SNickeau         * Primary key has moved during the time
482c3437056SNickeau         * It should be the UUID but not for older version
483c3437056SNickeau         *
484c3437056SNickeau         * If the primary key is null, no record was found
485c3437056SNickeau         */
486c3437056SNickeau        $rowId = $this->getRowId();
487c3437056SNickeau        if ($rowId !== null) {
488c3437056SNickeau            /**
489c3437056SNickeau             * We just add the primary key
490c3437056SNickeau             * otherwise as this is a associative
491c3437056SNickeau             * array, we will miss a value for the update statement
492c3437056SNickeau             */
493c3437056SNickeau            $values[] = $rowId;
494c3437056SNickeau
495c3437056SNickeau            $updateStatement = "update PAGES SET " . implode(", ", $columnClauses) . " where ROWID = ?";
496c3437056SNickeau            $request = $this->sqlite
497c3437056SNickeau                ->createRequest()
498c3437056SNickeau                ->setQueryParametrized($updateStatement, $values);
499c3437056SNickeau            $countChanges = 0;
500c3437056SNickeau            try {
501c3437056SNickeau                $countChanges = $request
502c3437056SNickeau                    ->execute()
503c3437056SNickeau                    ->getChangeCount();
504c3437056SNickeau            } catch (ExceptionCombo $e) {
505c3437056SNickeau                LogUtility::msg("There was a problem during the page attribute updates. : {$e->getMessage()}");
506c3437056SNickeau                return false;
507c3437056SNickeau            } finally {
508c3437056SNickeau                $request->close();
509c3437056SNickeau            }
510c3437056SNickeau            if ($countChanges !== 1) {
511c3437056SNickeau                LogUtility::msg("The database replication has not updated exactly 1 record but ($countChanges) record", LogUtility::LVL_MSG_ERROR, \action_plugin_combo_fulldatabasereplication::CANONICAL);
512c3437056SNickeau            }
513c3437056SNickeau
514c3437056SNickeau        } else {
515c3437056SNickeau
516c3437056SNickeau            /**
517c3437056SNickeau             * Creation
518c3437056SNickeau             */
519c3437056SNickeau            if ($this->page === null) {
520c3437056SNickeau                LogUtility::msg("The page should be defined to create a page row");
521c3437056SNickeau                return false;
522c3437056SNickeau            }
523c3437056SNickeau            $values[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] = $this->page->getPath()->getDokuwikiId();
524c3437056SNickeau            $values[PagePath::PROPERTY_NAME] = $this->page->getPath()->toAbsolutePath()->toString();
525c3437056SNickeau            /**
526c3437056SNickeau             * Default implements the auto-canonical feature
527c3437056SNickeau             */
528c3437056SNickeau            $values[Canonical::PROPERTY_NAME] = $this->page->getCanonicalOrDefault();
529c3437056SNickeau
530c3437056SNickeau            /**
531c3437056SNickeau             * Analytics
532c3437056SNickeau             */
533c3437056SNickeau            if (!isset($values[self::ANALYTICS_ATTRIBUTE])) {
534c3437056SNickeau                // otherwise we get an empty string
535c3437056SNickeau                // and a json function will not work
536c3437056SNickeau                $values[self::ANALYTICS_ATTRIBUTE] = Json::createEmpty()->toPrettyJsonString();
537c3437056SNickeau            }
538c3437056SNickeau
539c3437056SNickeau            /**
540c3437056SNickeau             * Page Id / Abbr are mandatory for url redirection
541c3437056SNickeau             */
542c3437056SNickeau            $this->addPageIdAttributeIfNeeded($values);
543c3437056SNickeau
544c3437056SNickeau            $request = $this->sqlite
545c3437056SNickeau                ->createRequest()
546c3437056SNickeau                ->setTableRow('PAGES', $values);
547c3437056SNickeau            try {
548c3437056SNickeau                /**
549c3437056SNickeau                 * rowid is used in {@link DatabasePageRow::exists()}
550c3437056SNickeau                 * to check if the page exists in the database
551c3437056SNickeau                 * We update it
552c3437056SNickeau                 */
553c3437056SNickeau                $this->row[self::ROWID] = $request
554c3437056SNickeau                    ->execute()
555c3437056SNickeau                    ->getInsertId();
556c3437056SNickeau                $this->row = array_merge($values, $this->row);
557c3437056SNickeau            } catch (ExceptionCombo $e) {
558c3437056SNickeau                LogUtility::msg("There was a problem during the updateAttributes insert. : {$e->getMessage()}");
559c3437056SNickeau                return false;
560c3437056SNickeau            } finally {
561c3437056SNickeau                $request->close();
562c3437056SNickeau            }
563c3437056SNickeau
564c3437056SNickeau        }
565c3437056SNickeau        return true;
566c3437056SNickeau
567c3437056SNickeau    }
568c3437056SNickeau
569c3437056SNickeau    public
570c3437056SNickeau    function getDescription()
571c3437056SNickeau    {
572c3437056SNickeau        return $this->getFromRow(PageDescription::DESCRIPTION_PROPERTY);
573c3437056SNickeau    }
574c3437056SNickeau
575c3437056SNickeau
576c3437056SNickeau    public
577c3437056SNickeau    function getPageName()
578c3437056SNickeau    {
579c3437056SNickeau        return $this->getFromRow(ResourceName::PROPERTY_NAME);
580c3437056SNickeau    }
581c3437056SNickeau
582c3437056SNickeau    public
583c3437056SNickeau    function exists(): bool
584c3437056SNickeau    {
585c3437056SNickeau        return $this->getFromRow(self::ROWID) !== null;
586c3437056SNickeau    }
587c3437056SNickeau
588c3437056SNickeau    /**
589c3437056SNickeau     * Called when a page is moved
590c3437056SNickeau     * @param $targetId
591c3437056SNickeau     */
592c3437056SNickeau    public
593c3437056SNickeau    function updatePathAndDokuwikiId($targetId)
594c3437056SNickeau    {
595c3437056SNickeau        if (!$this->exists()) {
596c3437056SNickeau            LogUtility::msg("The `database` page ($this) does not exist and cannot be moved to ($targetId)", LogUtility::LVL_MSG_ERROR);
597c3437056SNickeau        }
598c3437056SNickeau
599c3437056SNickeau        $path = $targetId;
600c3437056SNickeau        DokuPath::addRootSeparatorIfNotPresent($path);
601c3437056SNickeau        $attributes = [
602c3437056SNickeau            DokuwikiId::DOKUWIKI_ID_ATTRIBUTE => $targetId,
603c3437056SNickeau            PagePath::PROPERTY_NAME => $path
604c3437056SNickeau        ];
605c3437056SNickeau
606c3437056SNickeau        $this->upsertAttributes($attributes);
607c3437056SNickeau
608c3437056SNickeau    }
609c3437056SNickeau
610c3437056SNickeau    public
611c3437056SNickeau    function __toString()
612c3437056SNickeau    {
613c3437056SNickeau        return $this->page->__toString();
614c3437056SNickeau    }
615c3437056SNickeau
616c3437056SNickeau
617c3437056SNickeau    /**
618c3437056SNickeau     * Redirect are now added during a move
619c3437056SNickeau     * Not when a duplicate is found.
620c3437056SNickeau     * With the advent of the page id, it should never occurs anymore
621c3437056SNickeau     * @param Page $page
622c3437056SNickeau     * @deprecated 2012-10-28
623c3437056SNickeau     */
624c3437056SNickeau    private
625c3437056SNickeau    function deleteIfExistsAndAddRedirectAlias(Page $page): void
626c3437056SNickeau    {
627c3437056SNickeau
628c3437056SNickeau        if ($this->page != null) {
629c3437056SNickeau            $page->getDatabasePage()->deleteIfExist();
630c3437056SNickeau            $this->addRedirectAliasWhileBuildingRow($page);
631c3437056SNickeau        }
632c3437056SNickeau
633c3437056SNickeau    }
634c3437056SNickeau
635c3437056SNickeau    public
636c3437056SNickeau    function getCanonical()
637c3437056SNickeau    {
638c3437056SNickeau        return $this->getFromRow(Canonical::PROPERTY_NAME);
639c3437056SNickeau    }
640c3437056SNickeau
641c3437056SNickeau    /**
642c3437056SNickeau     * Set the field to their values
643c3437056SNickeau     * @param $row
644c3437056SNickeau     */
645c3437056SNickeau    public
646c3437056SNickeau    function setRow($row)
647c3437056SNickeau    {
648c3437056SNickeau        if ($row === null) {
649c3437056SNickeau            LogUtility::msg("A row should not be null");
650c3437056SNickeau            return;
651c3437056SNickeau        }
652c3437056SNickeau        if (!is_array($row)) {
653c3437056SNickeau            LogUtility::msg("A row should be an array");
654c3437056SNickeau            return;
655c3437056SNickeau        }
656c3437056SNickeau
657c3437056SNickeau        /**
658c3437056SNickeau         * All get function lookup the row
659c3437056SNickeau         */
660c3437056SNickeau        $this->row = $row;
661c3437056SNickeau
662c3437056SNickeau
663c3437056SNickeau    }
664c3437056SNickeau
665c3437056SNickeau    private
666c3437056SNickeau    function buildInitObjectFields()
667c3437056SNickeau    {
668c3437056SNickeau        $this->row = null;
669c3437056SNickeau
670c3437056SNickeau    }
671c3437056SNickeau
672c3437056SNickeau    public
673c3437056SNickeau    function rebuild(): DatabasePageRow
674c3437056SNickeau    {
675c3437056SNickeau
676c3437056SNickeau        if ($this->page != null) {
677c3437056SNickeau            $this->page->rebuild();
678c3437056SNickeau            $row = $this->getDatabaseRowFromPage($this->page);
679c3437056SNickeau            if ($row !== null) {
680c3437056SNickeau                $this->setRow($row);
681c3437056SNickeau            }
682c3437056SNickeau        }
683c3437056SNickeau        return $this;
684c3437056SNickeau
685c3437056SNickeau    }
686c3437056SNickeau
687c3437056SNickeau    /**
688c3437056SNickeau     * @return array - an array of the fix page metadata (ie not derived)
689c3437056SNickeau     * Therefore quick to insert/update
690c3437056SNickeau     *
691c3437056SNickeau     */
692c3437056SNickeau    private
693c3437056SNickeau    function getMetaRecord(): array
694c3437056SNickeau    {
695c3437056SNickeau        $sourceStore = MetadataDokuWikiStore::getOrCreateFromResource($this->page);
696c3437056SNickeau        $targetStore = MetadataDbStore::getOrCreateFromResource($this->page);
697c3437056SNickeau
698c3437056SNickeau        $record = array(
699c3437056SNickeau            Canonical::PROPERTY_NAME,
700c3437056SNickeau            PagePath::PROPERTY_NAME,
701c3437056SNickeau            ResourceName::PROPERTY_NAME,
702c3437056SNickeau            PageTitle::TITLE,
703c3437056SNickeau            PageH1::PROPERTY_NAME,
704c3437056SNickeau            PageDescription::PROPERTY_NAME,
705c3437056SNickeau            PageCreationDate::PROPERTY_NAME,
706c3437056SNickeau            ModificationDate::PROPERTY_NAME,
707c3437056SNickeau            PagePublicationDate::PROPERTY_NAME,
708c3437056SNickeau            StartDate::PROPERTY_NAME,
709c3437056SNickeau            EndDate::PROPERTY_NAME,
710c3437056SNickeau            Region::PROPERTY_NAME,
711c3437056SNickeau            Lang::PROPERTY_NAME,
712c3437056SNickeau            PageType::PROPERTY_NAME,
713c3437056SNickeau            DokuwikiId::DOKUWIKI_ID_ATTRIBUTE,
714c3437056SNickeau        );
715c3437056SNickeau        $metaRecord = [];
716c3437056SNickeau        foreach ($record as $name) {
717c3437056SNickeau            $metadata = Metadata::getForName($name);
718c3437056SNickeau            if ($metadata === null) {
719c3437056SNickeau                throw new ExceptionComboRuntime("The metadata ($name) is unknown");
720c3437056SNickeau            }
721c3437056SNickeau            $metaRecord[$name] = $metadata
722c3437056SNickeau                ->setResource($this->page)
723c3437056SNickeau                ->setReadStore($sourceStore)
724c3437056SNickeau                ->buildFromReadStore()
725c3437056SNickeau                ->setWriteStore($targetStore)
726c3437056SNickeau                ->toStoreValueOrDefault(); // used by the template, the value is or default
727c3437056SNickeau        }
728c3437056SNickeau
729c3437056SNickeau        if ($this->page->getPageId() !== null) {
730c3437056SNickeau            $this->addPageIdMeta($metaRecord);
731c3437056SNickeau        }
732c3437056SNickeau        return $metaRecord;
733c3437056SNickeau    }
734c3437056SNickeau
735c3437056SNickeau    public
736c3437056SNickeau    function deleteIfExist(): DatabasePageRow
737c3437056SNickeau    {
738c3437056SNickeau        if ($this->exists()) {
739c3437056SNickeau            $this->delete();
740c3437056SNickeau        }
741c3437056SNickeau        return $this;
742c3437056SNickeau    }
743c3437056SNickeau
744c3437056SNickeau    public
745c3437056SNickeau    function getRowId()
746c3437056SNickeau    {
747c3437056SNickeau        return $this->getFromRow(self::ROWID);
748c3437056SNickeau    }
749c3437056SNickeau
750c3437056SNickeau    private
751c3437056SNickeau    function getDatabaseRowFromPageId(string $pageId)
752c3437056SNickeau    {
753c3437056SNickeau
754c3437056SNickeau        if ($this->sqlite === null) {
755c3437056SNickeau            return null;
756c3437056SNickeau        }
757c3437056SNickeau
758c3437056SNickeau        $pageIdAttribute = PageId::PROPERTY_NAME;
759c3437056SNickeau        $query = $this->getParametrizedLookupQuery($pageIdAttribute);
760c3437056SNickeau        $request = Sqlite::createOrGetSqlite()
761c3437056SNickeau            ->createRequest()
762c3437056SNickeau            ->setQueryParametrized($query, [$pageId]);
763c3437056SNickeau        $rows = [];
764c3437056SNickeau        try {
765c3437056SNickeau            $rows = $request
766c3437056SNickeau                ->execute()
767c3437056SNickeau                ->getRows();
768c3437056SNickeau        } catch (ExceptionCombo $e) {
769c3437056SNickeau            LogUtility::msg($e->getMessage(), LogUtility::LVL_MSG_ERROR, $e->getCanonical());
770c3437056SNickeau            return null;
771c3437056SNickeau        } finally {
772c3437056SNickeau            $request->close();
773c3437056SNickeau        }
774c3437056SNickeau
775c3437056SNickeau        switch (sizeof($rows)) {
776c3437056SNickeau            case 0:
777c3437056SNickeau                return null;
778c3437056SNickeau            case 1:
779c3437056SNickeau                $id = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE];
780c3437056SNickeau                /**
781c3437056SNickeau                 * Page Id Collision detection
782c3437056SNickeau                 */
783c3437056SNickeau                if ($this->page != null && $id !== $this->page->getDokuwikiId()) {
784c3437056SNickeau                    $duplicatePage = Page::createPageFromId($id);
785c3437056SNickeau                    if (!$duplicatePage->exists()) {
786c3437056SNickeau                        // Move
787c3437056SNickeau                        LogUtility::msg("The non-existing duplicate page ($id) has been added as redirect alias for the page ($this->page)", LogUtility::LVL_MSG_INFO);
788c3437056SNickeau                        $this->addRedirectAliasWhileBuildingRow($duplicatePage);
789c3437056SNickeau                    } else {
790c3437056SNickeau                        // This can happens if two page were created not on the same website
791c3437056SNickeau                        // of if the sqlite database was deleted and rebuilt.
792c3437056SNickeau                        // The chance is really, really low
793c3437056SNickeau                        $errorMessage = "The page ($this->page) and the page ($id) have the same page id ($pageId)";
794c3437056SNickeau                        LogUtility::msg($errorMessage, LogUtility::LVL_MSG_ERROR, self::CANONICAL);
795c3437056SNickeau                        // What to do ?
796c3437056SNickeau                        // The database does not allow two page id with the same value
797c3437056SNickeau                        // If it happens, ugh, ugh, ..., a replication process between website may be.
798c3437056SNickeau                        return null;
799c3437056SNickeau                    }
800c3437056SNickeau                }
801c3437056SNickeau                return $rows[0];
802c3437056SNickeau            default:
803c3437056SNickeau                $existingPages = implode(", ", $rows);
804c3437056SNickeau                LogUtility::msg("The pages ($existingPages) have all the same page id ($pageId)", LogUtility::LVL_MSG_ERROR);
805c3437056SNickeau                return null;
806c3437056SNickeau        }
807c3437056SNickeau
808c3437056SNickeau    }
809c3437056SNickeau
810c3437056SNickeau
811c3437056SNickeau    private
812c3437056SNickeau    function getParametrizedLookupQuery(string $pageIdAttribute): string
813c3437056SNickeau    {
814c3437056SNickeau        $select = Sqlite::createSelectFromTableAndColumns("pages", self::PAGE_BUILD_ATTRIBUTES);
815c3437056SNickeau        return "$select where $pageIdAttribute = ?";
816c3437056SNickeau    }
817c3437056SNickeau
818c3437056SNickeau
819c3437056SNickeau    public function setPage(Page $page)
820c3437056SNickeau    {
821c3437056SNickeau        $this->page = $page;
822c3437056SNickeau        return $this;
823c3437056SNickeau    }
824c3437056SNickeau
825c3437056SNickeau    private
826c3437056SNickeau    function getDatabaseRowFromCanonical($canonical)
827c3437056SNickeau    {
828c3437056SNickeau        $query = $this->getParametrizedLookupQuery(Canonical::PROPERTY_NAME);
829c3437056SNickeau        $request = $this->sqlite
830c3437056SNickeau            ->createRequest()
831c3437056SNickeau            ->setQueryParametrized($query, [$canonical]);
832c3437056SNickeau        $rows = [];
833c3437056SNickeau        try {
834c3437056SNickeau            $rows = $request
835c3437056SNickeau                ->execute()
836c3437056SNickeau                ->getRows();
837c3437056SNickeau        } catch (ExceptionCombo $e) {
838c3437056SNickeau            LogUtility::msg("An exception has occurred with the page search from CANONICAL. " . $e->getMessage());
839c3437056SNickeau            return null;
840c3437056SNickeau        } finally {
841c3437056SNickeau            $request->close();
842c3437056SNickeau        }
843c3437056SNickeau
844c3437056SNickeau        switch (sizeof($rows)) {
845c3437056SNickeau            case 0:
846c3437056SNickeau                return null;
847c3437056SNickeau            case 1:
848c3437056SNickeau                $id = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE];
849c3437056SNickeau                if ($this->page !== null && $id !== $this->page->getDokuwikiId()) {
850c3437056SNickeau                    $duplicatePage = Page::createPageFromId($id);
851c3437056SNickeau                    if (!$duplicatePage->exists()) {
852c3437056SNickeau                        $this->addRedirectAliasWhileBuildingRow($duplicatePage);
853c3437056SNickeau                        LogUtility::msg("The non-existing duplicate page ($id) has been added as redirect alias for the page ($this->page)", LogUtility::LVL_MSG_INFO);
854c3437056SNickeau                    } else {
855c3437056SNickeau                        LogUtility::msg("The page ($this->page) and the page ($id) have the same canonical ($canonical)", LogUtility::LVL_MSG_ERROR);
856c3437056SNickeau                    }
857c3437056SNickeau                }
858c3437056SNickeau                return $rows[0];
859c3437056SNickeau            default:
860c3437056SNickeau                $existingPages = [];
861c3437056SNickeau                foreach ($rows as $row) {
862c3437056SNickeau                    $id = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE];
863c3437056SNickeau                    $duplicatePage = Page::createPageFromId($id);
864c3437056SNickeau                    if (!$duplicatePage->exists()) {
865c3437056SNickeau
866c3437056SNickeau                        $this->deleteIfExistsAndAddRedirectAlias($duplicatePage);
867c3437056SNickeau
868c3437056SNickeau                    } else {
869c3437056SNickeau
870c3437056SNickeau                        /**
871c3437056SNickeau                         * Check if the error may come from the auto-canonical
872c3437056SNickeau                         * (Never ever save generated data)
873c3437056SNickeau                         */
874c3437056SNickeau                        $canonicalLastNamesCount = PluginUtility::getConfValue(Canonical::CONF_CANONICAL_LAST_NAMES_COUNT, 0);
875c3437056SNickeau                        if ($canonicalLastNamesCount > 0) {
876c3437056SNickeau                            $this->page->unsetMetadata(Canonical::PROPERTY_NAME);
877c3437056SNickeau                            $duplicatePage->unsetMetadata(Canonical::PROPERTY_NAME);
878c3437056SNickeau                        }
879c3437056SNickeau
880c3437056SNickeau                        $existingPages[] = $row;
881c3437056SNickeau                    }
882c3437056SNickeau                }
883c3437056SNickeau                if (sizeof($existingPages) === 1) {
884c3437056SNickeau                    return $existingPages[0];
885c3437056SNickeau                } else {
886c3437056SNickeau                    $existingPages = implode(", ", $existingPages);
887c3437056SNickeau                    LogUtility::msg("The existing pages ($existingPages) have all the same canonical ($canonical)", LogUtility::LVL_MSG_ERROR);
888c3437056SNickeau                    return null;
889c3437056SNickeau                }
890c3437056SNickeau        }
891c3437056SNickeau    }
892c3437056SNickeau
893c3437056SNickeau    private
894c3437056SNickeau    function getDatabaseRowFromPath(string $path): ?array
895c3437056SNickeau    {
896c3437056SNickeau        DokuPath::addRootSeparatorIfNotPresent($path);
897c3437056SNickeau        return $this->getDatabaseRowFromAttribute(PagePath::PROPERTY_NAME, $path);
898c3437056SNickeau    }
899c3437056SNickeau
900c3437056SNickeau    private
901c3437056SNickeau    function getDatabaseRowFromDokuWikiId(string $id): ?array
902c3437056SNickeau    {
903c3437056SNickeau        return $this->getDatabaseRowFromAttribute(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $id);
904c3437056SNickeau    }
905c3437056SNickeau
906c3437056SNickeau    public
907c3437056SNickeau    function getDatabaseRowFromAttribute(string $attribute, string $value)
908c3437056SNickeau    {
909c3437056SNickeau        $query = $this->getParametrizedLookupQuery($attribute);
910c3437056SNickeau        $request = $this->sqlite
911c3437056SNickeau            ->createRequest()
912c3437056SNickeau            ->setQueryParametrized($query, [$value]);
913c3437056SNickeau        $rows = [];
914c3437056SNickeau        try {
915c3437056SNickeau            $rows = $request
916c3437056SNickeau                ->execute()
917c3437056SNickeau                ->getRows();
918c3437056SNickeau        } catch (ExceptionCombo $e) {
919c3437056SNickeau            LogUtility::msg("An exception has occurred with the page search from a PATH: " . $e->getMessage());
920c3437056SNickeau            return null;
921c3437056SNickeau        } finally {
922c3437056SNickeau            $request->close();
923c3437056SNickeau        }
924c3437056SNickeau
925c3437056SNickeau        switch (sizeof($rows)) {
926c3437056SNickeau            case 0:
927c3437056SNickeau                return null;
928c3437056SNickeau            case 1:
929c3437056SNickeau                $value = $rows[0][DokuwikiId::DOKUWIKI_ID_ATTRIBUTE];
930c3437056SNickeau                if ($this->page != null && $value !== $this->page->getDokuwikiId()) {
931c3437056SNickeau                    $duplicatePage = Page::createPageFromId($value);
932c3437056SNickeau                    if (!$duplicatePage->exists()) {
933c3437056SNickeau                        $this->addRedirectAliasWhileBuildingRow($duplicatePage);
934c3437056SNickeau                    } else {
935c3437056SNickeau                        LogUtility::msg("The page ($this->page) and the page ($value) have the same $attribute ($value)", LogUtility::LVL_MSG_ERROR);
936c3437056SNickeau                    }
937c3437056SNickeau                }
938c3437056SNickeau                return $rows[0];
939c3437056SNickeau            default:
940c3437056SNickeau                $existingPages = [];
941c3437056SNickeau                foreach ($rows as $row) {
942c3437056SNickeau                    $value = $row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE];
943c3437056SNickeau                    $duplicatePage = Page::createPageFromId($value);
944c3437056SNickeau                    if (!$duplicatePage->exists()) {
945c3437056SNickeau
946c3437056SNickeau                        $this->deleteIfExistsAndAddRedirectAlias($duplicatePage);
947c3437056SNickeau
948c3437056SNickeau                    } else {
949c3437056SNickeau                        $existingPages[] = $row;
950c3437056SNickeau                    }
951c3437056SNickeau                }
952c3437056SNickeau                if (sizeof($existingPages) === 1) {
953c3437056SNickeau                    return $existingPages[0];
954c3437056SNickeau                } else {
955c3437056SNickeau                    $existingPages = implode(", ", $existingPages);
956c3437056SNickeau                    LogUtility::msg("The existing pages ($existingPages) have all the same $attribute ($value)", LogUtility::LVL_MSG_ERROR);
957c3437056SNickeau                    return null;
958c3437056SNickeau                }
959c3437056SNickeau        }
960c3437056SNickeau    }
961c3437056SNickeau
962c3437056SNickeau    public
963c3437056SNickeau    function getPage(): ?Page
964c3437056SNickeau    {
965c3437056SNickeau        if (
966c3437056SNickeau            $this->page === null
967c3437056SNickeau            && $this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE] !== null
968c3437056SNickeau        ) {
969c3437056SNickeau            $this->page = Page::createPageFromId($this->row[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE]);
970c3437056SNickeau        }
971c3437056SNickeau        return $this->page;
972c3437056SNickeau    }
973c3437056SNickeau
974c3437056SNickeau    private
975c3437056SNickeau    function getDatabaseRowFromAlias($alias): ?array
976c3437056SNickeau    {
977c3437056SNickeau
978c3437056SNickeau        $pageIdAttribute = PageId::PROPERTY_NAME;
979c3437056SNickeau        $buildFields = self::PAGE_BUILD_ATTRIBUTES;
980c3437056SNickeau        $fields = array_reduce($buildFields, function ($carry, $element) {
981c3437056SNickeau            if ($carry !== null) {
982c3437056SNickeau                return "$carry, p.{$element}";
983c3437056SNickeau            } else {
984c3437056SNickeau                return "p.{$element}";
985c3437056SNickeau            }
986c3437056SNickeau        }, null);
987c3437056SNickeau        $query = "select {$fields} from PAGES p, PAGE_ALIASES pa where p.{$pageIdAttribute} = pa.{$pageIdAttribute} and pa.PATH = ? ";
988c3437056SNickeau        $request = $this->sqlite
989c3437056SNickeau            ->createRequest()
990c3437056SNickeau            ->setQueryParametrized($query, [$alias]);
991c3437056SNickeau        $rows = [];
992c3437056SNickeau        try {
993c3437056SNickeau            $rows = $request
994c3437056SNickeau                ->execute()
995c3437056SNickeau                ->getRows();
996c3437056SNickeau        } catch (ExceptionCombo $e) {
997c3437056SNickeau            LogUtility::msg("An exception has occurred with the alias selection query. {$e->getMessage()}");
998c3437056SNickeau            return null;
999c3437056SNickeau        } finally {
1000c3437056SNickeau            $request->close();
1001c3437056SNickeau        }
1002c3437056SNickeau        switch (sizeof($rows)) {
1003c3437056SNickeau            case 0:
1004c3437056SNickeau                return null;
1005c3437056SNickeau            case 1:
1006c3437056SNickeau                return $rows[0];
1007c3437056SNickeau            default:
1008c3437056SNickeau                $id = $rows[0]['ID'];
1009c3437056SNickeau                $pages = implode(",",
1010c3437056SNickeau                    array_map(
1011c3437056SNickeau                        function ($row) {
1012c3437056SNickeau                            return $row['ID'];
1013c3437056SNickeau                        },
1014c3437056SNickeau                        $rows
1015c3437056SNickeau                    )
1016c3437056SNickeau                );
1017c3437056SNickeau                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);
1018c3437056SNickeau                return $rows[0];
1019c3437056SNickeau        }
1020c3437056SNickeau    }
1021c3437056SNickeau
1022c3437056SNickeau
1023c3437056SNickeau    /**
1024c3437056SNickeau     * Utility function
1025c3437056SNickeau     * @param Page $pageAlias
1026c3437056SNickeau     */
1027c3437056SNickeau    private
1028c3437056SNickeau    function addRedirectAliasWhileBuildingRow(Page $pageAlias)
1029c3437056SNickeau    {
1030c3437056SNickeau
1031c3437056SNickeau        $aliasPath = $pageAlias->getPath()->toString();
1032c3437056SNickeau        try {
1033c3437056SNickeau            Aliases::createForPage($this->page)
1034c3437056SNickeau                ->addAlias($aliasPath)
1035c3437056SNickeau                ->sendToWriteStore();
1036c3437056SNickeau        } catch (ExceptionCombo $e) {
1037c3437056SNickeau            // we don't throw while getting
1038c3437056SNickeau            LogUtility::msg("Unable to add the alias ($aliasPath) for the page ($this->page)");
1039c3437056SNickeau        }
1040c3437056SNickeau
1041c3437056SNickeau    }
1042c3437056SNickeau
1043c3437056SNickeau    private
1044c3437056SNickeau    function addPageIdAttributeIfNeeded(array &$values)
1045c3437056SNickeau    {
1046c3437056SNickeau        if (!isset($values[PageId::getPersistentName()])) {
1047c3437056SNickeau            $values[PageId::getPersistentName()] = $this->page->getPageId();
1048c3437056SNickeau        }
1049c3437056SNickeau        if (!isset($values[PageId::PAGE_ID_ABBR_ATTRIBUTE])) {
1050c3437056SNickeau            $values[PageId::PAGE_ID_ABBR_ATTRIBUTE] = $this->page->getPageIdAbbr();
1051c3437056SNickeau        }
1052c3437056SNickeau    }
1053c3437056SNickeau
1054c3437056SNickeau    public
1055c3437056SNickeau    function getFromRow(string $attribute)
1056c3437056SNickeau    {
1057c3437056SNickeau        if ($this->row === null) {
1058c3437056SNickeau            return null;
1059c3437056SNickeau        }
1060c3437056SNickeau
1061c3437056SNickeau        if (!array_key_exists($attribute, $this->row)) {
1062c3437056SNickeau            /**
1063c3437056SNickeau             * An attribute should be added to {@link DatabasePageRow::PAGE_BUILD_ATTRIBUTES}
1064c3437056SNickeau             * or in the table
1065c3437056SNickeau             */
1066c3437056SNickeau            throw new ExceptionComboRuntime("The metadata ($attribute) was not found in the returned database row.", $this->getCanonical());
1067c3437056SNickeau        }
1068c3437056SNickeau
1069c3437056SNickeau        $value = $this->row[$attribute];
1070c3437056SNickeau
1071c3437056SNickeau        if ($value !== null) {
1072c3437056SNickeau            return $value;
1073c3437056SNickeau        }
1074c3437056SNickeau
1075c3437056SNickeau        // don't know why but the sqlite plugin returns them uppercase
1076c3437056SNickeau        // rowid is returned lowercase from the sqlite plugin
1077c3437056SNickeau        $upperAttribute = strtoupper($attribute);
1078c3437056SNickeau        return $this->row[$upperAttribute];
1079c3437056SNickeau
1080c3437056SNickeau    }
1081c3437056SNickeau
1082c3437056SNickeau
1083*4cadd4f8SNickeau    /**
1084*4cadd4f8SNickeau     * @throws ExceptionCombo
1085*4cadd4f8SNickeau     */
1086c3437056SNickeau    public function replicateAnalytics()
1087c3437056SNickeau    {
1088c3437056SNickeau
1089c3437056SNickeau        try {
1090c3437056SNickeau            $analyticsJson = $this->page->getAnalyticsDocument()->getOrProcessJson();
1091c3437056SNickeau        } catch (ExceptionCombo $e) {
1092*4cadd4f8SNickeau            throw new ExceptionCombo("Unable to get the analytics document", self::CANONICAL, 0, $e);
1093c3437056SNickeau        }
1094c3437056SNickeau
1095c3437056SNickeau        /**
1096c3437056SNickeau         * Replication Date
1097c3437056SNickeau         */
1098c3437056SNickeau        $replicationDateMeta = \ReplicationDate::createFromPage($this->page)
1099c3437056SNickeau            ->setWriteStore(MetadataDbStore::class)
1100*4cadd4f8SNickeau            ->setValue(new DateTime());
1101c3437056SNickeau
1102c3437056SNickeau        /**
1103c3437056SNickeau         * Analytics
1104c3437056SNickeau         */
1105c3437056SNickeau        $analyticsJsonAsString = $analyticsJson->toPrettyJsonString();
1106c3437056SNickeau        $analyticsJsonAsArray = $analyticsJson->toArray();
1107c3437056SNickeau
1108c3437056SNickeau        /**
1109c3437056SNickeau         * Record
1110c3437056SNickeau         */
1111c3437056SNickeau        $record[self::ANALYTICS_ATTRIBUTE] = $analyticsJsonAsString;
1112c3437056SNickeau        $record['IS_LOW_QUALITY'] = ($this->page->isLowQualityPage() === true ? 1 : 0);
1113c3437056SNickeau        $record['WORD_COUNT'] = $analyticsJsonAsArray[AnalyticsDocument::STATISTICS][AnalyticsDocument::WORD_COUNT];
1114c3437056SNickeau        $record[BacklinkCount::getPersistentName()] = $analyticsJsonAsArray[AnalyticsDocument::STATISTICS][BacklinkCount::getPersistentName()];
1115c3437056SNickeau        $record[$replicationDateMeta::getPersistentName()] = $replicationDateMeta->toStoreValue();
1116c3437056SNickeau        $this->upsertAttributes($record);
1117c3437056SNickeau    }
1118c3437056SNickeau
1119c3437056SNickeau
1120c3437056SNickeau}
1121