xref: /template/strap/ComboStrap/Event.php (revision b0217656fbd7074bf0361e84c602ec93a90c5feb)
1c3437056SNickeau<?php
2c3437056SNickeau
3c3437056SNickeau
4c3437056SNickeaunamespace ComboStrap;
5c3437056SNickeau
6c3437056SNickeau/**
7c3437056SNickeau * Class Event
8c3437056SNickeau * @package ComboStrap
9c3437056SNickeau * Asynchronous pub/sub system
10c3437056SNickeau *
11c3437056SNickeau * Dokuwiki allows event but they are synchronous
12c3437056SNickeau * because php does not live in multiple thread
13c3437056SNickeau *
14c3437056SNickeau * With the help of Sqlite, we make them asynchronous
15c3437056SNickeau */
16c3437056SNickeauclass Event
17c3437056SNickeau{
18c3437056SNickeau
19c3437056SNickeau    const EVENT_TABLE_NAME = "EVENTS_QUEUE";
20c3437056SNickeau
21c3437056SNickeau    const CANONICAL = "support";
22*b0217656Sgerardnico
23*b0217656Sgerardnico    /**
24*b0217656Sgerardnico     * Uppercase mandatory (the column is uppercased when returnd from a *)
25*b0217656Sgerardnico     */
26*b0217656Sgerardnico    const EVENT_NAME_ATTRIBUTE = "NAME";
27*b0217656Sgerardnico
28*b0217656Sgerardnico    /**
29*b0217656Sgerardnico     * Uppercase mandatory (the column is uppercased when returnd from a *)
30*b0217656Sgerardnico     */
31*b0217656Sgerardnico    const EVENT_DATA_ATTRIBUTE = "DATA";
32*b0217656Sgerardnico    /**
33*b0217656Sgerardnico     * Uppercase mandatory (the column is uppercased when returnd from a *)
34*b0217656Sgerardnico     */
35*b0217656Sgerardnico    const TIMESTAMP_ATTRIBUTE = "TIMESTAMP";
36c3437056SNickeau
37c3437056SNickeau    /**
38c3437056SNickeau     * process all replication request, created with {@link Event::createEvent()}
39c3437056SNickeau     *
40c3437056SNickeau     * by default, there is 5 pages in a default dokuwiki installation in the wiki namespace)
41*b0217656Sgerardnico     *
42*b0217656Sgerardnico     * @param int $maxEvent In case of a start or if there is a recursive bug. We don't want to take all the resources
43*b0217656Sgerardnico     *
44c3437056SNickeau     */
45*b0217656Sgerardnico    public static function dispatchEvent(int $maxEvent = 10)
46c3437056SNickeau    {
47c3437056SNickeau
4804fd306cSNickeau        try {
49c3437056SNickeau            $sqlite = Sqlite::createOrGetBackendSqlite();
5004fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
5104fd306cSNickeau            LogUtility::error("Sqlite is mandatory for asynchronous event", self::CANONICAL, $e);
52c3437056SNickeau            return;
53c3437056SNickeau        }
54c3437056SNickeau
5570bbd7f1Sgerardnico
56c3437056SNickeau        $rows = [];
57*b0217656Sgerardnico        /**
58*b0217656Sgerardnico         * Returning clause
59*b0217656Sgerardnico         * does not work
60*b0217656Sgerardnico         */
61*b0217656Sgerardnico        $version = $sqlite->getVersion();
62*b0217656Sgerardnico        if ($version > "3.35.0") {
63*b0217656Sgerardnico
64*b0217656Sgerardnico            // returning clause is available since 3.35 on delete
65*b0217656Sgerardnico            // https://www.sqlite.org/lang_returning.html
66*b0217656Sgerardnico
67*b0217656Sgerardnico            $eventTableName = self::EVENT_TABLE_NAME;
68*b0217656Sgerardnico            $statement = "delete from {$eventTableName} returning *";
69*b0217656Sgerardnico            // https://www.sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses
70*b0217656Sgerardnico            if ($sqlite->hasOption("SQLITE_ENABLE_UPDATE_DELETE_LIMIT")) {
71*b0217656Sgerardnico                $statement .= "order by timestamp limit {$maxEvent}";
72*b0217656Sgerardnico            }
73*b0217656Sgerardnico            $request = $sqlite->createRequest()
74*b0217656Sgerardnico                ->setStatement($statement);
75*b0217656Sgerardnico            try {
76*b0217656Sgerardnico                $rows = $request->execute()
77*b0217656Sgerardnico                    ->getRows();
78*b0217656Sgerardnico                if (sizeof($rows) === 0) {
79*b0217656Sgerardnico                    return;
80*b0217656Sgerardnico                }
81*b0217656Sgerardnico            } catch (ExceptionCompile $e) {
82*b0217656Sgerardnico                LogUtility::error($e->getMessage(), $e->getCanonical(), $e);
83*b0217656Sgerardnico            } finally {
84*b0217656Sgerardnico                $request->close();
85*b0217656Sgerardnico            }
86*b0217656Sgerardnico
87*b0217656Sgerardnico        }
88c3437056SNickeau
89c3437056SNickeau        /**
90c3437056SNickeau         * Error in the block before or not the good version
91c3437056SNickeau         * We try to get the records with a select/delete
92c3437056SNickeau         */
93c3437056SNickeau        if (sizeof($rows) === 0) {
94c3437056SNickeau
95c3437056SNickeau
96c3437056SNickeau            // technically the lock system of dokuwiki does not allow two process to run on
97c3437056SNickeau            // the indexer, we trust it
98c3437056SNickeau            $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID];
99c3437056SNickeau            $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes);
100*b0217656Sgerardnico            $select .= " order by " . self::TIMESTAMP_ATTRIBUTE . " limit {$maxEvent}";
101c3437056SNickeau            $request = $sqlite->createRequest()
102c3437056SNickeau                ->setQuery($select);
103c3437056SNickeau
104c3437056SNickeau            $rowsSelected = [];
105c3437056SNickeau            try {
106c3437056SNickeau                $rowsSelected = $request->execute()
107c3437056SNickeau                    ->getRows();
108c3437056SNickeau                if (sizeof($rowsSelected) === 0) {
109c3437056SNickeau                    return;
110c3437056SNickeau                }
11104fd306cSNickeau            } catch (ExceptionCompile $e) {
112c3437056SNickeau                LogUtility::msg("Error while retrieving the event {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical());
113c3437056SNickeau                return;
114c3437056SNickeau            } finally {
115c3437056SNickeau                $request->close();
116c3437056SNickeau            }
117c3437056SNickeau
118c3437056SNickeau            $eventTableName = self::EVENT_TABLE_NAME;
119c3437056SNickeau            $rows = [];
120c3437056SNickeau            foreach ($rowsSelected as $row) {
121c3437056SNickeau                $request = $sqlite->createRequest()
122c3437056SNickeau                    ->setQueryParametrized("delete from $eventTableName where rowid = ? ", [$row[DatabasePageRow::ROWID]]);
123c3437056SNickeau                try {
124*b0217656Sgerardnico                    $changeCount = $request->execute()->getChangeCount();
125c3437056SNickeau                    if ($changeCount !== 1) {
126c3437056SNickeau                        LogUtility::msg("The delete of the event was not successful or it was deleted by another process", LogUtility::LVL_MSG_ERROR);
127c3437056SNickeau                    } else {
128c3437056SNickeau                        $rows[] = $row;
129c3437056SNickeau                    }
13004fd306cSNickeau                } catch (ExceptionCompile $e) {
131c3437056SNickeau                    LogUtility::msg("Error while deleting the event. Message {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical());
132c3437056SNickeau                    return;
133c3437056SNickeau                } finally {
134c3437056SNickeau                    $request->close();
135c3437056SNickeau                }
136c3437056SNickeau            }
137c3437056SNickeau
138c3437056SNickeau
139c3437056SNickeau        }
140c3437056SNickeau
141c3437056SNickeau
142c3437056SNickeau        $eventCounter = 0;
143c3437056SNickeau        foreach ($rows as $row) {
144c3437056SNickeau            $eventCounter++;
145c3437056SNickeau            $eventName = $row[self::EVENT_NAME_ATTRIBUTE];
146c3437056SNickeau            $eventData = [];
147c3437056SNickeau            $eventDataJson = $row[self::EVENT_DATA_ATTRIBUTE];
148c3437056SNickeau            if ($eventDataJson !== null) {
149c3437056SNickeau                try {
150c3437056SNickeau                    $eventData = Json::createFromString($eventDataJson)->toArray();
15104fd306cSNickeau                } catch (ExceptionCompile $e) {
152c3437056SNickeau                    LogUtility::msg("The stored data for the event $eventName was not in the json format");
153c3437056SNickeau                    continue;
154c3437056SNickeau                }
155c3437056SNickeau            }
156c3437056SNickeau            \dokuwiki\Extension\Event::createAndTrigger($eventName, $eventData);
157c3437056SNickeau
158c3437056SNickeau            if ($eventCounter >= $maxEvent) {
159c3437056SNickeau                break;
160c3437056SNickeau            }
161c3437056SNickeau
162c3437056SNickeau        }
163c3437056SNickeau
164c3437056SNickeau    }
165c3437056SNickeau
166c3437056SNickeau    /**
167c3437056SNickeau     * Ask a replication in the background
168c3437056SNickeau     * @param string $name - a string with the reason
169c3437056SNickeau     * @param array $data
170c3437056SNickeau     */
171c3437056SNickeau    public static
172c3437056SNickeau    function createEvent(string $name, array $data)
173c3437056SNickeau    {
174c3437056SNickeau
17504fd306cSNickeau        try {
176c3437056SNickeau            $sqlite = Sqlite::createOrGetBackendSqlite();
17704fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
17804fd306cSNickeau            LogUtility::error("Unable to create the event $name. Sqlite is not available");
179c3437056SNickeau            return;
180c3437056SNickeau        }
181c3437056SNickeau
182c3437056SNickeau        /**
183c3437056SNickeau         * If not present
184c3437056SNickeau         */
185c3437056SNickeau        $entry = array(
186c3437056SNickeau            "name" => $name,
187c3437056SNickeau            "timestamp" => Iso8601Date::createFromNow()->toString()
188c3437056SNickeau        );
189c3437056SNickeau
19004fd306cSNickeau
191c3437056SNickeau        $entry["data"] = Json::createFromArray($data)->toPrettyJsonString();
19204fd306cSNickeau        $entry["data_hash"] = md5($entry["data"]);
193c3437056SNickeau
194c3437056SNickeau        /**
195c3437056SNickeau         * Execute
196c3437056SNickeau         */
197c3437056SNickeau        $request = $sqlite->createRequest()
198c3437056SNickeau            ->setTableRow(self::EVENT_TABLE_NAME, $entry);
199c3437056SNickeau        try {
200c3437056SNickeau            $request->execute();
20104fd306cSNickeau        } catch (ExceptionCompile $e) {
20204fd306cSNickeau            LogUtility::error("Unable to create the event $name. Error:" . $e->getMessage(), self::CANONICAL, $e);
203c3437056SNickeau        } finally {
204c3437056SNickeau            $request->close();
205c3437056SNickeau        }
206c3437056SNickeau
207c3437056SNickeau
208c3437056SNickeau    }
209c3437056SNickeau
210c3437056SNickeau    /**
211c3437056SNickeau     * @param $pageId
212c3437056SNickeau     *
213c3437056SNickeau     * This is equivalent to {@link TaskRunner}
214c3437056SNickeau     *
215c3437056SNickeau     * lib/exe/taskrunner.php?id='.rawurlencode($ID)
216c3437056SNickeau     * $taskRunner = new \dokuwiki\TaskRunner();
217c3437056SNickeau     * $taskRunner->run();
218c3437056SNickeau     *
219c3437056SNickeau     */
220c3437056SNickeau    public static function startTaskRunnerForPage($pageId)
221c3437056SNickeau    {
222c3437056SNickeau        $tmp = []; // No event data
223c3437056SNickeau        $tmp['page'] = $pageId;
224c3437056SNickeau        $evt = new \dokuwiki\Extension\Event('INDEXER_TASKS_RUN', $tmp);
22504fd306cSNickeau        $evt->advise_before();
226c3437056SNickeau        $evt->advise_after();
227c3437056SNickeau    }
228c3437056SNickeau
2294cadd4f8SNickeau
23004fd306cSNickeau    public static function getQueue(string $eventName = null): array
23104fd306cSNickeau    {
23204fd306cSNickeau        try {
23304fd306cSNickeau            $sqlite = Sqlite::createOrGetBackendSqlite();
23404fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
23504fd306cSNickeau            LogUtility::internalError("Sqlite is not available, no events was returned", self::CANONICAL);
23604fd306cSNickeau            return [];
23704fd306cSNickeau        }
2384cadd4f8SNickeau
2394cadd4f8SNickeau        /**
2404cadd4f8SNickeau         * Execute
2414cadd4f8SNickeau         */
2424cadd4f8SNickeau        $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID];
2434cadd4f8SNickeau        $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes);
24404fd306cSNickeau        $request = $sqlite->createRequest();
24504fd306cSNickeau        if (empty($eventName)) {
24604fd306cSNickeau            $request->setQuery($select);
24704fd306cSNickeau        } else {
24804fd306cSNickeau            $request->setQueryParametrized($select . " where " . self::EVENT_NAME_ATTRIBUTE . " = ?", [$eventName]);
24904fd306cSNickeau        }
2504cadd4f8SNickeau        try {
2514cadd4f8SNickeau            return $request->execute()
2524cadd4f8SNickeau                ->getRows();
25304fd306cSNickeau        } catch (ExceptionCompile $e) {
25404fd306cSNickeau            LogUtility::internalError("Unable to get the queue. Error:" . $e->getMessage(), self::CANONICAL, $e);
25504fd306cSNickeau            return [];
2564cadd4f8SNickeau        } finally {
2574cadd4f8SNickeau            $request->close();
2584cadd4f8SNickeau        }
2594cadd4f8SNickeau
2604cadd4f8SNickeau    }
2614cadd4f8SNickeau
2624cadd4f8SNickeau    /**
26304fd306cSNickeau     * @throws ExceptionCompile
2644cadd4f8SNickeau     */
2654cadd4f8SNickeau    public static function purgeQueue(): int
2664cadd4f8SNickeau    {
2674cadd4f8SNickeau        $sqlite = Sqlite::createOrGetBackendSqlite();
2684cadd4f8SNickeau        if ($sqlite === null) {
26904fd306cSNickeau            throw new ExceptionCompile("Sqlite is not available");
270c3437056SNickeau        }
271c3437056SNickeau
272c3437056SNickeau
273c3437056SNickeau        /**
274c3437056SNickeau         * Execute
275c3437056SNickeau         */
27604fd306cSNickeau        /** @noinspection SqlWithoutWhere */
277c3437056SNickeau        $request = $sqlite->createRequest()
2784cadd4f8SNickeau            ->setQuery("delete from " . self::EVENT_TABLE_NAME);
279c3437056SNickeau        try {
2804cadd4f8SNickeau            return $request->execute()
2814cadd4f8SNickeau                ->getChangeCount();
28204fd306cSNickeau        } catch (ExceptionCompile $e) {
28304fd306cSNickeau            throw new ExceptionCompile("Unable to count the number of event in the queue. Error:" . $e->getMessage(), self::CANONICAL, 0, $e);
284c3437056SNickeau        } finally {
285c3437056SNickeau            $request->close();
286c3437056SNickeau        }
287c3437056SNickeau    }
288c3437056SNickeau
28904fd306cSNickeau    /**
29004fd306cSNickeau     * @throws ExceptionCompile
29104fd306cSNickeau     */
29204fd306cSNickeau    public static function getEvents(string $eventName): array
29304fd306cSNickeau    {
29404fd306cSNickeau        return Event::getQueue($eventName);
29504fd306cSNickeau    }
29604fd306cSNickeau
297c3437056SNickeau
298c3437056SNickeau}
299