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