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 33c3437056SNickeau $sqlite = Sqlite::createOrGetBackendSqlite(); 34c3437056SNickeau if ($sqlite === null) { 35c3437056SNickeau LogUtility::msg("Sqlite is mandatory for asynchronous event"); 36c3437056SNickeau return; 37c3437056SNickeau } 38c3437056SNickeau 39c3437056SNickeau /** 40c3437056SNickeau * In case of a start or if there is a recursive bug 41c3437056SNickeau * We don't want to take all the resources 42c3437056SNickeau */ 43c3437056SNickeau $maxBackgroundEventLow = 10; 44c3437056SNickeau 45c3437056SNickeau $version = $sqlite->getVersion(); 46c3437056SNickeau $rows = []; 47c3437056SNickeau if ($version > "3.35.0") { 48c3437056SNickeau 49c3437056SNickeau // returning clause is available since 3.35 on delete 50c3437056SNickeau // https://www.sqlite.org/lang_returning.html 51c3437056SNickeau 52c3437056SNickeau $eventTableName = self::EVENT_TABLE_NAME; 53c3437056SNickeau $statement = "delete from {$eventTableName} returning *"; 54c3437056SNickeau // https://www.sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses 55c3437056SNickeau if ($sqlite->hasOption("SQLITE_ENABLE_UPDATE_DELETE_LIMIT")) { 56c3437056SNickeau $statement .= "order by timestamp limit {$maxBackgroundEventLow}"; 57c3437056SNickeau } 58c3437056SNickeau $request = $sqlite->createRequest() 59c3437056SNickeau ->setStatement($statement); 60c3437056SNickeau try { 61c3437056SNickeau $rows = $request->execute() 62c3437056SNickeau ->getRows(); 63c3437056SNickeau if (sizeof($rows) === 0) { 64c3437056SNickeau return; 65c3437056SNickeau } 66c3437056SNickeau } catch (ExceptionCombo $e) { 67c3437056SNickeau LogUtility::msg($e->getMessage(), LogUtility::LVL_MSG_ERROR, $e->getCanonical()); 68c3437056SNickeau } finally { 69c3437056SNickeau $request->close(); 70c3437056SNickeau } 71c3437056SNickeau 72c3437056SNickeau } 73c3437056SNickeau 74c3437056SNickeau /** 75c3437056SNickeau * Error in the block before or not the good version 76c3437056SNickeau * We try to get the records with a select/delete 77c3437056SNickeau */ 78c3437056SNickeau if (sizeof($rows) === 0) { 79c3437056SNickeau 80c3437056SNickeau 81c3437056SNickeau // technically the lock system of dokuwiki does not allow two process to run on 82c3437056SNickeau // the indexer, we trust it 83c3437056SNickeau $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID]; 84c3437056SNickeau $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes); 85c3437056SNickeau $request = $sqlite->createRequest() 86c3437056SNickeau ->setQuery($select); 87c3437056SNickeau 88c3437056SNickeau $rowsSelected = []; 89c3437056SNickeau try { 90c3437056SNickeau $rowsSelected = $request->execute() 91c3437056SNickeau ->getRows(); 92c3437056SNickeau if (sizeof($rowsSelected) === 0) { 93c3437056SNickeau return; 94c3437056SNickeau } 95c3437056SNickeau } catch (ExceptionCombo $e) { 96c3437056SNickeau LogUtility::msg("Error while retrieving the event {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical()); 97c3437056SNickeau return; 98c3437056SNickeau } finally { 99c3437056SNickeau $request->close(); 100c3437056SNickeau } 101c3437056SNickeau 102c3437056SNickeau $eventTableName = self::EVENT_TABLE_NAME; 103c3437056SNickeau $rows = []; 104c3437056SNickeau foreach ($rowsSelected as $row) { 105c3437056SNickeau $request = $sqlite->createRequest() 106c3437056SNickeau ->setQueryParametrized("delete from $eventTableName where rowid = ? ", [$row[DatabasePageRow::ROWID]]); 107c3437056SNickeau try { 108c3437056SNickeau $changeCount = $request->execute() 109c3437056SNickeau ->getChangeCount(); 110c3437056SNickeau if ($changeCount !== 1) { 111c3437056SNickeau LogUtility::msg("The delete of the event was not successful or it was deleted by another process", LogUtility::LVL_MSG_ERROR); 112c3437056SNickeau } else { 113c3437056SNickeau $rows[] = $row; 114c3437056SNickeau } 115c3437056SNickeau } catch (ExceptionCombo $e) { 116c3437056SNickeau LogUtility::msg("Error while deleting the event. Message {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical()); 117c3437056SNickeau return; 118c3437056SNickeau } finally { 119c3437056SNickeau $request->close(); 120c3437056SNickeau } 121c3437056SNickeau } 122c3437056SNickeau 123c3437056SNickeau 124c3437056SNickeau } 125c3437056SNickeau 126c3437056SNickeau 127c3437056SNickeau $eventCounter = 0; 128c3437056SNickeau foreach ($rows as $row) { 129c3437056SNickeau $eventCounter++; 130c3437056SNickeau $eventName = $row[self::EVENT_NAME_ATTRIBUTE]; 131c3437056SNickeau $eventData = []; 132c3437056SNickeau $eventDataJson = $row[self::EVENT_DATA_ATTRIBUTE]; 133c3437056SNickeau if ($eventDataJson !== null) { 134c3437056SNickeau try { 135c3437056SNickeau $eventData = Json::createFromString($eventDataJson)->toArray(); 136c3437056SNickeau } catch (ExceptionCombo $e) { 137c3437056SNickeau LogUtility::msg("The stored data for the event $eventName was not in the json format"); 138c3437056SNickeau continue; 139c3437056SNickeau } 140c3437056SNickeau } 141c3437056SNickeau \dokuwiki\Extension\Event::createAndTrigger($eventName, $eventData); 142c3437056SNickeau 143c3437056SNickeau if ($eventCounter >= $maxEvent) { 144c3437056SNickeau break; 145c3437056SNickeau } 146c3437056SNickeau 147c3437056SNickeau } 148c3437056SNickeau 149c3437056SNickeau } 150c3437056SNickeau 151c3437056SNickeau /** 152c3437056SNickeau * Ask a replication in the background 153c3437056SNickeau * @param string $name - a string with the reason 154c3437056SNickeau * @param array $data 155c3437056SNickeau */ 156c3437056SNickeau public static 157c3437056SNickeau function createEvent(string $name, array $data) 158c3437056SNickeau { 159c3437056SNickeau 160c3437056SNickeau $sqlite = Sqlite::createOrGetBackendSqlite(); 161c3437056SNickeau if ($sqlite === null) { 162c3437056SNickeau LogUtility::msg("Unable to create the event $name. Sqlite is not available"); 163c3437056SNickeau return; 164c3437056SNickeau } 165c3437056SNickeau 166c3437056SNickeau 167c3437056SNickeau /** 168c3437056SNickeau * If not present 169c3437056SNickeau */ 170c3437056SNickeau $entry = array( 171c3437056SNickeau "name" => $name, 172c3437056SNickeau "timestamp" => Iso8601Date::createFromNow()->toString() 173c3437056SNickeau ); 174c3437056SNickeau 175c3437056SNickeau if ($data !== null) { 176c3437056SNickeau $entry["data"] = Json::createFromArray($data)->toPrettyJsonString(); 177c3437056SNickeau } 178c3437056SNickeau 179c3437056SNickeau /** 180c3437056SNickeau * Execute 181c3437056SNickeau */ 182c3437056SNickeau $request = $sqlite->createRequest() 183c3437056SNickeau ->setTableRow(self::EVENT_TABLE_NAME, $entry); 184c3437056SNickeau try { 185c3437056SNickeau $request->execute(); 186c3437056SNickeau } catch (ExceptionCombo $e) { 187c3437056SNickeau LogUtility::msg("Unable to create the event $name. Error:" . $e->getMessage(), LogUtility::LVL_MSG_ERROR, $e->getCanonical()); 188c3437056SNickeau } finally { 189c3437056SNickeau $request->close(); 190c3437056SNickeau } 191c3437056SNickeau 192c3437056SNickeau 193c3437056SNickeau } 194c3437056SNickeau 195c3437056SNickeau /** 196c3437056SNickeau * @param $pageId 197c3437056SNickeau * 198c3437056SNickeau * This is equivalent to {@link TaskRunner} 199c3437056SNickeau * 200c3437056SNickeau * lib/exe/taskrunner.php?id='.rawurlencode($ID) 201c3437056SNickeau * $taskRunner = new \dokuwiki\TaskRunner(); 202c3437056SNickeau * $taskRunner->run(); 203c3437056SNickeau * 204c3437056SNickeau */ 205c3437056SNickeau public static function startTaskRunnerForPage($pageId) 206c3437056SNickeau { 207c3437056SNickeau $tmp = []; // No event data 208c3437056SNickeau $tmp['page'] = $pageId; 209c3437056SNickeau $evt = new \dokuwiki\Extension\Event('INDEXER_TASKS_RUN', $tmp); 210c3437056SNickeau $evt->advise_after(); 211c3437056SNickeau } 212c3437056SNickeau 213*4cadd4f8SNickeau /** 214*4cadd4f8SNickeau * @throws ExceptionCombo 215*4cadd4f8SNickeau */ 216*4cadd4f8SNickeau public static function getQueue(): array 217c3437056SNickeau { 218c3437056SNickeau $sqlite = Sqlite::createOrGetBackendSqlite(); 219c3437056SNickeau if ($sqlite === null) { 220*4cadd4f8SNickeau throw new ExceptionCombo("Sqlite is not available"); 221*4cadd4f8SNickeau } 222*4cadd4f8SNickeau 223*4cadd4f8SNickeau 224*4cadd4f8SNickeau /** 225*4cadd4f8SNickeau * Execute 226*4cadd4f8SNickeau */ 227*4cadd4f8SNickeau $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID]; 228*4cadd4f8SNickeau $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes); 229*4cadd4f8SNickeau $request = $sqlite->createRequest() 230*4cadd4f8SNickeau ->setQuery($select); 231*4cadd4f8SNickeau try { 232*4cadd4f8SNickeau return $request->execute() 233*4cadd4f8SNickeau ->getRows(); 234*4cadd4f8SNickeau } catch (ExceptionCombo $e) { 235*4cadd4f8SNickeau throw new ExceptionCombo("Unable to get the queue. Error:" . $e->getMessage(),self::CANONICAL,0,$e); 236*4cadd4f8SNickeau } finally { 237*4cadd4f8SNickeau $request->close(); 238*4cadd4f8SNickeau } 239*4cadd4f8SNickeau 240*4cadd4f8SNickeau } 241*4cadd4f8SNickeau 242*4cadd4f8SNickeau /** 243*4cadd4f8SNickeau * @throws ExceptionCombo 244*4cadd4f8SNickeau */ 245*4cadd4f8SNickeau public static function purgeQueue(): int 246*4cadd4f8SNickeau { 247*4cadd4f8SNickeau $sqlite = Sqlite::createOrGetBackendSqlite(); 248*4cadd4f8SNickeau if ($sqlite === null) { 249*4cadd4f8SNickeau throw new ExceptionCombo("Sqlite is not available"); 250c3437056SNickeau } 251c3437056SNickeau 252c3437056SNickeau 253c3437056SNickeau /** 254c3437056SNickeau * Execute 255c3437056SNickeau */ 256c3437056SNickeau $request = $sqlite->createRequest() 257*4cadd4f8SNickeau ->setQuery("delete from " . self::EVENT_TABLE_NAME); 258c3437056SNickeau try { 259*4cadd4f8SNickeau return $request->execute() 260*4cadd4f8SNickeau ->getChangeCount(); 261c3437056SNickeau } catch (ExceptionCombo $e) { 262*4cadd4f8SNickeau throw new ExceptionCombo("Unable to count the number of event in the queue. Error:" . $e->getMessage(),self::CANONICAL,0,$e); 263c3437056SNickeau } finally { 264c3437056SNickeau $request->close(); 265c3437056SNickeau } 266c3437056SNickeau } 267c3437056SNickeau 268c3437056SNickeau 269c3437056SNickeau} 270