| 1 |
<?php |
| 2 |
|
| 3 |
namespace FluentForm\Database\Migrations; |
| 4 |
|
| 5 |
defined('ABSPATH') or die; |
| 6 |
|
| 7 |
/** |
| 8 |
* Temporary compatibility migration for legacy manager scope records. |
| 9 |
* |
| 10 |
* Older installs may have "specific forms = yes" with an empty allowed-forms |
| 11 |
* list, which historically behaved like unrestricted access. The newer scoped |
| 12 |
* ACL logic treats that state as "no forms allowed", so we normalize those |
| 13 |
* legacy records once during upgrade. |
| 14 |
* |
| 15 |
* This can be removed after a few stable release cycles, once older installs |
| 16 |
* have had enough time to pass through the one-time normalization. |
| 17 |
*/ |
| 18 |
class LegacyManagerScopes |
| 19 |
{ |
| 20 |
const BATCH_SIZE = 500; |
| 21 |
const COMPLETED_OPTION = 'fluentform_empty_manager_scopes_normalized'; |
| 22 |
const CURSOR_OPTION = 'fluentform_empty_manager_scopes_normalization_cursor'; |
| 23 |
|
| 24 |
public static function migrate() |
| 25 |
{ |
| 26 |
if (get_option(self::COMPLETED_OPTION)) { |
| 27 |
return; |
| 28 |
} |
| 29 |
|
| 30 |
global $wpdb; |
| 31 |
|
| 32 |
$allowedFormsMetaKey = '_fluent_forms_allowed_forms'; |
| 33 |
$specificFormsMetaKey = '_fluent_forms_has_specific_forms_permission'; |
| 34 |
$lastProcessedUserId = max(0, intval(get_option(self::CURSOR_OPTION, 0))); |
| 35 |
|
| 36 |
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time upgrade cleanup for legacy manager scope data. |
| 37 |
$scopedUserIds = $wpdb->get_col($wpdb->prepare( |
| 38 |
"SELECT user_id |
| 39 |
FROM {$wpdb->usermeta} |
| 40 |
WHERE meta_key = %s |
| 41 |
AND meta_value = %s |
| 42 |
AND user_id > %d |
| 43 |
ORDER BY user_id ASC |
| 44 |
LIMIT %d", |
| 45 |
$specificFormsMetaKey, |
| 46 |
'yes', |
| 47 |
$lastProcessedUserId, |
| 48 |
self::BATCH_SIZE |
| 49 |
)); |
| 50 |
|
| 51 |
if (false === $scopedUserIds) { |
| 52 |
return; |
| 53 |
} |
| 54 |
|
| 55 |
$scopedUserIds = array_values(array_filter(array_map('intval', (array) $scopedUserIds))); |
| 56 |
|
| 57 |
if (!$scopedUserIds) { |
| 58 |
self::markComplete(); |
| 59 |
return; |
| 60 |
} |
| 61 |
|
| 62 |
if (!self::normalizeLegacyBatch($scopedUserIds, $specificFormsMetaKey, $allowedFormsMetaKey)) { |
| 63 |
return; |
| 64 |
} |
| 65 |
|
| 66 |
$lastProcessedUserId = (int) end($scopedUserIds); |
| 67 |
|
| 68 |
if (count($scopedUserIds) < self::BATCH_SIZE) { |
| 69 |
self::markComplete(); |
| 70 |
return; |
| 71 |
} |
| 72 |
|
| 73 |
update_option(self::CURSOR_OPTION, $lastProcessedUserId, 'no'); |
| 74 |
} |
| 75 |
|
| 76 |
protected static function normalizeLegacyBatch($userIds, $specificFormsMetaKey, $allowedFormsMetaKey) |
| 77 |
{ |
| 78 |
global $wpdb; |
| 79 |
|
| 80 |
$placeholders = implode(', ', array_fill(0, count($userIds), '%d')); |
| 81 |
|
| 82 |
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Batched read for one-time upgrade cleanup. |
| 83 |
$rows = $wpdb->get_results($wpdb->prepare( |
| 84 |
"SELECT user_id, meta_value |
| 85 |
FROM {$wpdb->usermeta} |
| 86 |
WHERE meta_key = %s |
| 87 |
AND user_id IN ({$placeholders})", |
| 88 |
array_merge([$allowedFormsMetaKey], $userIds) |
| 89 |
)); |
| 90 |
|
| 91 |
if (false === $rows) { |
| 92 |
return false; |
| 93 |
} |
| 94 |
|
| 95 |
$allowedFormsByUser = []; |
| 96 |
|
| 97 |
foreach ((array) $rows as $row) { |
| 98 |
$allowedFormsByUser[intval($row->user_id)][] = $row->meta_value; |
| 99 |
} |
| 100 |
|
| 101 |
$legacyUserIds = []; |
| 102 |
|
| 103 |
foreach ($userIds as $userId) { |
| 104 |
$metaValues = isset($allowedFormsByUser[$userId]) ? $allowedFormsByUser[$userId] : []; |
| 105 |
$hasAssignedForms = false; |
| 106 |
|
| 107 |
foreach ($metaValues as $metaValue) { |
| 108 |
$allowedForms = maybe_unserialize($metaValue); |
| 109 |
$allowedForms = is_array($allowedForms) |
| 110 |
? array_values(array_filter(array_map('intval', $allowedForms))) |
| 111 |
: []; |
| 112 |
|
| 113 |
if ($allowedForms) { |
| 114 |
$hasAssignedForms = true; |
| 115 |
break; |
| 116 |
} |
| 117 |
} |
| 118 |
|
| 119 |
if (!$hasAssignedForms) { |
| 120 |
$legacyUserIds[] = $userId; |
| 121 |
} |
| 122 |
} |
| 123 |
|
| 124 |
if (!$legacyUserIds) { |
| 125 |
return true; |
| 126 |
} |
| 127 |
|
| 128 |
$legacyPlaceholders = implode(', ', array_fill(0, count($legacyUserIds), '%d')); |
| 129 |
|
| 130 |
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Batched meta update for one-time upgrade cleanup. |
| 131 |
$updated = $wpdb->query($wpdb->prepare( |
| 132 |
"UPDATE {$wpdb->usermeta} |
| 133 |
SET meta_value = %s |
| 134 |
WHERE meta_key = %s |
| 135 |
AND user_id IN ({$legacyPlaceholders})", |
| 136 |
array_merge(['no', $specificFormsMetaKey], $legacyUserIds) |
| 137 |
)); |
| 138 |
|
| 139 |
if (false === $updated) { |
| 140 |
return false; |
| 141 |
} |
| 142 |
|
| 143 |
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Batched meta delete for one-time upgrade cleanup. |
| 144 |
$deleted = $wpdb->query($wpdb->prepare( |
| 145 |
"DELETE FROM {$wpdb->usermeta} |
| 146 |
WHERE meta_key = %s |
| 147 |
AND user_id IN ({$legacyPlaceholders})", |
| 148 |
array_merge([$allowedFormsMetaKey], $legacyUserIds) |
| 149 |
)); |
| 150 |
|
| 151 |
return false !== $deleted; |
| 152 |
} |
| 153 |
|
| 154 |
protected static function markComplete() |
| 155 |
{ |
| 156 |
delete_option(self::CURSOR_OPTION); |
| 157 |
update_option(self::COMPLETED_OPTION, FLUENTFORM_VERSION, 'no'); |
| 158 |
} |
| 159 |
} |
| 160 |
|