xref: /plugin/combo/ComboStrap/Event.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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";
22c3437056SNickeau    const EVENT_NAME_ATTRIBUTE = "name";
23c3437056SNickeau    const EVENT_DATA_ATTRIBUTE = "data";
24c3437056SNickeau
25c3437056SNickeau    /**
26c3437056SNickeau     * process all replication request, created with {@link Event::createEvent()}
27c3437056SNickeau     *
28c3437056SNickeau     * by default, there is 5 pages in a default dokuwiki installation in the wiki namespace)
29c3437056SNickeau     */
30c3437056SNickeau    public static function dispatchEvent($maxEvent = 10)
31c3437056SNickeau    {
32c3437056SNickeau
33*04fd306cSNickeau        try {
34c3437056SNickeau            $sqlite = Sqlite::createOrGetBackendSqlite();
35*04fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
36*04fd306cSNickeau            LogUtility::error("Sqlite is mandatory for asynchronous event", self::CANONICAL, $e);
37c3437056SNickeau            return;
38c3437056SNickeau        }
39c3437056SNickeau
40c3437056SNickeau        /**
41c3437056SNickeau         * In case of a start or if there is a recursive bug
42c3437056SNickeau         * We don't want to take all the resources
43c3437056SNickeau         */
44c3437056SNickeau        $maxBackgroundEventLow = 10;
45c3437056SNickeau
46c3437056SNickeau        $version = $sqlite->getVersion();
47c3437056SNickeau        $rows = [];
48*04fd306cSNickeau        /**
49*04fd306cSNickeau         * Returning clause
50*04fd306cSNickeau         */
51*04fd306cSNickeau        $isCi = PluginUtility::isCi(); // Sqlite plugin seems to not support the returning clause - it returns a number as result set in CI
52*04fd306cSNickeau        $isDev = PluginUtility::isDevOrTest(); // may not work for normal users
53*04fd306cSNickeau        if ($version > "3.35.0" && $isDev && !$isCi) {
54c3437056SNickeau
55c3437056SNickeau            // returning clause is available since 3.35 on delete
56c3437056SNickeau            // https://www.sqlite.org/lang_returning.html
57c3437056SNickeau
58c3437056SNickeau            $eventTableName = self::EVENT_TABLE_NAME;
59c3437056SNickeau            $statement = "delete from {$eventTableName} returning *";
60c3437056SNickeau            // https://www.sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses
61c3437056SNickeau            if ($sqlite->hasOption("SQLITE_ENABLE_UPDATE_DELETE_LIMIT")) {
62c3437056SNickeau                $statement .= "order by timestamp limit {$maxBackgroundEventLow}";
63c3437056SNickeau            }
64c3437056SNickeau            $request = $sqlite->createRequest()
65c3437056SNickeau                ->setStatement($statement);
66c3437056SNickeau            try {
67c3437056SNickeau                $rows = $request->execute()
68c3437056SNickeau                    ->getRows();
69c3437056SNickeau                if (sizeof($rows) === 0) {
70c3437056SNickeau                    return;
71c3437056SNickeau                }
72*04fd306cSNickeau            } catch (ExceptionCompile $e) {
73*04fd306cSNickeau                LogUtility::error($e->getMessage(), $e->getCanonical(), $e);
74c3437056SNickeau            } finally {
75c3437056SNickeau                $request->close();
76c3437056SNickeau            }
77c3437056SNickeau
78c3437056SNickeau        }
79c3437056SNickeau
80c3437056SNickeau        /**
81c3437056SNickeau         * Error in the block before or not the good version
82c3437056SNickeau         * We try to get the records with a select/delete
83c3437056SNickeau         */
84c3437056SNickeau        if (sizeof($rows) === 0) {
85c3437056SNickeau
86c3437056SNickeau
87c3437056SNickeau            // technically the lock system of dokuwiki does not allow two process to run on
88c3437056SNickeau            // the indexer, we trust it
89c3437056SNickeau            $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID];
90c3437056SNickeau            $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes);
91c3437056SNickeau            $request = $sqlite->createRequest()
92c3437056SNickeau                ->setQuery($select);
93c3437056SNickeau
94c3437056SNickeau            $rowsSelected = [];
95c3437056SNickeau            try {
96c3437056SNickeau                $rowsSelected = $request->execute()
97c3437056SNickeau                    ->getRows();
98c3437056SNickeau                if (sizeof($rowsSelected) === 0) {
99c3437056SNickeau                    return;
100c3437056SNickeau                }
101*04fd306cSNickeau            } catch (ExceptionCompile $e) {
102c3437056SNickeau                LogUtility::msg("Error while retrieving the event {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical());
103c3437056SNickeau                return;
104c3437056SNickeau            } finally {
105c3437056SNickeau                $request->close();
106c3437056SNickeau            }
107c3437056SNickeau
108c3437056SNickeau            $eventTableName = self::EVENT_TABLE_NAME;
109c3437056SNickeau            $rows = [];
110c3437056SNickeau            foreach ($rowsSelected as $row) {
111c3437056SNickeau                $request = $sqlite->createRequest()
112c3437056SNickeau                    ->setQueryParametrized("delete from $eventTableName where rowid = ? ", [$row[DatabasePageRow::ROWID]]);
113c3437056SNickeau                try {
114c3437056SNickeau                    $changeCount = $request->execute()
115c3437056SNickeau                        ->getChangeCount();
116c3437056SNickeau                    if ($changeCount !== 1) {
117c3437056SNickeau                        LogUtility::msg("The delete of the event was not successful or it was deleted by another process", LogUtility::LVL_MSG_ERROR);
118c3437056SNickeau                    } else {
119c3437056SNickeau                        $rows[] = $row;
120c3437056SNickeau                    }
121*04fd306cSNickeau                } catch (ExceptionCompile $e) {
122c3437056SNickeau                    LogUtility::msg("Error while deleting the event. Message {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical());
123c3437056SNickeau                    return;
124c3437056SNickeau                } finally {
125c3437056SNickeau                    $request->close();
126c3437056SNickeau                }
127c3437056SNickeau            }
128c3437056SNickeau
129c3437056SNickeau
130c3437056SNickeau        }
131c3437056SNickeau
132c3437056SNickeau
133c3437056SNickeau        $eventCounter = 0;
134c3437056SNickeau        foreach ($rows as $row) {
135c3437056SNickeau            $eventCounter++;
136c3437056SNickeau            $eventName = $row[self::EVENT_NAME_ATTRIBUTE];
137c3437056SNickeau            $eventData = [];
138c3437056SNickeau            $eventDataJson = $row[self::EVENT_DATA_ATTRIBUTE];
139c3437056SNickeau            if ($eventDataJson !== null) {
140c3437056SNickeau                try {
141c3437056SNickeau                    $eventData = Json::createFromString($eventDataJson)->toArray();
142*04fd306cSNickeau                } catch (ExceptionCompile $e) {
143c3437056SNickeau                    LogUtility::msg("The stored data for the event $eventName was not in the json format");
144c3437056SNickeau                    continue;
145c3437056SNickeau                }
146c3437056SNickeau            }
147c3437056SNickeau            \dokuwiki\Extension\Event::createAndTrigger($eventName, $eventData);
148c3437056SNickeau
149c3437056SNickeau            if ($eventCounter >= $maxEvent) {
150c3437056SNickeau                break;
151c3437056SNickeau            }
152c3437056SNickeau
153c3437056SNickeau        }
154c3437056SNickeau
155c3437056SNickeau    }
156c3437056SNickeau
157c3437056SNickeau    /**
158c3437056SNickeau     * Ask a replication in the background
159c3437056SNickeau     * @param string $name - a string with the reason
160c3437056SNickeau     * @param array $data
161c3437056SNickeau     */
162c3437056SNickeau    public static
163c3437056SNickeau    function createEvent(string $name, array $data)
164c3437056SNickeau    {
165c3437056SNickeau
166*04fd306cSNickeau        try {
167c3437056SNickeau            $sqlite = Sqlite::createOrGetBackendSqlite();
168*04fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
169*04fd306cSNickeau            LogUtility::error("Unable to create the event $name. Sqlite is not available");
170c3437056SNickeau            return;
171c3437056SNickeau        }
172c3437056SNickeau
173c3437056SNickeau        /**
174c3437056SNickeau         * If not present
175c3437056SNickeau         */
176c3437056SNickeau        $entry = array(
177c3437056SNickeau            "name" => $name,
178c3437056SNickeau            "timestamp" => Iso8601Date::createFromNow()->toString()
179c3437056SNickeau        );
180c3437056SNickeau
181*04fd306cSNickeau
182c3437056SNickeau        $entry["data"] = Json::createFromArray($data)->toPrettyJsonString();
183*04fd306cSNickeau        $entry["data_hash"] = md5($entry["data"]);
184c3437056SNickeau
185c3437056SNickeau        /**
186c3437056SNickeau         * Execute
187c3437056SNickeau         */
188c3437056SNickeau        $request = $sqlite->createRequest()
189c3437056SNickeau            ->setTableRow(self::EVENT_TABLE_NAME, $entry);
190c3437056SNickeau        try {
191c3437056SNickeau            $request->execute();
192*04fd306cSNickeau        } catch (ExceptionCompile $e) {
193*04fd306cSNickeau            LogUtility::error("Unable to create the event $name. Error:" . $e->getMessage(), self::CANONICAL, $e);
194c3437056SNickeau        } finally {
195c3437056SNickeau            $request->close();
196c3437056SNickeau        }
197c3437056SNickeau
198c3437056SNickeau
199c3437056SNickeau    }
200c3437056SNickeau
201c3437056SNickeau    /**
202c3437056SNickeau     * @param $pageId
203c3437056SNickeau     *
204c3437056SNickeau     * This is equivalent to {@link TaskRunner}
205c3437056SNickeau     *
206c3437056SNickeau     * lib/exe/taskrunner.php?id='.rawurlencode($ID)
207c3437056SNickeau     * $taskRunner = new \dokuwiki\TaskRunner();
208c3437056SNickeau     * $taskRunner->run();
209c3437056SNickeau     *
210c3437056SNickeau     */
211c3437056SNickeau    public static function startTaskRunnerForPage($pageId)
212c3437056SNickeau    {
213c3437056SNickeau        $tmp = []; // No event data
214c3437056SNickeau        $tmp['page'] = $pageId;
215c3437056SNickeau        $evt = new \dokuwiki\Extension\Event('INDEXER_TASKS_RUN', $tmp);
216*04fd306cSNickeau        $evt->advise_before();
217c3437056SNickeau        $evt->advise_after();
218c3437056SNickeau    }
219c3437056SNickeau
2204cadd4f8SNickeau
221*04fd306cSNickeau    public static function getQueue(string $eventName = null): array
222*04fd306cSNickeau    {
223*04fd306cSNickeau        try {
224*04fd306cSNickeau            $sqlite = Sqlite::createOrGetBackendSqlite();
225*04fd306cSNickeau        } catch (ExceptionSqliteNotAvailable $e) {
226*04fd306cSNickeau            LogUtility::internalError("Sqlite is not available, no events was returned", self::CANONICAL);
227*04fd306cSNickeau            return [];
228*04fd306cSNickeau        }
2294cadd4f8SNickeau
2304cadd4f8SNickeau        /**
2314cadd4f8SNickeau         * Execute
2324cadd4f8SNickeau         */
2334cadd4f8SNickeau        $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID];
2344cadd4f8SNickeau        $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes);
235*04fd306cSNickeau        $request = $sqlite->createRequest();
236*04fd306cSNickeau        if (empty($eventName)) {
237*04fd306cSNickeau            $request->setQuery($select);
238*04fd306cSNickeau        } else {
239*04fd306cSNickeau            $request->setQueryParametrized($select . " where " . self::EVENT_NAME_ATTRIBUTE . " = ?", [$eventName]);
240*04fd306cSNickeau        }
2414cadd4f8SNickeau        try {
2424cadd4f8SNickeau            return $request->execute()
2434cadd4f8SNickeau                ->getRows();
244*04fd306cSNickeau        } catch (ExceptionCompile $e) {
245*04fd306cSNickeau            LogUtility::internalError("Unable to get the queue. Error:" . $e->getMessage(), self::CANONICAL, $e);
246*04fd306cSNickeau            return [];
2474cadd4f8SNickeau        } finally {
2484cadd4f8SNickeau            $request->close();
2494cadd4f8SNickeau        }
2504cadd4f8SNickeau
2514cadd4f8SNickeau    }
2524cadd4f8SNickeau
2534cadd4f8SNickeau    /**
254*04fd306cSNickeau     * @throws ExceptionCompile
2554cadd4f8SNickeau     */
2564cadd4f8SNickeau    public static function purgeQueue(): int
2574cadd4f8SNickeau    {
2584cadd4f8SNickeau        $sqlite = Sqlite::createOrGetBackendSqlite();
2594cadd4f8SNickeau        if ($sqlite === null) {
260*04fd306cSNickeau            throw new ExceptionCompile("Sqlite is not available");
261c3437056SNickeau        }
262c3437056SNickeau
263c3437056SNickeau
264c3437056SNickeau        /**
265c3437056SNickeau         * Execute
266c3437056SNickeau         */
267*04fd306cSNickeau        /** @noinspection SqlWithoutWhere */
268c3437056SNickeau        $request = $sqlite->createRequest()
2694cadd4f8SNickeau            ->setQuery("delete from " . self::EVENT_TABLE_NAME);
270c3437056SNickeau        try {
2714cadd4f8SNickeau            return $request->execute()
2724cadd4f8SNickeau                ->getChangeCount();
273*04fd306cSNickeau        } catch (ExceptionCompile $e) {
274*04fd306cSNickeau            throw new ExceptionCompile("Unable to count the number of event in the queue. Error:" . $e->getMessage(), self::CANONICAL, 0, $e);
275c3437056SNickeau        } finally {
276c3437056SNickeau            $request->close();
277c3437056SNickeau        }
278c3437056SNickeau    }
279c3437056SNickeau
280*04fd306cSNickeau    /**
281*04fd306cSNickeau     * @throws ExceptionCompile
282*04fd306cSNickeau     */
283*04fd306cSNickeau    public static function getEvents(string $eventName): array
284*04fd306cSNickeau    {
285*04fd306cSNickeau        return Event::getQueue($eventName);
286*04fd306cSNickeau    }
287*04fd306cSNickeau
288c3437056SNickeau
289c3437056SNickeau}
290