1<?php 2 3use dokuwiki\Extension\ActionPlugin; 4use dokuwiki\Extension\EventHandler; 5use dokuwiki\Extension\Event; 6use dokuwiki\plugin\struct\meta\SchemaImporter; 7use dokuwiki\plugin\sqlite\SQLiteDB; 8use dokuwiki\plugin\sqlite\Tools; 9 10class action_plugin_structpublish_migration extends ActionPlugin 11{ 12 public const MIN_DB_STRUCT = 19; 13 14 /** @var string */ 15 protected $table = 'data_structpublish'; 16 17 /** 18 * @inheritDoc 19 */ 20 public function register(EventHandler $controller) 21 { 22 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handleMigrations'); 23 } 24 25 /** 26 * Call our custom migrations. We do not use our own database, 27 * so we cannot use the mechanism in sqlite init() 28 * which processes updateXXXX.sql files 29 * 30 * @param Event $event 31 * @return bool 32 * @throws Exception 33 */ 34 public function handleMigrations(Event $event) 35 { 36 /** @var \helper_plugin_struct_db $helper */ 37 $helper = plugin_load('helper', 'struct_db'); 38 39 // abort if struct is not installed 40 if (!$helper) { 41 throw new Exception('Plugin struct is required!'); 42 } 43 44 $sqlite = $helper->getDB(); 45 46 [$dbVersionStruct, $dbVersionStructpublish] = $this->getDbVersions($sqlite); 47 48 // check if struct has required version 49 if ($dbVersionStruct < self::MIN_DB_STRUCT) { 50 throw new Exception( 51 'Plugin struct is outdated. Minimum required database version is ' . self::MIN_DB_STRUCT 52 ); 53 } 54 55 // check whether we are already up-to-date 56 $latestVersion = $this->getLatestVersion(); 57 if (isset($dbVersionStructpublish) && (int) $dbVersionStructpublish >= $latestVersion) { 58 return true; 59 } 60 61 // check whether we have any pending migrations 62 $pending = range(($dbVersionStructpublish ?: 0) + 1, $latestVersion); 63 if ($pending === []) { 64 return true; 65 } 66 67 // execute the migrations 68 $ok = true; 69 70 foreach ($pending as $version) { 71 $call = 'migration' . $version; 72 $ok = $ok && $this->$call($sqlite); 73 } 74 75 // update migration status in struct database 76 if ($ok) { 77 $sql = "REPLACE INTO opts (val,opt) VALUES ($version,'dbversion_structpublish')"; 78 $ok = $ok && $sqlite->query($sql); 79 } 80 81 return $ok; 82 } 83 84 /** 85 * Read the current versions for struct and struct publish from the database 86 * 87 * @param SQLiteDB $sqlite 88 * @return array [structversion, structpublishversion] 89 */ 90 protected function getDbVersions($sqlite) 91 { 92 $dbVersionStruct = null; 93 $dbVersionStructpublish = null; 94 95 $sql = 'SELECT opt, val FROM opts WHERE opt=? OR opt=?'; 96 $vals = $sqlite->queryAll($sql, ['dbversion', 'dbversion_structpublish']); 97 98 foreach ($vals as $val) { 99 if ($val['opt'] === 'dbversion') { 100 $dbVersionStruct = $val['val']; 101 } 102 if ($val['opt'] === 'dbversion_structpublish') { 103 $dbVersionStructpublish = $val['val']; 104 } 105 } 106 return [$dbVersionStruct, $dbVersionStructpublish]; 107 } 108 109 /** 110 * @return int 111 */ 112 protected function getLatestVersion() 113 { 114 return (int) trim(file_get_contents(DOKU_PLUGIN . 'structpublish/db/latest.version', false)); 115 } 116 117 /** 118 * Database setup 119 * 120 * @param SQLiteDB $sqlite 121 * @return bool 122 */ 123 protected function migration1($sqlite) 124 { 125 $file = DOKU_PLUGIN . 'structpublish/db/json/structpublish0001.struct.json'; 126 $schemaJson = file_get_contents($file); 127 $importer = new SchemaImporter('structpublish', $schemaJson); 128 $ok = (bool) $importer->build(); 129 130 if ($ok) { 131 $sql = io_readFile(DOKU_PLUGIN . 'structpublish/db/update0001.sql', false); 132 $sqlArr = Tools::SQLstring2array($sql); 133 foreach ($sqlArr as $sql) { 134 $ok = $ok && $sqlite->query($sql); 135 } 136 } 137 138 return $ok; 139 } 140 141 /** 142 * Reset 'latest' flag to 0 for all rows except actually latest ones 143 * for each pid / status combination. 144 * 145 * @param SQLiteDB $sqlite 146 * @return bool 147 */ 148 protected function migration2($sqlite) 149 { 150 $sql = "SELECT rid, pid, latest, col1, max(col4) FROM $this->table GROUP BY pid, col1"; 151 $latest = $sqlite->queryAll($sql); 152 $rids = array_column($latest, 'rid'); 153 154 $sql = "UPDATE $this->table SET latest = 0 WHERE rid NOT IN (" . implode(', ', $rids) . ')'; 155 156 return (bool) $sqlite->query($sql); 157 } 158 159 /** 160 * Set 'latest' flag to 0 for all rows except actually latest ones 161 * for each page, 162 * 163 * @param SQLiteDB $sqlite 164 * @return bool 165 */ 166 protected function migration3($sqlite) 167 { 168 $sql = "WITH cte AS ( 169 SELECT rid, pid, col1 AS status, col4 as rev, 170 rank() OVER ( PARTITION BY pid 171 ORDER BY col4 DESC, col1 = 'draft', col1 = 'approved', col1 = 'published' 172 ) AS r 173 FROM data_structpublish 174 ) 175 SELECT rid, pid, status, rev 176 FROM cte 177 WHERE r = 1 178 ORDER BY pid ASC;"; 179 180 $latest = $sqlite->queryAll($sql); 181 $rids = array_column($latest, 'rid'); 182 183 $sql = "UPDATE $this->table SET latest = 0 WHERE rid NOT IN (" . implode(', ', $rids) . ')'; 184 185 return (bool) $sqlite->query($sql); 186 } 187} 188