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');
}
}
}