PluginProbe
Media Cloud Sync / trunk
Media Cloud Sync vtrunk
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / base / db.php

db.php in Media Cloud Sync trunk, at includes/base/db.php

435 lines 14.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Dudlewebs\WPMCS;
3
4 defined('ABSPATH') || exit;
5
6 class Db {
7 private static $instance = null;
8 private string $assets_url;
9 private string $version;
10 private string $token;
11
12
13 /**
14 * Admin constructor.
15 * @since 1.0.0
16 */
17 public function __construct() {
18 $this->assets_url = WPMCS_ASSETS_URL;
19 $this->version = WPMCS_VERSION;
20 $this->token = WPMCS_TOKEN;
21 }
22
23 /**
24 * Get table name with correct DB prefix (multisite-aware)
25 *
26 * @param int|null $blog_id Optional. Blog ID for multisite.
27 * @return string
28 */
29 public static function get_table_name( $blog_id = null ) {
30 global $wpdb;
31 $prefix = is_multisite() ? $wpdb->get_blog_prefix( $blog_id ?? get_current_blog_id() ) : $wpdb->prefix;
32 return $prefix . WPMCS_ITEM_TABLE;
33 }
34
35 /**
36 * Create Database Table
37 *
38 */
39 public function create_table() {
40 global $wpdb;
41
42 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
43
44 $table_name = self::get_table_name();
45 $queries = array();
46 $charset_collate = $wpdb->get_charset_collate();
47 if($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
48 $queries[] = "
49 CREATE TABLE {$table_name} (
50 id BIGINT(20) NOT NULL AUTO_INCREMENT,
51 provider VARCHAR(18) NOT NULL,
52 region VARCHAR(255) NOT NULL,
53 storage VARCHAR(255) NOT NULL,
54 source_id BIGINT(20) NOT NULL,
55 source_path VARCHAR(1024) NOT NULL,
56 source_type VARCHAR(18) NOT NULL,
57 url VARCHAR(1024) NOT NULL,
58 `key` VARCHAR(1024) NOT NULL,
59 original_source_path VARCHAR(1024) NOT NULL,
60 original_key VARCHAR(1024) NOT NULL,
61 is_private TINYINT(1) NOT NULL DEFAULT 0,
62 extra LONGTEXT DEFAULT NULL,
63
64 PRIMARY KEY (id),
65
66 UNIQUE KEY uidx_url (url(190), id),
67 UNIQUE KEY uidx_key (`key`(190), id),
68 UNIQUE KEY uidx_source_path (source_path(190), id),
69 UNIQUE KEY uidx_original_source_path (original_source_path(190), id),
70 UNIQUE KEY uidx_original_key (original_key(190), id),
71 KEY idx_item_lookup (
72 source_id,
73 source_type,
74 provider,
75 storage,
76 region
77 ),
78 KEY idx_source_path_provider (
79 provider,
80 storage,
81 region,
82 source_path(190)
83 )
84 ) $charset_collate;";
85 }
86 dbDelta( $queries );
87 }
88
89 /**
90 * Create tables across all sites (on activation)
91 */
92 public static function handle_tables($network_wide = false) {
93 // If network-wide activation, create tables for all sites in multisite
94 if (is_multisite() && $network_wide) {
95 $sites = get_sites(['number' => 1000, 'fields' => 'ids']); // Adjust number as needed
96 // If no sites found, return early
97 if (is_wp_error($sites) || !is_array($sites) || empty($sites)) {
98 return;
99 }
100
101 // Create table for each site in multisite
102 foreach ($sites as $site_id) {
103 switch_to_blog($site_id);
104 self::instance()->create_table();
105 restore_current_blog();
106 }
107 } else {
108 self::instance()->create_table();
109 }
110 }
111
112 /**
113 * Database Upgrade
114 * @since 1.0.0
115 */
116 public function do_database_upgrade() {
117 global $wpdb;
118 $table_name = self::get_table_name();
119 $current_version = get_option( $this->token.'_db_version' );
120 $latest_version = WPMCS_DB_VERSION;
121
122 if ( $current_version === false ) {
123 $current_version = $latest_version;
124 }
125
126 /**
127 * If DB version below 1.0.1
128 * @since 1.2.11
129 *
130 * This is to handle the migration of Google Cloud credentials from file to JSON string.
131 */
132 if (version_compare($current_version, '1.0.1', '<')) {
133 $credentials = Utils::get_credentials();
134 if (isset($credentials['service']) && $credentials['service'] === 'gcloud') {
135 $config = $credentials['config'] ?? null;
136 if($config) {
137 $config_json_path = $config['config_json']['path'] ?? null;
138 if (!empty($config_json_path) && file_exists($config_json_path)) {
139 $config_json = file_get_contents($config_json_path);
140 if (!empty($config_json)) {
141 $credentials['config']['config_json'] = $config_json;
142 Utils::update_credentials($credentials);
143 $updated = Utils::update_option('credentials', $credentials, Schema::getConstant('GLOBAL_SETTINGS_KEY'));
144 if ($updated) {
145 unlink($config_json_path);
146 }
147 }
148 }
149 }
150 }
151 }
152
153 /**
154 * If DB version below 1.0.2
155 *
156 * @since 1.2.12
157 * This is to handle the migration of media library counter from the global settings to the specific media library key.
158 */
159 if (version_compare($current_version, '1.0.2', '<')) {
160 $settings_key = Schema::getConstant('COUNTER_KEY');
161 $media_library = Utils::get_option( '', [], $settings_key );
162 if (is_array($media_library) && !empty($media_library)) {
163 Utils::update_option('', ['media_library' => $media_library ?? []], $settings_key);
164 }
165 }
166
167
168 /**
169 * If DB version below 1.0.3
170 *
171 * @since 1.2.13
172 * This is to handle the migration of media library counter from the global settings to the specific media library key.
173 * Re-Create plugin specific upload directory
174 */
175 if (version_compare($current_version, '1.0.3', '<')) {
176 Counter::instance()->fetch_and_update();
177 }
178
179 /**
180 * If DB version below 1.0.4
181 *
182 * @since 1.3.1
183 * This is to handle the removal of htaccess from uploads folder and adding htaccess to plugin specific upload directory
184 */
185 if (version_compare($current_version, '1.0.4', '<')) {
186 $upload_dir = wp_upload_dir();
187 $upload_base = $upload_dir['basedir'];
188
189 if (is_dir($upload_base)) {
190 $files_to_delete = array(
191 $upload_base . '/.htaccess',
192 $upload_base . '/' . Schema::getConstant('UPLOADS') . '/.htaccess',
193 $upload_base . '/' . Schema::getConstant('UPLOADS') .'/index.php',
194 );
195 foreach ($files_to_delete as $file) {
196 if (file_exists($file)) {
197 @unlink($file); // deletes the file
198 }
199 }
200 }
201
202 // Re-Create plugin specific upload directory
203 do_action('wpmcs_create_plugin_dir');
204 }
205
206
207 /**
208 * If DB version below 1.0.5
209 *
210 * @since 1.3.5
211 * Schema adjustments:
212 * - Add original_source_path AFTER `key`
213 * - Add original_key AFTER original_source_path
214 * - Add required indexes
215 */
216 if ( version_compare( $current_version, '1.0.5', '<' ) ) {
217 global $wpdb;
218
219 $table_name = self::get_table_name();
220
221 /**
222 * 1. Add original_source_path AFTER `key`
223 */
224 $exists = $wpdb->get_var(
225 $wpdb->prepare(
226 "SHOW COLUMNS FROM {$table_name} LIKE %s",
227 'original_source_path'
228 )
229 );
230
231 if ( ! $exists ) {
232 $wpdb->query(
233 "ALTER TABLE {$table_name}
234 ADD COLUMN original_source_path VARCHAR(1024) NOT NULL
235 AFTER `key`"
236 );
237 }
238
239 /**
240 * 2. Add original_key AFTER original_source_path
241 */
242 $exists = $wpdb->get_var(
243 $wpdb->prepare(
244 "SHOW COLUMNS FROM {$table_name} LIKE %s",
245 'original_key'
246 )
247 );
248
249 if ( ! $exists ) {
250 $wpdb->query(
251 "ALTER TABLE {$table_name}
252 ADD COLUMN original_key VARCHAR(1024) NOT NULL
253 AFTER original_source_path"
254 );
255 }
256
257 /**
258 * 3. Unique index for original_source_path
259 */
260 $exists = $wpdb->get_var(
261 $wpdb->prepare(
262 "SHOW INDEX FROM {$table_name} WHERE Key_name = %s",
263 'uidx_original_source_path'
264 )
265 );
266
267 if ( ! $exists ) {
268 $wpdb->query(
269 "ALTER TABLE {$table_name}
270 ADD UNIQUE KEY uidx_original_source_path (original_source_path(190), id)"
271 );
272 }
273
274 /**
275 * 4. Unique index for original_key
276 */
277 $exists = $wpdb->get_var(
278 $wpdb->prepare(
279 "SHOW INDEX FROM {$table_name} WHERE Key_name = %s",
280 'uidx_original_key'
281 )
282 );
283
284 if ( ! $exists ) {
285 $wpdb->query(
286 "ALTER TABLE {$table_name}
287 ADD UNIQUE KEY uidx_original_key (original_key(190), id)"
288 );
289 }
290
291 /**
292 * 5. Item lookup index (get by source_id)
293 */
294 $exists = $wpdb->get_var(
295 $wpdb->prepare(
296 "SHOW INDEX FROM {$table_name} WHERE Key_name = %s",
297 'idx_item_lookup'
298 )
299 );
300
301 if ( ! $exists ) {
302 $wpdb->query(
303 "ALTER TABLE {$table_name}
304 ADD KEY idx_item_lookup (
305 source_id,
306 source_type,
307 provider,
308 storage,
309 region
310 )"
311 );
312 }
313
314 /**
315 * 6. Provider-aware source_path prefix index
316 */
317 $exists = $wpdb->get_var(
318 $wpdb->prepare(
319 "SHOW INDEX FROM {$table_name} WHERE Key_name = %s",
320 'idx_source_path_provider'
321 )
322 );
323
324 if ( ! $exists ) {
325 $wpdb->query(
326 "ALTER TABLE {$table_name}
327 ADD KEY idx_source_path_provider (
328 provider,
329 storage,
330 region,
331 source_path(190)
332 )"
333 );
334 }
335
336 // Update upgrade version option only for who had previous version to trigger initial upgrade tracking
337 update_option( $this->token . '_db_upgrade_version', '0.0.0', false );
338 }
339
340 /**
341 * If DB version below 1.0.6
342 *
343 * @since 1.3.12
344 * Flatten recursively nested wpmcs_status_data caused by legacy set_status writes.
345 */
346 if ( version_compare( $current_version, '1.0.6', '<' ) ) {
347 $this->repair_nested_status_data();
348 }
349
350
351
352 update_option( $this->token.'_db_version', $latest_version, true );
353 }
354
355 /**
356 * One-time repair for recursively nested wpmcs_status_data.
357 * Flattens status entries caused by legacy set_status writes that used get_option directly.
358 *
359 * @since 1.3.12
360 * @return void
361 */
362 private function repair_nested_status_data() {
363 $meta_name = Schema::getConstant('STATUS_KEY');
364 $current_settings = Utils::get_option('status', [], $meta_name);
365
366 if (!is_array($current_settings) || empty($current_settings)) {
367 return;
368 }
369
370 $known_keys = ['cdnRead', 'storageCredentials'];
371 $collected = [];
372 $this->collect_status_entries($current_settings, $collected);
373
374 if (empty($collected)) {
375 return;
376 }
377
378 $normalized = [];
379 foreach ($known_keys as $key) {
380 if (isset($collected[$key])) {
381 $normalized[$key] = $collected[$key];
382 }
383 }
384
385 if (empty($normalized) || $normalized === $current_settings) {
386 return;
387 }
388
389 Utils::update_option('status', $normalized, $meta_name);
390 }
391
392 /**
393 * Collect status entries from all nesting levels, preferring the latest check.
394 * @since 1.3.12
395 * @param array $settings
396 * @param array $collected
397 * @return void
398 */
399 private function collect_status_entries($settings, &$collected) {
400 if (!is_array($settings)) {
401 return;
402 }
403
404 $known_keys = ['cdnRead', 'storageCredentials'];
405
406 foreach ($known_keys as $key) {
407 if (isset($settings[$key]) && is_array($settings[$key]) && array_key_exists('status', $settings[$key])) {
408 $entry = $settings[$key];
409 $checked = isset($entry['lastChecked']) ? (int) $entry['lastChecked'] : 0;
410
411 if (!isset($collected[$key]) || $checked >= ($collected[$key]['lastChecked'] ?? 0)) {
412 $collected[$key] = $entry;
413 }
414 }
415 }
416
417 if (isset($settings['status']) && is_array($settings['status'])) {
418 $this->collect_status_entries($settings['status'], $collected);
419 }
420 }
421
422 /**
423 * Ensures only one instance of Class is loaded or can be loaded.
424 *
425 * @return Db Class instance
426 * @since 1.0.0
427 * @static
428 */
429 public static function instance(){
430 if (is_null(self::$instance)) {
431 self::$instance = new self();
432 }
433 return self::$instance;
434 }
435 }