add_node([ 'id' => 'extendify-site-assistant', 'title' => \__('Site Assistant', 'extendify-local'), 'href' => \admin_url() . $this->getRoute(), 'parent' => 'site-name', 'meta' => ['position' => 1], ]); }, 11); // When Launch is finished, fire this to set the correct permalinks. // phpcs:ignore WordPress.Security.NonceVerification if (isset($_GET['extendify-launch-success'])) { \add_action('admin_init', function () { \flush_rewrite_rules(); }); } \add_action('admin_menu', function () { // If no partner, we don't show any menu. if (!PartnerData::$id) { return; } $assist = new AssistAdminPage(); // Adds the top Assist menu. $this->addAdminMenu( __('Site Assistant', 'extendify-local'), $assist->slug, [$assist, 'pageContent'] ); if (!Config::$showLaunch) { return; } $launch = new LaunchAdminPage(); $this->addSubMenu( // translators: Launch is a noun. __('Launch', 'extendify-local'), $launch->slug, [$launch, 'pageContent'] ); $autoLaunch = new AutoLaunchAdminPage(); $this->addSubMenu( // translators: Launch is a noun. __('Auto Launch', 'extendify-local'), $autoLaunch->slug, [$autoLaunch, 'pageContent'] ); }); // If the user is redirected to this while visiting our url, intercept it. \add_filter('wp_redirect', function ($url) { if (!PartnerData::$id) { return $url; } // Check for extendify-launch-success as other plugins will not override // this as they intercept the request. // phpcs:ignore WordPress.Security.NonceVerification if (isset($_GET['extendify-launch-success'])) { return \admin_url() . $this->getRoute(); } // Special treatment for Yoast to disable their redirect when installing. if ($url === \admin_url() . 'admin.php?page=wpseo_installation_successful_free') { return \admin_url() . $this->getRoute(); } // Special treatment for Germanized for WooCommerce to disable their redirect when installing. if ($url === \admin_url() . 'admin.php?page=wc-gzd-setup') { return \admin_url() . $this->getRoute(); } return $url; }, 9999); } /** * A helper for handling sub menus * * @param string $name - The menu name. * @param string $slug - The menu slug. * @param callable $callback - The callback to render the page. * * @return void */ public function addSubMenu($name, $slug, $callback = '') { \add_submenu_page( // Uses a "dummy" page for adding pages without a menu. (constant('EXTENDIFY_DEVMODE') ? 'extendify-assist' : 'extendify-page'), $name, $name, Config::$requiredCapability, $slug, $callback ); } /** * Adds Extendify top menu * * @param string $label - The menu label. * @param string $slug - The menu slug. * @param string|callable $callback - The callback to render the page. * @return void */ public function addAdminMenu($label, $slug, $callback) { $menuLabel = sprintf( '%1$s ', $label ); \add_menu_page( 'Extendify', $menuLabel, Config::$requiredCapability, $slug, $callback, // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode, Generic.Files.LineLength.TooLong 'data:image/svg+xml;base64,' . base64_encode(''), PartnerData::$id ? 0 : null ); } /** * Routes pages accordingly * * @return string */ public function getRoute() { // This router use to handle more cases but now everyone goes to Assist. return 'admin.php?page=extendify-assist'; } /** * Routes /wp-admin/?launch-redirect based on Launch and Agent state. * * @return void */ public function handleLaunchRedirect() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if (!isset($_GET['launch-redirect']) || \wp_doing_ajax() || !PartnerData::$id) { return; } // If launch hasn't yet completed, they go to launch/auto-launch if (!Config::$launchCompleted) { $useAgentOnboarding = PartnerData::setting('useAgentOnboarding') || Config::preview('agent-onboarding') || constant('EXTENDIFY_DEVMODE'); $page = $useAgentOnboarding ? 'extendify-auto-launch' : 'extendify-launch'; \wp_safe_redirect(\admin_url('admin.php?page=' . $page)); exit; } // If they have the Agent onboarding enabled, redirect to home. if (PartnerData::setting('showAIAgents')) { \wp_safe_redirect(\add_query_arg( ['extendify-open-agent' => '1'], \home_url('/') )); exit; } // Otherwise, they go to Assist. \wp_safe_redirect(\admin_url() . $this->getRoute()); exit; } /** * Redirect once to Launch, only once (at least once) when * the email matches the entry in WP Admin > Settings > General. * * @return void */ public function redirectOnce() { if (wp_doing_ajax()) { return; } if (defined('EXTENDIFY_IS_THEME_EXTENDABLE') && !EXTENDIFY_IS_THEME_EXTENDABLE) { return; } if (\get_option('extendify_launch_loaded', false) || !Config::$showLaunch) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if (isset($_GET['page']) && $_GET['page'] === 'extendify-auto-launch') { return; } // Only redirect if we aren't already on the page. // phpcs:ignore WordPress.Security.NonceVerification.Recommended if (isset($_GET['page']) && $_GET['page'] === 'extendify-launch') { $agentOnboarding = PartnerData::setting('useAgentOnboarding') || Config::preview('agent-onboarding') || constant('EXTENDIFY_DEVMODE'); if ($agentOnboarding) { // If they landed on launch but have the Agent onboarding enabled, redirect to auto-launch. $redirect_url = \add_query_arg( ['page' => 'extendify-auto-launch'], \admin_url('admin.php') ); \wp_safe_redirect($redirect_url); exit; } return; } $user = \wp_get_current_user(); if ( $user // Check the main admin email, and they have an admin role. && \get_option('admin_email') === $user->user_email && in_array('administrator', $user->roles, true) ) { // Only redirect 3 times. $currentCount = \get_option('extendify_attempted_redirect_count', 0); if ($currentCount >= 3) { return; } \update_option('extendify_attempted_redirect_count', ($currentCount + 1)); \update_option('extendify_attempted_redirect', gmdate('Y-m-d H:i:s')); // Update permalink structure to postname when auto-redirecting to Launch \update_option('permalink_structure', '/%postname%/'); \update_option('extendify_needs_rewrite_flush', true); $allowed_launch_params = [ 'objective', 'title', 'description', 'structure', 'tone', 'skip', // new for autolaunch (some duplicated) 'type', 'title', 'description', 'objective', 'category', 'structure', 'tone', 'products', 'appointments', 'events', 'donations', 'multilingual', 'contact', 'address', 'blog', 'landing-page', 'cta-link', 'build-id', 'go', ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash $autoLaunch = isset($_GET['auto-launch']) && filter_var($_GET['auto-launch'], FILTER_VALIDATE_BOOLEAN); $query_params = ['page' => $autoLaunch ? 'extendify-auto-launch' : 'extendify-launch']; foreach ($allowed_launch_params as $param) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $value = sanitize_text_field(wp_unslash($_GET[$param] ?? '')); if (!empty($value)) { $query_params[$param] = $value; } } $redirect_url = \add_query_arg($query_params, \admin_url('admin.php')); \wp_safe_redirect($redirect_url); exit; } } /** * Flushes WordPress rewrite rules if previously scheduled by `redirectOnce()`. * * This is triggered based on a flag (`extendify_needs_rewrite_flush`) that is set * when the permalink structure is modified. Flushing rewrite rules in the same * request where the permalink structure is updated can be ineffective due to * internal WordPress caching, so the flush is deferred to a subsequent request. * * @return void */ public function maybeForceFlush() { if (\get_option('extendify_needs_rewrite_flush', false)) { \flush_rewrite_rules(true); \delete_option('extendify_needs_rewrite_flush'); } } }