diff --git a/acf-hide-layout.php b/acf-hide-layout.php index 67c2a29..19a4118 100644 --- a/acf-hide-layout.php +++ b/acf-hide-layout.php @@ -228,6 +228,13 @@ private function init_hooks() { public function init() { // Set up localisation. $this->load_plugin_textdomain(); + // Include and run migration script if in admin + if ($this->is_request('admin')) { + $migration_file = dirname($this->file) . '/migrate-to-acf-native.php'; + if (file_exists($migration_file)) { + require_once $migration_file; + } + } } /** diff --git a/migrate-to-acf-native.php b/migrate-to-acf-native.php new file mode 100644 index 0000000..698e234 --- /dev/null +++ b/migrate-to-acf-native.php @@ -0,0 +1,262 @@ +'; + echo '

Migrate ACF Hidden Layouts

'; + echo '

This tool migrates hidden layouts created by the plugin flyntwp/acf-hide-layout to ACF\'s native "Deactivate layout" feature.

'; + + echo '
'; + \wp_nonce_field('flynt_migrate_acf_hide_layout', 'flynt_migrate_acf_hide_layout_nonce'); + echo '

'; + echo '

'; + echo '
'; + + if ($isSubmitted && \check_admin_referer('flynt_migrate_acf_hide_layout', 'flynt_migrate_acf_hide_layout_nonce')) { + $results = migrateHiddenLayouts((bool) $dryRun); + renderResults($results, (bool) $dryRun); + } + + echo ''; +} + +/** + * Perform migration of hidden layouts to ACF native layout_meta disabled list + * + * @param bool $dryRun If true, do not persist changes + * @return array Summary of actions and per-post logs + */ +function migrateHiddenLayouts($dryRun = true) +{ + $summary = [ + 'dryRun' => $dryRun, + 'checkedPosts' => 0, + 'updatedPosts' => 0, + 'errors' => [], + 'logs' => [], + ]; + + // Check function availability + $hasAcf = \function_exists('acf_get_fields') || \function_exists('get_field_objects'); + $hasAcfMetaApi = \function_exists('acf_update_metadata_by_field'); + + if (!$hasAcf || !$hasAcfMetaApi) { + $summary['errors'][] = 'Required ACF functions are not available. Ensure ACF Pro 6.0+ is active.'; + return $summary; + } + + // Retrieve all public post types including custom, exclude revisions and attachments + $postTypes = \get_post_types(['public' => true], 'names'); + + $queryArgs = [ + 'post_type' => array_values($postTypes), + 'post_status' => ['publish', 'draft', 'pending', 'future', 'private'], + 'posts_per_page' => -1, + 'fields' => 'ids', + 'no_found_rows' => true, + 'update_post_term_cache' => false, + 'update_post_meta_cache' => false, + ]; + + $postIds = \get_posts($queryArgs); + + foreach ($postIds as $postId) { + $summary['checkedPosts']++; + + $postLog = [ + 'postId' => $postId, + 'fieldsProcessed' => 0, + 'layoutsFound' => 0, + 'layoutsHiddenDetected' => 0, + 'layoutsNewlyDisabled' => 0, + 'fieldLogs' => [], + ]; + + // Get all ACF field objects attached to this post + $fieldObjects = \function_exists('get_field_objects') ? \call_user_func('get_field_objects', $postId) : null; + if (empty($fieldObjects)) { + $summary['logs'][] = $postLog; + continue; + } + + foreach ($fieldObjects as $fieldName => $fieldObject) { + if (!isset($fieldObject['type']) || $fieldObject['type'] !== 'flexible_content') { + continue; + } + + $postLog['fieldsProcessed']++; + + // We need to iterate rows of this flexible content + $hasRows = \function_exists('have_rows') ? (bool) \call_user_func('have_rows', $fieldName, $postId) : false; + if (!$hasRows) { + continue; + } + + // Collect disabled indices for this field + $disabledLayouts = []; + $renamedLayouts = []; + + // If there is existing layout_meta, preserve it + $existingLayoutMeta = \get_post_meta($postId, '_' . $fieldName . '_layout_meta', true); + if (is_array($existingLayoutMeta)) { + if (!empty($existingLayoutMeta['disabled']) && is_array($existingLayoutMeta['disabled'])) { + $disabledLayouts = array_values(array_unique(array_map('intval', $existingLayoutMeta['disabled']))); + } + if (!empty($existingLayoutMeta['renamed']) && is_array($existingLayoutMeta['renamed'])) { + $renamedLayouts = $existingLayoutMeta['renamed']; + } + } + + // Iterate rows and detect plugin hidden flags + if (\call_user_func('have_rows', $fieldName, $postId)) { + $rowIndex = 0; + while (\call_user_func('have_rows', $fieldName, $postId)) { + \call_user_func('the_row'); + $postLog['layoutsFound']++; + + // Determine the plugin field key suffix used for hidden flag + // Per plugin: name is "{$field['name']}_{$key}_{$field_key}" where $field_key is acf_hide_layout + // Here, $key is zero-based row index in stored meta for flexible content + $hideFieldName = $fieldName . '_' . $rowIndex . '_acf_hide_layout'; + + // Build fake field array to use acf_get_value API similar to plugin's read method + $hideLayoutField = [ + 'name' => $hideFieldName, + 'key' => 'field_acf_hide_layout', + ]; + + $isHidden = null; + if (\function_exists('acf_get_value')) { + $isHidden = \call_user_func('acf_get_value', $postId, $hideLayoutField); + } else { + $isHidden = \get_post_meta($postId, $hideFieldName, true); + } + + if (!empty($isHidden)) { + $postLog['layoutsHiddenDetected']++; + if (!in_array($rowIndex, $disabledLayouts, true)) { + $disabledLayouts[] = $rowIndex; + $postLog['layoutsNewlyDisabled']++; + } + } + + $rowIndex++; + } + } + + sort($disabledLayouts); + + // If changes, write layout_meta using ACF API + $fieldLog = [ + 'fieldName' => $fieldName, + 'disabled' => $disabledLayouts, + 'renamed' => $renamedLayouts, + ]; + + if (!empty($disabledLayouts)) { + if (!$dryRun) { + if (\function_exists('acf_update_metadata_by_field')) { + \call_user_func( + 'acf_update_metadata_by_field', + $postId, + [ + 'name' => '_' . $fieldName . '_layout_meta', + ], + [ + 'disabled' => $disabledLayouts, + 'renamed' => $renamedLayouts, + ] + ); + $summary['updatedPosts']++; + } else { + // Fallback to update_post_meta if ACF helper is not available + \update_post_meta($postId, '_' . $fieldName . '_layout_meta', [ + 'disabled' => $disabledLayouts, + 'renamed' => $renamedLayouts, + ]); + $summary['updatedPosts']++; + } + } + } + + $postLog['fieldLogs'][] = $fieldLog; + } + + $summary['logs'][] = $postLog; + } + + return $summary; +} + +/** + * Render migration results in admin UI + * + * @param array $results + * @param bool $dryRun + * @return void + */ +function renderResults($results, $dryRun) +{ + if (!empty($results['errors'])) { + foreach ($results['errors'] as $error) { + echo '

' . \esc_html($error) . '

'; + } + return; + } + + $summaryText = sprintf( + 'Processed %d posts. %s %d post(s) were updated.', + (int) $results['checkedPosts'], + $dryRun ? 'Dry run: no changes saved.' : 'Changes saved.', + (int) $results['updatedPosts'] + ); + + echo '

' . \esc_html($summaryText) . '

'; + + echo '

Details

'; + echo '
'; + + foreach ($results['logs'] as $postLog) { + echo '
'; + echo '

Post ID ' . intval($postLog['postId']) . '

'; + echo '

Fields processed: ' . intval($postLog['fieldsProcessed']) . ' | Layouts found: ' . intval($postLog['layoutsFound']) . ' | Hidden detected: ' . intval($postLog['layoutsHiddenDetected']) . ' | Newly disabled: ' . intval($postLog['layoutsNewlyDisabled']) . '

'; + + if (!empty($postLog['fieldLogs'])) { + echo ''; + } + echo '
'; + } + + echo '
'; +} +