Install   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 424
Duplicated Lines 0 %

Test Coverage

Coverage 3.33%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 131
c 2
b 0
f 0
dl 0
loc 424
ccs 4
cts 120
cp 0.0333
rs 10
wmc 21

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A check_version() 0 13 4
A install() 0 6 1
A create_tables() 0 85 1
A convert_user_meta() 0 87 4
A add_foreign_keys() 0 83 3
B add_foreign_key() 0 78 7
1
<?php
2
/**
3
 * Mollie install.
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2022 Pronamic
7
 * @license   GPL-3.0-or-later
8
 * @package   Pronamic\WordPress\Pay
9
 */
10
11
namespace Pronamic\WordPress\Pay\Gateways\Mollie;
12
13
/**
14
 * Title: Mollie install.
15
 * Description:
16
 * Copyright: 2005-2022 Pronamic
17
 * Company: Pronamic
18
 *
19
 * @author  Remco Tolsma
20
 * @version 2.1.0
21
 * @since   2.1.0
22
 */
23
class Install {
24
	/**
25
	 * Integration.
26
	 *
27
	 * @var Integration
28
	 */
29
	private $integration;
30
31
	/**
32
	 * Construct install.
33
	 *
34
	 * @link https://github.com/woocommerce/woocommerce/blob/4.0.0/includes/class-woocommerce.php#L368
35
	 * @link https://github.com/woocommerce/woocommerce/blob/4.0.0/includes/class-wc-install.php#L1568
36
	 * @link https://github.com/woocommerce/woocommerce/blob/4.0.0/includes/class-wc-install.php#L153-L166
37
	 * @param Integration $integration Integration.
38
	 */
39 7
	public function __construct( Integration $integration ) {
40 7
		$this->integration = $integration;
41
42 7
		add_action( 'init', array( $this, 'check_version' ), 5 );
43 7
	}
44
45
	/**
46
	 * Check version.
47
	 *
48
	 * @link https://github.com/woocommerce/woocommerce/blob/4.0.0/includes/class-wc-install.php#L168-L178
49
	 * @return void
50
	 */
51
	public function check_version() {
52
		$version_option = $this->integration->get_version_option();
53
		$version        = $this->integration->get_version();
54
55
		if ( null === $version_option || null === $version ) {
56
			return;
57
		}
58
59
		$version_option = \strval( $version_option );
60
		$version        = \strval( $version );
61
62
		if ( version_compare( $version_option, $version, '<' ) ) {
63
			$this->install();
64
		}
65
	}
66
67
	/**
68
	 * Install.
69
	 *
70
	 * @link https://github.com/woocommerce/woocommerce/blob/4.0.0/includes/class-wc-install.php#L272-L306
71
	 * @return void
72
	 */
73
	public function install() {
74
		$this->create_tables();
75
		$this->add_foreign_keys();
76
		$this->convert_user_meta();
77
78
		$this->integration->update_version_option();
79
	}
80
81
	/**
82
	 * Create tables.
83
	 *
84
	 * @link https://github.com/woocommerce/woocommerce/blob/4.0.0/includes/class-wc-install.php#L630-L720
85
	 * @return void
86
	 */
87
	private function create_tables() {
88
		global $wpdb;
89
90
		/**
91
		 * Requirements.
92
		 */
93
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
94
95
		/**
96
		 * Table options.
97
		 *
98
		 * In MySQL 5.6, InnoDB is the default MySQL storage engine. Unless you
99
		 * have configured a different default storage engine,  issuing a
100
		 * CREATE TABLE statement without an ENGINE= clause creates an InnoDB
101
		 * table.
102
		 *
103
		 * @link https://dev.mysql.com/doc/refman/5.6/en/innodb-introduction.html
104
		 *
105
		 * If a storage engine is specified that is not available, MySQL uses
106
		 * the default engine instead. Normally, this is MyISAM. For example,
107
		 * if a table definition includes the ENGINE=INNODB option but the MySQL
108
		 * server does not support INNODB tables, the table is created as a
109
		 * MyISAM table.
110
		 *
111
		 * @link https://dev.mysql.com/doc/refman/5.6/en/create-table.html
112
		 */
113
		$table_options = 'ENGINE=InnoDB ' . $wpdb->get_charset_collate();
114
115
		/**
116
		 * Queries.
117
		 *
118
		 * @link https://github.com/WordPress/WordPress/blob/5.3/wp-admin/includes/schema.php
119
		 */
120
		$queries = "
121
			CREATE TABLE $wpdb->pronamic_pay_mollie_organizations (
122
				id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
123
				mollie_id varchar(16) NOT NULL,
124
				name varchar(128) DEFAULT NULL,
125
				email varchar(100) DEFAULT NULL,
126
				PRIMARY KEY  ( id ),
127
				UNIQUE KEY mollie_id ( mollie_id )
128
			) $table_options;
129
			CREATE TABLE $wpdb->pronamic_pay_mollie_profiles (
130
				id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
131
				mollie_id varchar(16) NOT NULL,
132
				organization_id bigint(20) unsigned DEFAULT NULL,
133
				name varchar(128) DEFAULT NULL,
134
				email varchar(100) DEFAULT NULL,
135
				api_key_test varchar(35) DEFAULT NULL,
136
				api_key_live varchar(35) DEFAULT NULL,
137
				PRIMARY KEY  ( id ),
138
				UNIQUE KEY mollie_id ( mollie_id ),
139
				KEY organization_id ( organization_id )
140
			) $table_options;
141
			CREATE TABLE $wpdb->pronamic_pay_mollie_customers (
142
				id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
143
				mollie_id varchar(16) NOT NULL,
144
				organization_id bigint(20) unsigned DEFAULT NULL,
145
				profile_id bigint(20) unsigned DEFAULT NULL,
146
				test_mode tinyint(1) NOT NULL,
147
				email varchar(100) DEFAULT NULL,
148
				name varchar(255) DEFAULT NULL,
149
				PRIMARY KEY  ( id ),
150
				UNIQUE KEY mollie_id ( mollie_id ),
151
				KEY organization_id ( organization_id ),
152
				KEY profile_id ( profile_id ),
153
				KEY test_mode ( test_mode ),
154
				KEY email ( email )
155
			) $table_options;
156
			CREATE TABLE $wpdb->pronamic_pay_mollie_customer_users (
157
				id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
158
				customer_id bigint(20) unsigned NOT NULL,
159
				user_id bigint(20) unsigned NOT NULL,
160
				PRIMARY KEY  ( id ),
161
				UNIQUE KEY customer_user ( customer_id, user_id )
162
			) $table_options;
163
		";
164
165
		/**
166
		 * Execute.
167
		 *
168
		 * @link https://developer.wordpress.org/reference/functions/dbdelta/
169
		 * @link https://github.com/WordPress/WordPress/blob/5.3/wp-admin/includes/upgrade.php#L2538-L2915
170
		 */
171
		\dbDelta( $queries );
172
	}
173
174
	/**
175
	 * Add foreign keys.
176
	 *
177
	 * @return void
178
	 */
179
	private function add_foreign_keys() {
180
		global $wpdb;
181
182
		/**
183
		 * Foreign keys.
184
		 *
185
		 * @link https://core.trac.wordpress.org/ticket/19207
186
		 * @link https://dev.mysql.com/doc/refman/5.6/en/create-table-foreign-keys.html
187
		 */
188
		$data = array(
189
			(object) array(
190
				'table' => $wpdb->pronamic_pay_mollie_profiles,
191
				'name'  => 'fk_profile_organization_id',
192
				'query' => "
193
					ALTER TABLE  $wpdb->pronamic_pay_mollie_profiles
194
					ADD CONSTRAINT fk_profile_organization_id
195
					FOREIGN KEY ( organization_id )
196
					REFERENCES $wpdb->pronamic_pay_mollie_organizations ( id )
197
					ON DELETE RESTRICT
198
					ON UPDATE RESTRICT
199
					;
200
				",
201
			),
202
			(object) array(
203
				'table' => $wpdb->pronamic_pay_mollie_customers,
204
				'name'  => 'fk_customer_organization_id',
205
				'query' => "
206
					ALTER TABLE $wpdb->pronamic_pay_mollie_customers
207
					ADD CONSTRAINT fk_customer_organization_id
208
					FOREIGN KEY ( organization_id )
209
					REFERENCES $wpdb->pronamic_pay_mollie_organizations ( id )
210
					ON DELETE RESTRICT
211
					ON UPDATE RESTRICT
212
					;
213
				",
214
			),
215
			(object) array(
216
				'table' => $wpdb->pronamic_pay_mollie_customers,
217
				'name'  => 'fk_customer_profile_id',
218
				'query' => "
219
					ALTER TABLE $wpdb->pronamic_pay_mollie_customers
220
					ADD CONSTRAINT fk_customer_profile_id
221
					FOREIGN KEY ( profile_id )
222
					REFERENCES $wpdb->pronamic_pay_mollie_profiles ( id )
223
					ON DELETE RESTRICT
224
					ON UPDATE RESTRICT
225
					;
226
				",
227
			),
228
			(object) array(
229
				'table' => $wpdb->pronamic_pay_mollie_customer_users,
230
				'name'  => 'fk_customer_id',
231
				'query' => "
232
					ALTER TABLE $wpdb->pronamic_pay_mollie_customer_users
233
					ADD CONSTRAINT fk_customer_id
234
					FOREIGN KEY customer_id ( customer_id )
235
					REFERENCES $wpdb->pronamic_pay_mollie_customers ( id )
236
					ON DELETE RESTRICT
237
					ON UPDATE RESTRICT
238
					;
239
				",
240
			),
241
			(object) array(
242
				'table' => $wpdb->pronamic_pay_mollie_customer_users,
243
				'name'  => 'fk_customer_user_id',
244
				'query' => "
245
					ALTER TABLE $wpdb->pronamic_pay_mollie_customer_users
246
					ADD CONSTRAINT fk_customer_user_id
247
					FOREIGN KEY user_id ( user_id )
248
					REFERENCES $wpdb->users ( id )
249
					ON DELETE CASCADE
250
					ON UPDATE CASCADE
251
					;
252
				",
253
			),
254
		);
255
256
		foreach ( $data as $item ) {
257
			try {
258
				$this->add_foreign_key( $item );
259
			} catch ( \Exception $e ) {
260
				// Foreign keys are not strictly required.
261
				continue;
262
			}
263
		}
264
	}
265
266
	/**
267
	 * Add specified foreign key.
268
	 *
269
	 * @param object $item Foreign key data.
270
	 * @return void
271
	 * @throws \Exception Throws exception when adding foreign key fails.
272
	 * @throws \InvalidArgumentException Throws invalid argument exception if item misses required `table`, `name` or `query` property.
273
	 */
274
	private function add_foreign_key( $item ) {
275
		global $wpdb;
276
277
		if ( ! \property_exists( $item, 'table' ) ) {
278
			throw new \InvalidArgumentException( 'Foreign key item must contain `table` property.' );
279
		}
280
281
		if ( ! \property_exists( $item, 'name' ) ) {
282
			throw new \InvalidArgumentException( 'Foreign key item must contain `name` property.' );
283
		}
284
285
		if ( ! \property_exists( $item, 'query' ) ) {
286
			throw new \InvalidArgumentException( 'Foreign key item must contain `query` property.' );
287
		}
288
289
		/**
290
		 * Suppress errors.
291
		 *
292
		 * We suppress errors because adding foreign keys to for example
293
		 * a `$wpdb->users` MyISAM table will trigger the following error:
294
		 *
295
		 * "Error in query (1005): Can't create table '●●●●●●●●. # Sql-●●●●●●●●●●' (errno: 150)"
296
		 *
297
		 * @link https://github.com/WordPress/WordPress/blob/5.3/wp-includes/wp-db.php#L1544-L1559
298
		 */
299
		$suppress_errors = $wpdb->suppress_errors( true );
300
301
		/**
302
		 * Check if foreign key exists
303
		 *
304
		 * @link https://github.com/woocommerce/woocommerce/blob/3.9.0/includes/class-wc-install.php#L663-L681
305
		 */
306
		$result = $wpdb->get_var(
307
			$wpdb->prepare(
308
				"
309
			SELECT COUNT(*)
310
			FROM information_schema.TABLE_CONSTRAINTS
311
			WHERE CONSTRAINT_SCHEMA = %s
312
			AND CONSTRAINT_NAME = %s
313
			AND CONSTRAINT_TYPE = 'FOREIGN KEY'
314
			AND TABLE_NAME = %s
315
			",
316
				$wpdb->dbname,
317
				$item->name,
318
				$item->table
319
			)
320
		);
321
322
		if ( null === $result ) {
323
			throw new \Exception(
324
				\sprintf(
325
					'Could not count foreign keys: %s, database error: %s.',
326
					$item->name,
327
					$wpdb->last_error
328
				)
329
			);
330
		}
331
332
		$number_constraints = \intval( $result );
333
334
		if ( 0 === $number_constraints ) {
335
			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared.
336
			$result = $wpdb->query( $item->query );
337
338
			$wpdb->suppress_errors( $suppress_errors );
339
340
			if ( false === $result ) {
341
				throw new \Exception(
342
					\sprintf(
343
						'Could not add foreign key: %s, database error: %s.',
344
						$item->name,
345
						$wpdb->last_error
346
					)
347
				);
348
			}
349
		}
350
351
		$wpdb->suppress_errors( $suppress_errors );
352
	}
353
354
	/**
355
	 * Convert user meta.
356
	 *
357
	 * @return void
358
	 * @throws \Exception Throws exception when database update query fails.
359
	 */
360
	private function convert_user_meta() {
361
		global $wpdb;
362
363
		$query = "
364
			INSERT IGNORE INTO $wpdb->pronamic_pay_mollie_customers (
365
				mollie_id,
366
				test_mode
367
			)
368
			SELECT
369
				meta_value AS mollie_id,
370
				'_pronamic_pay_mollie_customer_id_test' = meta_key AS test_mode
371
			FROM
372
				$wpdb->usermeta
373
			WHERE
374
				meta_key IN (
375
					'_pronamic_pay_mollie_customer_id',
376
					'_pronamic_pay_mollie_customer_id_test'
377
				)
378
					AND
379
				meta_value != ''
380
			;
381
		";
382
383
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared.
384
		$result = $wpdb->query( $query );
385
386
		if ( false === $result ) {
387
			throw new \Exception(
388
				sprintf(
389
					'Could not convert user meta, database error: %s.',
390
					$wpdb->last_error
391
				)
392
			);
393
		}
394
395
		/**
396
		 * Collate caluse.
397
		 *
398
		 * Force a specific collate to fix:
399
		 * "Illegal mix of collations (utf8mb4_unicode_ci,IMPLICIT) and
400
		 * (utf8mb4_unicode_520_ci,IMPLICIT) for operation '='. ".
401
		 *
402
		 * @link https://dev.mysql.com/doc/refman/8.0/en/charset-collate.html
403
		 */
404
		$collate_clause = '';
405
406
		if ( ! empty( $wpdb->collate ) ) {
407
			$collate_clause = \sprintf(
408
				'COLLATE %s',
409
				$wpdb->collate
410
			);
411
		}
412
413
		$query = "
414
			INSERT IGNORE INTO $wpdb->pronamic_pay_mollie_customer_users (
415
				customer_id,
416
				user_id
417
			)
418
			SELECT
419
				mollie_customer.id AS mollie_customer_id,
420
				wp_user.ID AS wp_user_id
421
			FROM
422
				$wpdb->pronamic_pay_mollie_customers AS mollie_customer
423
					INNER JOIN
424
				$wpdb->usermeta AS wp_user_meta
425
						ON wp_user_meta.meta_value = mollie_customer.mollie_id $collate_clause
426
					INNER JOIN
427
				$wpdb->users AS wp_user
428
						ON wp_user_meta.user_id = wp_user.ID
429
			WHERE
430
				wp_user_meta.meta_key IN (
431
					'_pronamic_pay_mollie_customer_id',
432
					'_pronamic_pay_mollie_customer_id_test'
433
				)
434
					AND
435
				wp_user_meta.meta_value != ''
436
			;
437
		";
438
439
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared.
440
		$result = $wpdb->query( $query );
441
442
		if ( false === $result ) {
443
			throw new \Exception(
444
				sprintf(
445
					'Could not convert user meta, database error: %s.',
446
					$wpdb->last_error
447
				)
448
			);
449
		}
450
	}
451
}
452