Elgg  Version 3.0
AlterDatabaseToMultiByteCharset.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Upgrades;
4 
7 
12 
13  private $utf8mb4_tables = [
14  // InnoDB
15  'access_collection_membership',
16  'access_collections',
17  'annotations',
18  'api_users',
19  'config',
20  'entities',
21  'entity_relationships',
22  'metadata',
23  'private_settings',
24  'queue',
25  'river',
26  'system_log',
27  'users_remember_me_cookies',
28  'users_sessions',
29  // MEMORY
30  'hmac_cache',
31  'users_apisessions',
32  ];
33 
34  // Columns with utf8 encoding and utf8_general_ci collation
35  // $table => [
36  // $column => $index
37  // ]
38 
39  private $non_mb4_columns = [
40  'config' => [
41  'name' => [
42  'primary' => true,
43  'name' => 'name',
44  'unique' => false,
45  ],
46  ],
47  'entities' => [
48  'subtype' => [
49  'primary' => false,
50  'name' => 'subtype',
51  'unique' => false,
52  ],
53  ],
54  'queue' => [
55  'name' => [
56  'primary' => false,
57  'name' => "name",
58  'unique' => false,
59  ],
60  ],
61  'users_sessions' => [
62  'session' => [
63  'primary' => true,
64  'name' => 'session',
65  'unique' => false,
66  ],
67  ],
68  'hmac_cache' => [
69  'hmac' => [
70  'primary' => true,
71  'name' => 'hmac',
72  'unique' => false,
73  ],
74  ],
75  'system_log' => [
76  'object_class' => [
77  'primary' => false,
78  'name' => 'object_class',
79  'unique' => false,
80  ],
81  'object_type' => [
82  'primary' => false,
83  'name' => 'object_type',
84  'unique' => false,
85  ],
86  'object_subtype' => [
87  'primary' => false,
88  'name' => 'object_subtype',
89  'unique' => false,
90  ],
91  'event' => [
92  'primary' => false,
93  'name' => 'event',
94  'unique' => false,
95  ],
96  'river_key' => [
97  'primary' => false,
98  'name' => 'river_key',
99  'unique' => false,
100  'columns' => ['object_type', 'object_subtype', 'event']
101  ],
102  ]
103  ];
104 
108  public function getVersion() {
109  return 2017080900;
110  }
111 
115  public function needsIncrementOffset() {
116  return false;
117  }
118 
122  public function shouldBeSkipped() {
123 
124  $config = _elgg_services()->dbConfig->getConnectionConfig();
125  $rows = elgg()->db->getData("SHOW TABLE STATUS FROM `{$config['database']}`");
126 
127  $prefixed_table_names = array_map(function ($t) use ($config) {
128  return "{$config['prefix']}{$t}";
129  }, $this->utf8mb4_tables);
130 
131  foreach ($rows as $row) {
132  if (in_array($row->Name, $prefixed_table_names) && $row->Collation !== 'utf8mb4_general_ci') {
133  return false;
134  }
135  }
136 
137  return true;
138  }
139 
143  public function countItems() {
144  return 1;
145  }
146 
150  public function run(Result $result, $offset) {
151 
152  $config = _elgg_services()->dbConfig->getConnectionConfig();
153 
154  try {
155  // check if we need to change a global variable
156  $row = elgg()->db->getDataRow("SHOW GLOBAL VARIABLES LIKE 'innodb_large_prefix'");
157 
158  if (empty($row) || $row->Value === 'OFF') {
159  // required to allow bigger index sizes required for utf8mb4
160  elgg()->db->updateData("SET GLOBAL innodb_large_prefix = 'ON'");
161  }
162  } catch (\Exception $e) {
163  // something went wrong, maybe database permissions, or version
164  $result->addFailures();
165  $result->addError("Failure to set 'innodb_large_prefix'. Ask your database administrator for more information.");
166  $result->addError("Alternatively ask the database administrator to (temporarily) set 'innodb_large_prefix' to 'ON'.");
167  $result->addError($e->getMessage());
168 
169  return $result;
170  }
171 
172  try {
173  // alter table structure
174  elgg()->db->updateData("
175  ALTER DATABASE
176  `{$config['database']}`
177  CHARACTER SET = utf8mb4
178  COLLATE = utf8mb4_unicode_ci
179  ");
180 
181  foreach ($this->utf8mb4_tables as $table) {
182  if (!empty($this->non_mb4_columns[$table])) {
183  foreach ($this->non_mb4_columns[$table] as $column => $index) {
184  if ($index) {
185  if ($index['primary']) {
186  elgg()->db->updateData("
187  ALTER TABLE {$config['prefix']}{$table}
188  DROP PRIMARY KEY
189  ");
190  } else {
191  elgg()->db->updateData("
192  ALTER TABLE {$config['prefix']}{$table}
193  DROP KEY {$index['name']}
194  ");
195  }
196  }
197  }
198  }
199 
200  elgg()->db->updateData("
201  ALTER TABLE {$config['prefix']}{$table}
202  ROW_FORMAT=DYNAMIC
203  ");
204 
205  elgg()->db->updateData("
206  ALTER TABLE {$config['prefix']}{$table}
207  CONVERT TO CHARACTER SET utf8mb4
208  COLLATE utf8mb4_general_ci
209  ");
210 
211  if (!empty($this->non_mb4_columns[$table])) {
212  foreach ($this->non_mb4_columns[$table] as $column => $index) {
213  if (empty($index['columns'])) {
214  // Alter table only if the key is not composite
215  elgg()->db->updateData("
216  ALTER TABLE {$config['prefix']}{$table}
217  MODIFY $column VARCHAR(255)
218  CHARACTER SET utf8
219  COLLATE utf8_unicode_ci
220  ");
221  }
222 
223  if (!$index) {
224  continue;
225  }
226 
227  $sql = "ADD";
228  if ($index['unique']) {
229  $sql .= " UNIQUE ({$index['name']})";
230  } else if ($index['primary']) {
231  $sql .= " PRIMARY KEY ({$index['name']})";
232  } else {
233  $key_columns = elgg_extract('columns', $index, [$column]);
234  $key_columns = implode(',', $key_columns);
235  $sql .= " KEY {$index['name']} ($key_columns)";
236  }
237 
238  elgg()->db->updateData("
239  ALTER TABLE {$config['prefix']}{$table}
240  $sql
241  ");
242  }
243  }
244  }
245  } catch (\Exception $e) {
246  $result->addFailures();
247  $result->addError($e->getMessage());
248 
249  return $result;
250  }
251 
252  $result->addSuccesses();
253 
254  return $result;
255  }
256 }
Interface to be implement for asynchronous upgrades, i.e.
getVersion()
{Version of the upgrade.This tells the date when the upgrade was added. It consists of eight digits a...
$rows
Definition: redis.php:20
countItems()
{The total number of items to process during the upgrade.If unknown, Batch::UNKNOWN_COUNT should be r...
shouldBeSkipped()
{Should this upgrade be skipped?If true, the upgrade will not be performed and cannot be accessed lat...
$column
Definition: add.php:10
if(!$count) $offset
Definition: pagination.php:26
addSuccesses($num=1)
Set an item (or items) as successfully upgraded.
Definition: Result.php:68
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
addFailures($num=1)
Increment failure count.
Definition: Result.php:49
addError($message)
Add new error message to the batch.
Definition: Result.php:24
Result of a single BatchUpgrade run.
Definition: Result.php:8
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:1131
_elgg_services()
Get the global service provider.
Definition: elgglib.php:1292
$table
Definition: cron.php:57
$index
Definition: gallery.php:47
needsIncrementOffset()
{Should the run() method receive an offset representing all processed items?If true, run() will receive as $offset the number of items already processed. This is useful if you are only modifying data, and need to use the $offset in a function like elgg_get_entities*() to know how many to skip over.If false, run() will receive as $offset the total number of failures. This should be used if your process deletes or moves data out of the way of the process. E.g. if you delete 50 objects on each run(), you may still use the $offset to skip objects that already failed once.bool}
var elgg
Definition: elgglib.js:4
run(Result $result, $offset)
{Runs upgrade on a single batch of items.If countItems() returns Batch::UNKNOWN_COUNT, this method must call $result->markCompleted() when the upgrade is complete.Result of the batch (this must be returned) Number to skip when processingResult}