Elgg  Version master
AlterDatabaseToMultiByteCharset.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Upgrades;
4 
8 
13 
14  private $utf8mb4_tables = [
15  // InnoDB
16  'access_collection_membership',
17  'access_collections',
18  'annotations',
19  'api_users',
20  'config',
21  'entities',
22  'entity_relationships',
23  'metadata',
24  'private_settings',
25  'queue',
26  'river',
27  'system_log',
28  'users_remember_me_cookies',
29  'users_sessions',
30  // MEMORY
31  'hmac_cache',
32  'users_apisessions',
33  ];
34 
35  // Columns with utf8 encoding and utf8_general_ci collation
36  // $table => [
37  // $column => $index
38  // ]
39 
40  private $non_mb4_columns = [
41  'config' => [
42  'name' => [
43  'primary' => true,
44  'name' => 'name',
45  'unique' => false,
46  ],
47  ],
48  'entities' => [
49  'subtype' => [
50  'primary' => false,
51  'name' => 'subtype',
52  'unique' => false,
53  ],
54  ],
55  'queue' => [
56  'name' => [
57  'primary' => false,
58  'name' => "name",
59  'unique' => false,
60  ],
61  ],
62  'users_sessions' => [
63  'session' => [
64  'primary' => true,
65  'name' => 'session',
66  'unique' => false,
67  ],
68  ],
69  'hmac_cache' => [
70  'hmac' => [
71  'primary' => true,
72  'name' => 'hmac',
73  'unique' => false,
74  ],
75  ],
76  'system_log' => [
77  'object_class' => [
78  'primary' => false,
79  'name' => 'object_class',
80  'unique' => false,
81  ],
82  'object_type' => [
83  'primary' => false,
84  'name' => 'object_type',
85  'unique' => false,
86  ],
87  'object_subtype' => [
88  'primary' => false,
89  'name' => 'object_subtype',
90  'unique' => false,
91  ],
92  'event' => [
93  'primary' => false,
94  'name' => 'event',
95  'unique' => false,
96  ],
97  'river_key' => [
98  'primary' => false,
99  'name' => 'river_key',
100  'unique' => false,
101  'columns' => ['object_type', 'object_subtype', 'event']
102  ],
103  ]
104  ];
105 
109  public function getVersion(): int {
110  return 2017080900;
111  }
112 
116  public function needsIncrementOffset(): bool {
117  return false;
118  }
119 
123  public function shouldBeSkipped(): bool {
124 
125  $config = _elgg_services()->dbConfig->getConnectionConfig();
126  $rows = _elgg_services()->db->getConnection(DbConfig::READ)->executeQuery("SHOW TABLE STATUS FROM `{$config['database']}`");
127 
128  $prefixed_table_names = array_map(function ($t) use ($config) {
129  return "{$config['prefix']}{$t}";
130  }, $this->utf8mb4_tables);
131 
132  foreach ($rows->fetchAllAssociative() as $row) {
133  $row = (object) $row;
134  if (in_array($row->Name, $prefixed_table_names) && $row->Collation !== 'utf8mb4_general_ci') {
135  return false;
136  }
137  }
138 
139  return true;
140  }
141 
145  public function countItems(): int {
146  return 1;
147  }
148 
152  public function run(Result $result, $offset): Result {
153 
154  $config = _elgg_services()->dbConfig->getConnectionConfig();
155 
156  try {
157  // check if we need to change a global variable
158  $db_result = _elgg_services()->db->getConnection(DbConfig::READ)->executeQuery("SHOW GLOBAL VARIABLES LIKE 'innodb_large_prefix'");
159  $rows = $db_result->fetchAllAssociative();
160 
161  if (empty($rows) || $rows[0]['Value'] === 'OFF') {
162  // required to allow bigger index sizes required for utf8mb4
163  _elgg_services()->db->getConnection(DbConfig::WRITE)->executeStatement("SET GLOBAL innodb_large_prefix = 'ON'");
164  }
165  } catch (\Exception $e) {
166  // something went wrong, maybe database permissions, or version
167  $result->addFailures();
168  $result->addError("Failure to set 'innodb_large_prefix'. Ask your database administrator for more information.");
169  $result->addError("Alternatively ask the database administrator to (temporarily) set 'innodb_large_prefix' to 'ON'.");
170  $result->addError($e->getMessage());
171 
172  return $result;
173  }
174 
175  try {
176  // alter table structure
177  $connection = _elgg_services()->db->getConnection(DbConfig::WRITE);
178  $connection->executeStatement("
179  ALTER DATABASE
180  `{$config['database']}`
181  CHARACTER SET = utf8mb4
182  COLLATE = utf8mb4_unicode_ci
183  ");
184 
185  foreach ($this->utf8mb4_tables as $table) {
186  if (!empty($this->non_mb4_columns[$table])) {
187  foreach ($this->non_mb4_columns[$table] as $column => $index) {
188  if ($index) {
189  if ($index['primary']) {
190  $connection->executeStatement("
191  ALTER TABLE {$config['prefix']}{$table}
192  DROP PRIMARY KEY
193  ");
194  } else {
195  $connection->executeStatement("
196  ALTER TABLE {$config['prefix']}{$table}
197  DROP KEY {$index['name']}
198  ");
199  }
200  }
201  }
202  }
203 
204  $connection->executeStatement("
205  ALTER TABLE {$config['prefix']}{$table}
206  ROW_FORMAT=DYNAMIC
207  ");
208 
209  $connection->executeStatement("
210  ALTER TABLE {$config['prefix']}{$table}
211  CONVERT TO CHARACTER SET utf8mb4
212  COLLATE utf8mb4_general_ci
213  ");
214 
215  if (!empty($this->non_mb4_columns[$table])) {
216  foreach ($this->non_mb4_columns[$table] as $column => $index) {
217  if (empty($index['columns'])) {
218  // Alter table only if the key is not composite
219  $connection->executeStatement("
220  ALTER TABLE {$config['prefix']}{$table}
221  MODIFY $column VARCHAR(255)
222  CHARACTER SET utf8
223  COLLATE utf8_unicode_ci
224  ");
225  }
226 
227  if (!$index) {
228  continue;
229  }
230 
231  $sql = "ADD";
232  if ($index['unique']) {
233  $sql .= " UNIQUE ({$index['name']})";
234  } else if ($index['primary']) {
235  $sql .= " PRIMARY KEY ({$index['name']})";
236  } else {
237  $key_columns = elgg_extract('columns', $index, [$column]);
238  $key_columns = implode(',', $key_columns);
239  $sql .= " KEY {$index['name']} ($key_columns)";
240  }
241 
242  $connection->executeStatement("
243  ALTER TABLE {$config['prefix']}{$table}
244  $sql
245  ");
246  }
247  }
248  }
249  } catch (\Exception $e) {
250  $result->addFailures();
251  $result->addError($e->getMessage());
252 
253  return $result;
254  }
255 
256  $result->addSuccesses();
257 
258  return $result;
259  }
260 }
$column
Definition: add.php:10
return[ 'admin/delete_admin_notices'=>['access'=> 'admin'], 'admin/menu/save'=>['access'=> 'admin'], 'admin/plugins/activate'=>['access'=> 'admin'], 'admin/plugins/activate_all'=>['access'=> 'admin'], 'admin/plugins/deactivate'=>['access'=> 'admin'], 'admin/plugins/deactivate_all'=>['access'=> 'admin'], 'admin/plugins/set_priority'=>['access'=> 'admin'], 'admin/security/security_txt'=>['access'=> 'admin'], 'admin/security/settings'=>['access'=> 'admin'], 'admin/security/regenerate_site_secret'=>['access'=> 'admin'], 'admin/site/cache/invalidate'=>['access'=> 'admin'], 'admin/site/flush_cache'=>['access'=> 'admin'], 'admin/site/icons'=>['access'=> 'admin'], 'admin/site/set_maintenance_mode'=>['access'=> 'admin'], 'admin/site/set_robots'=>['access'=> 'admin'], 'admin/site/theme'=>['access'=> 'admin'], 'admin/site/unlock_upgrade'=>['access'=> 'admin'], 'admin/site/settings'=>['access'=> 'admin'], 'admin/upgrade'=>['access'=> 'admin'], 'admin/upgrade/reset'=>['access'=> 'admin'], 'admin/user/ban'=>['access'=> 'admin'], 'admin/user/bulk/ban'=>['access'=> 'admin'], 'admin/user/bulk/delete'=>['access'=> 'admin'], 'admin/user/bulk/unban'=>['access'=> 'admin'], 'admin/user/bulk/validate'=>['access'=> 'admin'], 'admin/user/change_email'=>['access'=> 'admin'], 'admin/user/delete'=>['access'=> 'admin'], 'admin/user/login_as'=>['access'=> 'admin'], 'admin/user/logout_as'=>[], 'admin/user/makeadmin'=>['access'=> 'admin'], 'admin/user/resetpassword'=>['access'=> 'admin'], 'admin/user/removeadmin'=>['access'=> 'admin'], 'admin/user/unban'=>['access'=> 'admin'], 'admin/user/validate'=>['access'=> 'admin'], 'annotation/delete'=>[], 'avatar/upload'=>[], 'comment/save'=>[], 'diagnostics/download'=>['access'=> 'admin'], 'entity/chooserestoredestination'=>[], 'entity/delete'=>[], 'entity/mute'=>[], 'entity/restore'=>[], 'entity/subscribe'=>[], 'entity/trash'=>[], 'entity/unmute'=>[], 'entity/unsubscribe'=>[], 'login'=>['access'=> 'logged_out'], 'logout'=>[], 'notifications/mute'=>['access'=> 'public'], 'plugins/settings/remove'=>['access'=> 'admin'], 'plugins/settings/save'=>['access'=> 'admin'], 'plugins/usersettings/save'=>[], 'register'=>['access'=> 'logged_out', 'middleware'=>[\Elgg\Router\Middleware\RegistrationAllowedGatekeeper::class,],], 'river/delete'=>[], 'settings/notifications'=>[], 'settings/notifications/subscriptions'=>[], 'user/changepassword'=>['access'=> 'public'], 'user/requestnewpassword'=>['access'=> 'public'], 'useradd'=>['access'=> 'admin'], 'usersettings/save'=>[], 'widgets/add'=>[], 'widgets/delete'=>[], 'widgets/move'=>[], 'widgets/save'=>[],]
Definition: actions.php:73
Database configuration service.
Definition: DbConfig.php:13
Class to extend for asynchronous upgrades, i.e.
Result of a single BatchUpgrade run.
Definition: Result.php:10
shouldBeSkipped()
{Should this upgrade be skipped?If true, the upgrade will not be performed and cannot be accessed lat...
countItems()
{The total number of items to process during the upgrade.If unknown, Batch::UNKNOWN_COUNT should be r...
run(Result $result, $offset)
{Runs upgrade on a single batch of items.If countItems() returns Batch::UNKNOWN_COUNT,...
getVersion()
{Version of the upgrade.This tells the date when the upgrade was added. It consists of eight digits a...
needsIncrementOffset()
{Should the run() method receive an offset representing all processed items?If true,...
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
$table
Definition: database.php:52
$index
Definition: gallery.php:40
_elgg_services()
Get the global service provider.
Definition: elgglib.php:337
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:240
if(empty($count)) $offset
Definition: pagination.php:26
$rows
Definition: redis.php:25