diff --git a/website/src/components/Meta.js b/website/src/components/Meta.js index 2077617..326e1cc 100644 --- a/website/src/components/Meta.js +++ b/website/src/components/Meta.js @@ -1,7 +1,7 @@ import GlobalsContext from 'contexts/GlobalsContext'; import { trimTrailingSlash } from 'lib/utils'; -import { NextSeo } from 'next-seo'; import Head from 'next/head'; +import { NextSeo } from 'next-seo'; import { useContext } from 'react'; export default function Meta({ title, description, image, seo }) { diff --git a/website/src/pages/api/revalidate.js b/website/src/pages/api/revalidate.js index 27af3d4..9a71a5a 100644 --- a/website/src/pages/api/revalidate.js +++ b/website/src/pages/api/revalidate.js @@ -10,12 +10,14 @@ export default async function handler(req, res) { } if (!paths || paths.length === 0) { + // eslint-disable-next-line no-console console.log('No paths provided for revalidation'); return void res.json({ revalidated: false }); } try { await Promise.all(paths.map((path) => res.revalidate(path))); + // eslint-disable-next-line no-console console.log('Paths successfully revalidated:', paths); return void res.json({ revalidated: true }); } catch (err) { diff --git a/wordpress/docker-compose.yml b/wordpress/docker-compose.yml index 3eaacb4..67b4ab8 100644 --- a/wordpress/docker-compose.yml +++ b/wordpress/docker-compose.yml @@ -22,6 +22,7 @@ services: restart: 'no' environment: + PORT: ${PORT:-3000} WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: root WORDPRESS_DB_PASSWORD: somewordpress @@ -32,6 +33,8 @@ services: define('HEADLESS_REVALIDATE_SECRET', 'bubs-next-vercel-revalidate-secret-key'); define('HEADLESS_AUTH_SECRET', 'bubs-next-wp-auth-secret-key'); define('HEADLESS_API_SECRET', 'bubs-next-headless-secret-key'); + define('WP_STELLATE_ENABLED', false); + define('WP_STELLATE_CONFIG', 'staging'); define('WP_LOCAL_DEV', true); define('WP_THEME', 'timber'); define('WP_DEBUG_LOG', true ); diff --git a/wordpress/wp-content/themes/headless/functions.php b/wordpress/wp-content/themes/headless/functions.php index f793849..4591267 100644 --- a/wordpress/wp-content/themes/headless/functions.php +++ b/wordpress/wp-content/themes/headless/functions.php @@ -9,17 +9,19 @@ // Customize these variables per site $staging_wp_host = 'bubsnexts.wpengine.com'; +$development_wp_host = 'bubsnextd.wpengine.com'; $dashboard_cleanup = false; // Optionally will hide all but our custom widget $docs_link = ''; // set to a path if you have a site/document for editor instructions // stellate config +$stellate_logging_enabled = false; // for debugging $stellate_production_enabled = true; $stellate_staging_enabled = false; $stellate_staging_service_name = ""; -$stellate_staging_token = ""; +$stellate_staging_purging_token = ""; $stellate_development_enabled = false; $stellate_development_service_name = ""; -$stellate_development_token = ""; +$stellate_development_purging_token = ""; $stellate_purge_redirection = true; $stellate_purge_acf_options = true; @@ -27,11 +29,14 @@ if (defined('WP_ENV') && WP_ENV == 'development') { define('WP_HOST', 'localhost'); $headless_domain = 'http://localhost:3000'; -} else { +} elseif (function_exists('is_wpe')) { $headless_domain = rtrim(get_theme_mod('headless_preview_url'), '/'); - if (strpos($_SERVER['HTTP_HOST'], $staging_wp_host) !== false) { - define('WP_HOST', 'staging'); + if ( + strpos($_SERVER['HTTP_HOST'], $staging_wp_host) !== false || + strpos($_SERVER['HTTP_HOST'], $development_wp_host) !== false + ) { + define('WP_HOST', strpos($_SERVER['HTTP_HOST'], $staging_wp_host) !== false ? 'staging' : 'development'); } else { define('WP_HOST', 'production'); } diff --git a/wordpress/wp-content/themes/headless/setup/helpers/stellate.php b/wordpress/wp-content/themes/headless/setup/helpers/stellate.php index 59b2527..cf15b52 100644 --- a/wordpress/wp-content/themes/headless/setup/helpers/stellate.php +++ b/wordpress/wp-content/themes/headless/setup/helpers/stellate.php @@ -1,77 +1,239 @@ update( + $wpdb->options, + ['option_value' => $token], + ['option_name' => 'stellate_purging_token'], + ); -function disable_stellate_plugin() -{ - $plugin_slug = 'stellate/wp-stellate.php'; - if (!defined('WP_STELLATE_ENABLED')) { - if (defined('WP_HOST') && WP_HOST == "production") { - global $stellate_production_enabled; - if ($stellate_production_enabled) { - define('WP_STELLATE_ENABLED', true); - } - } + // Force refresh of the alloptions cache + wp_cache_delete('alloptions', 'options'); - if (defined('WP_HOST') && WP_HOST == "staging") { - global $stellate_staging_enabled; - if ($stellate_staging_enabled) { - define('WP_STELLATE_ENABLED', true); - } - } + if ($stellate_logging_enabled) { + error_log('Stellate: Updated purging token'); + } +} - if (defined('WP_HOST') && WP_HOST == "development") { - global $stellate_development_enabled; - if ($stellate_development_enabled) { - define('WP_STELLATE_ENABLED', true); - } - } +/** + * Configures the Stellate plugin based on the current environment. + * This function sets up the Stellate service name and purging token + * for different environments (staging, development, localhost, production). + * We need this because we don't want to purge cache during local/staging development. + * It's hooked to run after all plugins are loaded. + */ +function configure_stellate_plugin() { + global $stellate_staging_service_name, $stellate_staging_purging_token; + global $stellate_development_service_name, $stellate_development_purging_token; + global $stellate_production_enabled, $stellate_staging_enabled, $stellate_development_enabled; + + if (!defined('WP_HOST')) { + return; } + $config = [ + 'staging' => [ + 'enabled' => $stellate_staging_enabled, + 'service_name' => $stellate_staging_service_name, + 'purging_token' => $stellate_staging_purging_token, + ], + 'development' => [ + 'enabled' => $stellate_development_enabled, + 'service_name' => $stellate_development_service_name, + 'purging_token' => $stellate_development_purging_token, + ], + 'production' => [ + 'enabled' => $stellate_production_enabled, + ], + ]; - if (defined('WP_STELLATE_ENABLED') && WP_STELLATE_ENABLED) { - // leave enabled - } else { - // disable + $env = WP_HOST; + + if ($env === 'localhost' && defined('WP_STELLATE_CONFIG')) { + $env = WP_STELLATE_CONFIG; + } + + if (isset($config[$env]) && $config[$env]['enabled']) { + if (isset($config[$env]['service_name'])) { + update_option('stellate_service_name', $config[$env]['service_name']); + } + if (isset($config[$env]['purging_token'])) { + update_stellate_token($config[$env]['purging_token']); + } + if (!defined('WP_STELLATE_ENABLED')) { + define('WP_STELLATE_ENABLED', true); + } + } + + // with configs now defined, we will deactivate the plugin if it's not enabled + $plugin_slug = 'stellate/wp-stellate.php'; + if (!defined('WP_STELLATE_ENABLED') || !WP_STELLATE_ENABLED) { deactivate_plugins($plugin_slug); } } +// Add this action to run after plugins are loaded +// add_action('plugins_loaded', 'configure_stellate_plugin', 20); +add_action('admin_init', 'configure_stellate_plugin'); + global $stellate_purge_redirection; global $stellate_purge_acf_options; -if ($stellate_purge_redirection) { - function purge_redirection() - { - stellate_add_purge_entity('purged_types', 'RedirectionRedirects'); +/** + * Handles purging of redirects in Stellate. + * This function is called when redirects are added, updated, deleted, enabled, or disabled. + * It purges the specific redirect from Stellate and revalidates the URL in Vercel. + * + * @param mixed $redirect The redirect object or ID. + */ +function purge_redirection($redirect) { + global $stellate_logging_enabled; + $origin = null; + + // Determine the origin URL of the redirect + if (is_numeric($redirect)) { + // This is likely a new redirect being created + if ($stellate_logging_enabled) { + error_log("Stellate: New redirect created with ID: {$redirect}"); + } + // Get the redirect details + $redirect_object = Red_Item::get_by_id($redirect); + if ($redirect_object) { + $origin = $redirect_object->get_url(); + } + } elseif (is_object($redirect) && method_exists($redirect, 'get_url')) { + $origin = $redirect->get_url(); + } else { + $origin = $redirect; + } + + if ($origin) { + if ($stellate_logging_enabled) { + error_log("Stellate: Purging redirection - Origin: {$origin}"); + } + + // Purge the specific redirect in Stellate + $query = "mutation PurgeRedirect { + purgeType: _purgeType(type: \"RedirectionRedirects\") + }"; + + $variables = [ + 'origin' => $origin, + 'soft' => get_option('stellate_soft_purge') === 'on', + ]; + + $err = stellate_call_admin_api($query, $variables); + + if ($err) { + error_log('Stellate: Error purging redirect - ' . $err); + } else { + error_log("Stellate: Successfully purged redirect - Origin: {$origin}"); + + // Revalidate the URL in Vercel + vercel_revalidate([$origin]); + } + } else { + if ($stellate_logging_enabled) { + error_log('Stellate: Unable to determine origin for redirect'); + } } - add_action('redirection_redirect_updated', 'purge_redirection'); - add_action('redirection_redirect_deleted', 'purge_redirection'); } -if ($stellate_purge_acf_options) { - function purge_acf_options() - { - stellate_add_purge_entity('purged_types', 'AcfOptionsThemeSettings'); +/** + * Sets up the redirection purging functionality if enabled. + * This function adds the necessary action hooks for redirect events. + */ +function setup_redirection_purging() { + global $stellate_logging_enabled; + global $stellate_purge_redirection; + + if ($stellate_purge_redirection && function_exists('stellate_call_admin_api')) { + // Add hooks for various redirect events + add_action('redirection_redirect_added', 'purge_redirection'); + add_action('redirection_redirect_updated', 'purge_redirection'); + add_action('redirection_redirect_deleted', 'purge_redirection'); + add_action('redirection_redirect_disabled', 'purge_redirection'); + add_action('redirection_redirect_enabled', 'purge_redirection'); + } +} + +// Call the setup function +setup_redirection_purging(); + +/** + * Purges the ACF options from Stellate cache. + * This function is called when ACF options are saved. + */ +function purge_acf_options() { + global $stellate_logging_enabled; + + if ($stellate_logging_enabled) { + error_log('Stellate: Purging ACF options'); + } + + stellate_add_purge_entity('AcfOptionsThemeSettings', 1); + + if ($stellate_logging_enabled) { + error_log('Stellate: ACF options purged'); } - add_action('acf/options_page/save', 'purge_acf_options'); } -add_action('redirection_redirect_updated', 'purge_redirection'); -add_action('redirection_redirect_deleted', 'purge_redirection'); -add_action('acf/options_page/save', 'purge_acf_options'); +/** + * Sets up the ACF options purging functionality if enabled. + * This function adds the necessary action hook for ACF options saving event. + */ +function setup_acf_options_purging() { + global $stellate_logging_enabled; + global $stellate_purge_acf_options; + if ($stellate_purge_acf_options) { + add_action('acf/options_page/save', 'purge_acf_options'); -function vercel_revalidate($urls) -{ + if ($stellate_logging_enabled) { + error_log('Stellate: ACF options purging set up'); + } + } +} + +// Call the setup function +setup_acf_options_purging(); + +/** + * Sends a revalidation request to Vercel for the specified URLs. + * This function handles both development and production environments, + * and logs the process if logging is enabled. + * In preview, we use the Bypass Protection header to bypass the protection + * https://vercel.com/docs/security/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation + * + * @param array $urls The URLs to be revalidated. + */ +function vercel_revalidate($urls) { global $headless_domain; + global $stellate_logging_enabled; + global $vercel_protection_bypass; if (defined('WP_ENV') && WP_ENV == 'development') { - $api_domain = 'http://host.docker.internal:3000'; + $port = isset($_ENV['PORT']) ? $_ENV['PORT'] : '3000'; + $api_domain = "http://host.docker.internal:$port"; } else { $api_domain = $headless_domain; } @@ -79,14 +241,19 @@ function vercel_revalidate($urls) $paths = []; if (!defined('HEADLESS_REVALIDATE_SECRET')) { - error_log('HEADLESS_REVALIDATE_SECRET not defined'); + if ($stellate_logging_enabled) { + error_log('Stellate: HEADLESS_REVALIDATE_SECRET not defined'); + } return; } foreach ($urls as $url) { - if (strpos($url, '://')) { + if (strpos($url, '://') !== false) { $parsed = parse_url($url); $url = $parsed['path']; + } else { + // Handle relative paths + $url = '/' . ltrim($url, '/'); } if ($url !== '/') { $url = rtrim($url, '/'); // Remove trailing slash @@ -96,31 +263,71 @@ function vercel_revalidate($urls) $post_url = $api_domain . '/api/revalidate'; - $body = array( + $body = [ 'paths' => $paths, - 'secret' => HEADLESS_REVALIDATE_SECRET - ); + 'secret' => HEADLESS_REVALIDATE_SECRET, + ]; - $args = array( + $args = [ 'body' => json_encode($body), - 'headers' => array('Content-Type' => 'application/json') - ); + 'headers' => ['Content-Type' => 'application/json'], + 'timeout' => 10, // default of 5 seconds needs a little more time + ]; + + if (isset($vercel_protection_bypass)) { + $args['headers']['x-vercel-protection-bypass'] = $vercel_protection_bypass; + } + + if ($stellate_logging_enabled) { + error_log('Stellate: Sending revalidation request to: ' . $post_url); + error_log('Stellate: Revalidation response body: ' . json_encode($body)); + } - wp_remote_post($post_url, $args); + $response = wp_remote_post($post_url, $args); + + if ($stellate_logging_enabled) { + if (is_wp_error($response)) { + error_log('Stellate: Revalidation request failed: ' . $response->get_error_message()); + } else { + error_log( + 'Stellate: Revalidation request response code: ' . + wp_remote_retrieve_response_code($response), + ); + error_log( + 'Stellate: Revalidation request response body: ' . wp_remote_retrieve_body($response), + ); + } + } } -function stellate_purge_callback($purge_data) -{ +/** + * Handles Stellate purge requests + * + * This function processes purge data from Stellate, collects paths to be purged, + * and initiates the revalidation process for those paths. + * + * @param array $purge_data The purge data received from Stellate + */ +function stellate_purge_callback($purge_data) { + global $stellate_logging_enabled; $paths = []; - // Uncomment to add logs to debug.log - // error_log('attempting handle_stellate_purge'); - // error_log(json_encode($purge_data)); + if ($stellate_logging_enabled) { + error_log('Stellate: Attempting handle_stellate_purge'); + error_log('Stellate: Purge data: ' . json_encode($purge_data)); + } if ($purge_data['has_purged_all']) { - $query = new WP_Query(array('posts_per_page' => -1, 'fields' => 'ids')); + if ($stellate_logging_enabled) { + error_log('Stellate: Purging all content'); + } + $query = new WP_Query(['posts_per_page' => -1, 'fields' => 'ids']); foreach ($query as $id) { - array_push($paths, get_permalink($id)); + $path = get_permalink($id); + array_push($paths, $path); + if ($stellate_logging_enabled) { + error_log('Stellate: Adding path to purge: ' . $path); + } } vercel_revalidate($paths); return; @@ -128,9 +335,16 @@ function stellate_purge_callback($purge_data) // Process purged types foreach ($purge_data['purged_types'] as $type) { - $path = get_post_type_archive_link($type); - if ($path) { - array_push($paths, $path); + if ($type === 'RedirectionRedirects') { + // We can skip, we handle this in the redirection function above + } else { + $path = get_post_type_archive_link($type); + if ($path) { + array_push($paths, $path); + if ($stellate_logging_enabled) { + error_log('Stellate: Adding archive path to purge: ' . $path); + } + } } } @@ -144,12 +358,20 @@ function stellate_purge_callback($purge_data) $path = get_permalink($id); if ($path) { array_push($paths, $path); + if ($stellate_logging_enabled) { + error_log('Stellate: Adding individual path to purge: ' . $path); + } } } } } + if ($stellate_logging_enabled) { + error_log('Stellate: Total paths to purge: ' . count($paths)); + error_log('Stellate: Paths to purge: ' . implode(', ', $paths)); + } + vercel_revalidate($paths); } -add_action('stellate_purge', 'stellate_purge_callback', 10, 2); +add_action('stellate_purge', 'stellate_purge_callback', 10, 2); \ No newline at end of file