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 3304fd306cSNickeau try { 34c3437056SNickeau $sqlite = Sqlite::createOrGetBackendSqlite(); 3504fd306cSNickeau } catch (ExceptionSqliteNotAvailable $e) { 3604fd306cSNickeau 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 46*70bbd7f1Sgerardnico 47c3437056SNickeau $rows = []; 48*70bbd7f1Sgerardnico// /** 49*70bbd7f1Sgerardnico// * Returning clause 50*70bbd7f1Sgerardnico// * does not work 51*70bbd7f1Sgerardnico// */ 52*70bbd7f1Sgerardnico// $version = $sqlite->getVersion(); 53*70bbd7f1Sgerardnico// $isCi = PluginUtility::isCi(); // Sqlite plugin seems to not support the returning clause - it returns a number as result set in CI 54*70bbd7f1Sgerardnico// $isDev = PluginUtility::isDevOrTest(); // may not work for normal users 55*70bbd7f1Sgerardnico// if ($version > "3.35.0" && $isDev && !$isCi) { 56*70bbd7f1Sgerardnico// 57*70bbd7f1Sgerardnico// // returning clause is available since 3.35 on delete 58*70bbd7f1Sgerardnico// // https://www.sqlite.org/lang_returning.html 59*70bbd7f1Sgerardnico// 60*70bbd7f1Sgerardnico// $eventTableName = self::EVENT_TABLE_NAME; 61*70bbd7f1Sgerardnico// $statement = "delete from {$eventTableName} returning *"; 62*70bbd7f1Sgerardnico// // https://www.sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses 63*70bbd7f1Sgerardnico// if ($sqlite->hasOption("SQLITE_ENABLE_UPDATE_DELETE_LIMIT")) { 64*70bbd7f1Sgerardnico// $statement .= "order by timestamp limit {$maxBackgroundEventLow}"; 65*70bbd7f1Sgerardnico// } 66*70bbd7f1Sgerardnico// $request = $sqlite->createRequest() 67*70bbd7f1Sgerardnico// ->setStatement($statement); 68*70bbd7f1Sgerardnico// try { 69*70bbd7f1Sgerardnico// $rows = $request->execute() 70*70bbd7f1Sgerardnico// ->getRows(); 71*70bbd7f1Sgerardnico// if (sizeof($rows) === 0) { 72*70bbd7f1Sgerardnico// return; 73*70bbd7f1Sgerardnico// } 74*70bbd7f1Sgerardnico// } catch (ExceptionCompile $e) { 75*70bbd7f1Sgerardnico// LogUtility::error($e->getMessage(), $e->getCanonical(), $e); 76*70bbd7f1Sgerardnico// } finally { 77*70bbd7f1Sgerardnico// $request->close(); 78*70bbd7f1Sgerardnico// } 79*70bbd7f1Sgerardnico// 80*70bbd7f1Sgerardnico// } 81c3437056SNickeau 82c3437056SNickeau /** 83c3437056SNickeau * Error in the block before or not the good version 84c3437056SNickeau * We try to get the records with a select/delete 85c3437056SNickeau */ 86c3437056SNickeau if (sizeof($rows) === 0) { 87c3437056SNickeau 88c3437056SNickeau 89c3437056SNickeau // technically the lock system of dokuwiki does not allow two process to run on 90c3437056SNickeau // the indexer, we trust it 91c3437056SNickeau $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID]; 92c3437056SNickeau $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes); 93c3437056SNickeau $request = $sqlite->createRequest() 94c3437056SNickeau ->setQuery($select); 95c3437056SNickeau 96c3437056SNickeau $rowsSelected = []; 97c3437056SNickeau try { 98c3437056SNickeau $rowsSelected = $request->execute() 99c3437056SNickeau ->getRows(); 100c3437056SNickeau if (sizeof($rowsSelected) === 0) { 101c3437056SNickeau return; 102c3437056SNickeau } 10304fd306cSNickeau } catch (ExceptionCompile $e) { 104c3437056SNickeau LogUtility::msg("Error while retrieving the event {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical()); 105c3437056SNickeau return; 106c3437056SNickeau } finally { 107c3437056SNickeau $request->close(); 108c3437056SNickeau } 109c3437056SNickeau 110c3437056SNickeau $eventTableName = self::EVENT_TABLE_NAME; 111c3437056SNickeau $rows = []; 112c3437056SNickeau foreach ($rowsSelected as $row) { 113c3437056SNickeau $request = $sqlite->createRequest() 114c3437056SNickeau ->setQueryParametrized("delete from $eventTableName where rowid = ? ", [$row[DatabasePageRow::ROWID]]); 115c3437056SNickeau try { 116c3437056SNickeau $changeCount = $request->execute() 117c3437056SNickeau ->getChangeCount(); 118c3437056SNickeau if ($changeCount !== 1) { 119c3437056SNickeau LogUtility::msg("The delete of the event was not successful or it was deleted by another process", LogUtility::LVL_MSG_ERROR); 120c3437056SNickeau } else { 121c3437056SNickeau $rows[] = $row; 122c3437056SNickeau } 12304fd306cSNickeau } catch (ExceptionCompile $e) { 124c3437056SNickeau LogUtility::msg("Error while deleting the event. Message {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, $e->getCanonical()); 125c3437056SNickeau return; 126c3437056SNickeau } finally { 127c3437056SNickeau $request->close(); 128c3437056SNickeau } 129c3437056SNickeau } 130c3437056SNickeau 131c3437056SNickeau 132c3437056SNickeau } 133c3437056SNickeau 134c3437056SNickeau 135c3437056SNickeau $eventCounter = 0; 136c3437056SNickeau foreach ($rows as $row) { 137c3437056SNickeau $eventCounter++; 138c3437056SNickeau $eventName = $row[self::EVENT_NAME_ATTRIBUTE]; 139c3437056SNickeau $eventData = []; 140c3437056SNickeau $eventDataJson = $row[self::EVENT_DATA_ATTRIBUTE]; 141c3437056SNickeau if ($eventDataJson !== null) { 142c3437056SNickeau try { 143c3437056SNickeau $eventData = Json::createFromString($eventDataJson)->toArray(); 14404fd306cSNickeau } catch (ExceptionCompile $e) { 145c3437056SNickeau LogUtility::msg("The stored data for the event $eventName was not in the json format"); 146c3437056SNickeau continue; 147c3437056SNickeau } 148c3437056SNickeau } 149c3437056SNickeau \dokuwiki\Extension\Event::createAndTrigger($eventName, $eventData); 150c3437056SNickeau 151c3437056SNickeau if ($eventCounter >= $maxEvent) { 152c3437056SNickeau break; 153c3437056SNickeau } 154c3437056SNickeau 155c3437056SNickeau } 156c3437056SNickeau 157c3437056SNickeau } 158c3437056SNickeau 159c3437056SNickeau /** 160c3437056SNickeau * Ask a replication in the background 161c3437056SNickeau * @param string $name - a string with the reason 162c3437056SNickeau * @param array $data 163c3437056SNickeau */ 164c3437056SNickeau public static 165c3437056SNickeau function createEvent(string $name, array $data) 166c3437056SNickeau { 167c3437056SNickeau 16804fd306cSNickeau try { 169c3437056SNickeau $sqlite = Sqlite::createOrGetBackendSqlite(); 17004fd306cSNickeau } catch (ExceptionSqliteNotAvailable $e) { 17104fd306cSNickeau LogUtility::error("Unable to create the event $name. Sqlite is not available"); 172c3437056SNickeau return; 173c3437056SNickeau } 174c3437056SNickeau 175c3437056SNickeau /** 176c3437056SNickeau * If not present 177c3437056SNickeau */ 178c3437056SNickeau $entry = array( 179c3437056SNickeau "name" => $name, 180c3437056SNickeau "timestamp" => Iso8601Date::createFromNow()->toString() 181c3437056SNickeau ); 182c3437056SNickeau 18304fd306cSNickeau 184c3437056SNickeau $entry["data"] = Json::createFromArray($data)->toPrettyJsonString(); 18504fd306cSNickeau $entry["data_hash"] = md5($entry["data"]); 186c3437056SNickeau 187c3437056SNickeau /** 188c3437056SNickeau * Execute 189c3437056SNickeau */ 190c3437056SNickeau $request = $sqlite->createRequest() 191c3437056SNickeau ->setTableRow(self::EVENT_TABLE_NAME, $entry); 192c3437056SNickeau try { 193c3437056SNickeau $request->execute(); 19404fd306cSNickeau } catch (ExceptionCompile $e) { 19504fd306cSNickeau LogUtility::error("Unable to create the event $name. Error:" . $e->getMessage(), self::CANONICAL, $e); 196c3437056SNickeau } finally { 197c3437056SNickeau $request->close(); 198c3437056SNickeau } 199c3437056SNickeau 200c3437056SNickeau 201c3437056SNickeau } 202c3437056SNickeau 203c3437056SNickeau /** 204c3437056SNickeau * @param $pageId 205c3437056SNickeau * 206c3437056SNickeau * This is equivalent to {@link TaskRunner} 207c3437056SNickeau * 208c3437056SNickeau * lib/exe/taskrunner.php?id='.rawurlencode($ID) 209c3437056SNickeau * $taskRunner = new \dokuwiki\TaskRunner(); 210c3437056SNickeau * $taskRunner->run(); 211c3437056SNickeau * 212c3437056SNickeau */ 213c3437056SNickeau public static function startTaskRunnerForPage($pageId) 214c3437056SNickeau { 215c3437056SNickeau $tmp = []; // No event data 216c3437056SNickeau $tmp['page'] = $pageId; 217c3437056SNickeau $evt = new \dokuwiki\Extension\Event('INDEXER_TASKS_RUN', $tmp); 21804fd306cSNickeau $evt->advise_before(); 219c3437056SNickeau $evt->advise_after(); 220c3437056SNickeau } 221c3437056SNickeau 2224cadd4f8SNickeau 22304fd306cSNickeau public static function getQueue(string $eventName = null): array 22404fd306cSNickeau { 22504fd306cSNickeau try { 22604fd306cSNickeau $sqlite = Sqlite::createOrGetBackendSqlite(); 22704fd306cSNickeau } catch (ExceptionSqliteNotAvailable $e) { 22804fd306cSNickeau LogUtility::internalError("Sqlite is not available, no events was returned", self::CANONICAL); 22904fd306cSNickeau return []; 23004fd306cSNickeau } 2314cadd4f8SNickeau 2324cadd4f8SNickeau /** 2334cadd4f8SNickeau * Execute 2344cadd4f8SNickeau */ 2354cadd4f8SNickeau $attributes = [self::EVENT_NAME_ATTRIBUTE, self::EVENT_DATA_ATTRIBUTE, DatabasePageRow::ROWID]; 2364cadd4f8SNickeau $select = Sqlite::createSelectFromTableAndColumns(self::EVENT_TABLE_NAME, $attributes); 23704fd306cSNickeau $request = $sqlite->createRequest(); 23804fd306cSNickeau if (empty($eventName)) { 23904fd306cSNickeau $request->setQuery($select); 24004fd306cSNickeau } else { 24104fd306cSNickeau $request->setQueryParametrized($select . " where " . self::EVENT_NAME_ATTRIBUTE . " = ?", [$eventName]); 24204fd306cSNickeau } 2434cadd4f8SNickeau try { 2444cadd4f8SNickeau return $request->execute() 2454cadd4f8SNickeau ->getRows(); 24604fd306cSNickeau } catch (ExceptionCompile $e) { 24704fd306cSNickeau LogUtility::internalError("Unable to get the queue. Error:" . $e->getMessage(), self::CANONICAL, $e); 24804fd306cSNickeau return []; 2494cadd4f8SNickeau } finally { 2504cadd4f8SNickeau $request->close(); 2514cadd4f8SNickeau } 2524cadd4f8SNickeau 2534cadd4f8SNickeau } 2544cadd4f8SNickeau 2554cadd4f8SNickeau /** 25604fd306cSNickeau * @throws ExceptionCompile 2574cadd4f8SNickeau */ 2584cadd4f8SNickeau public static function purgeQueue(): int 2594cadd4f8SNickeau { 2604cadd4f8SNickeau $sqlite = Sqlite::createOrGetBackendSqlite(); 2614cadd4f8SNickeau if ($sqlite === null) { 26204fd306cSNickeau throw new ExceptionCompile("Sqlite is not available"); 263c3437056SNickeau } 264c3437056SNickeau 265c3437056SNickeau 266c3437056SNickeau /** 267c3437056SNickeau * Execute 268c3437056SNickeau */ 26904fd306cSNickeau /** @noinspection SqlWithoutWhere */ 270c3437056SNickeau $request = $sqlite->createRequest() 2714cadd4f8SNickeau ->setQuery("delete from " . self::EVENT_TABLE_NAME); 272c3437056SNickeau try { 2734cadd4f8SNickeau return $request->execute() 2744cadd4f8SNickeau ->getChangeCount(); 27504fd306cSNickeau } catch (ExceptionCompile $e) { 27604fd306cSNickeau throw new ExceptionCompile("Unable to count the number of event in the queue. Error:" . $e->getMessage(), self::CANONICAL, 0, $e); 277c3437056SNickeau } finally { 278c3437056SNickeau $request->close(); 279c3437056SNickeau } 280c3437056SNickeau } 281c3437056SNickeau 28204fd306cSNickeau /** 28304fd306cSNickeau * @throws ExceptionCompile 28404fd306cSNickeau */ 28504fd306cSNickeau public static function getEvents(string $eventName): array 28604fd306cSNickeau { 28704fd306cSNickeau return Event::getQueue($eventName); 28804fd306cSNickeau } 28904fd306cSNickeau 290c3437056SNickeau 291c3437056SNickeau} 292