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