shell bypass 403
import { useEffect, useState, useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Transition } from '@headlessui/react';
import { installPlugin, activatePlugin } from '@shared/api/wp';
import { pageNames } from '@shared/lib/pages';
import { deepMerge } from '@shared/lib/utils';
import { useAIConsentStore } from '@shared/state/ai-consent';
import { colord } from 'colord';
import { getPartnerPlugins } from '@launch/api/DataApi';
import {
updateTemplatePart,
addSectionLinksToNav,
addPageLinksToNav,
updateOption,
updatePattern,
getOption,
getPageById,
getActivePlugins,
prefetchAssistData,
postLaunchFunctions,
createNavigation,
updateNavAttributes,
installFontFamilies,
} from '@launch/api/WPApi';
import { importTemporaryProducts } from '@launch/api/WooCommerce';
import { PagesSkeleton } from '@launch/components/CreatingSite/PageSkeleton';
import { useConfetti } from '@launch/hooks/useConfetti';
import { useWarnOnLeave } from '@launch/hooks/useWarnOnLeave';
import {
updateButtonLinks,
updateSinglePageLinksToSections,
} from '@launch/lib/linkPages';
import { uploadLogo } from '@launch/lib/logo';
import {
retryOperation,
waitFor200Response,
wasInstalled,
} from '@launch/lib/util';
import {
createWpPages,
createBlogSampleData,
generateCustomPageContent,
replacePlaceholderPatterns,
updateGlobalStyleVariant,
setHelloWorldFeaturedImage,
} from '@launch/lib/wp';
import { usePagesStore } from '@launch/state/Pages';
import { usePagesSelectionStore } from '@launch/state/pages-selections';
import { useUserSelectionStore } from '@launch/state/user-selections';
import { Logo, Spinner } from '@launch/svg';
const {
homeUrl,
adminUrl,
partnerLogo,
partnerName,
installedPlugins = [],
requiredPlugins = [],
} = window.extSharedData;
export const CreatingSite = () => {
const [isShowing] = useState(true);
const [confettiReady, setConfettiReady] = useState(false);
const [confettiColors, setConfettiColors] = useState(['#ffffff']);
const [warnOnLeaveReady, setWarnOnLeaveReady] = useState(true);
const {
goals,
siteType,
siteInformation,
siteStructure,
getGoalsPlugins,
variation,
siteProfile,
siteStrings,
siteImages,
CTALink,
siteObjective,
} = useUserSelectionStore();
const { pages, style } = usePagesSelectionStore();
const [info, setInfo] = useState([]);
const [infoDesc, setInfoDesc] = useState([]);
const inform = (msg) => setInfo((info) => [msg, ...info]);
const informDesc = (msg) => setInfoDesc((infoDesc) => [msg, ...infoDesc]);
const [pagesToAnimate, setPagesToAnimate] = useState([]);
const { setPage } = usePagesStore();
const customFontFamilies =
variation?.settings?.typography?.fontFamilies?.custom;
const { setUserGaveConsent } = useAIConsentStore();
const redirectUrl =
// on landing pages for some users, we redirect to home_url
window.extOnbData.redirectToWebsite && siteObjective === 'landing-page'
? `${homeUrl}?extendify-launch-success`
: `${adminUrl}admin.php?page=extendify-assist&extendify-launch-success`;
useWarnOnLeave(warnOnLeaveReady);
const doEverything = useCallback(async () => {
try {
const hasBlogGoal = goals?.find((goal) => goal.slug === 'blog');
await updateOption('permalink_structure', '/%postname%/');
await waitFor200Response();
inform(__('Applying your website styles', 'extendify-local'));
informDesc(__('Creating a beautiful website', 'extendify-local'));
await new Promise((resolve) => setTimeout(resolve, 1000));
// If they are launching the site, it means they agreed to the terms
setUserGaveConsent(true);
if (siteInformation.title) {
await updateOption('blogname', siteInformation.title);
}
await waitFor200Response();
// TODO: Refactor to assume 0default for site type
const siteTypeUpdated = {
...(siteType ?? {}),
// Override with the ai site type if it exists
name: siteProfile?.aiSiteType ?? siteType.name,
};
await updateOption(
'extendify_siteType',
// Only persist the site type if the slug exists
siteType?.slug ? siteTypeUpdated : {},
);
await waitFor200Response();
// Install font families that are not in the theme.
if (customFontFamilies?.length) {
const installedFontFamilies =
await installFontFamilies(customFontFamilies);
await updateGlobalStyleVariant(
deepMerge(
variation,
// We set to null first to reset the field.
{ settings: { typography: { fontFamilies: { custom: null } } } },
// We add the installed font families here to activate them.
{
settings: {
typography: {
fontFamilies: {
custom: installedFontFamilies.filter(Boolean),
},
},
},
},
) ?? {},
);
} else {
await updateGlobalStyleVariant(variation);
}
const navigationId = await createNavigation();
let headerCode = updateNavAttributes(style?.headerCode, {
ref: navigationId,
});
if (siteObjective === 'landing-page') {
// remove the header navigation from the landing page
headerCode = headerCode
.replace(/<!--\s*wp:navigation\b[^>]*.*\/-->/gis, '')
.replace(
/<!--\s*wp:social-links\b[^>]*>.*?<!--\s*\/wp:social-links\s*-->/gis,
'',
);
}
await waitFor200Response();
await updateTemplatePart('extendable/header', headerCode);
await waitFor200Response();
await updateTemplatePart('extendable/footer', style?.footerCode);
const goalsPlugins = getGoalsPlugins();
// Add required plugins to the end of the list to give them lower priority
// when filtering out duplicates.
const sortedPlugins = [...goalsPlugins, ...requiredPlugins]
// Remove duplicates
.reduce((acc, plugin) => {
const found = acc.find(
({ wordpressSlug: s }) => s === plugin.wordpressSlug,
);
return found ? acc : [...acc, plugin];
}, [])
// We add give to the front. See here why:
// https://github.com/extendify/company-product/issues/713
.sort(({ wordpressSlug }) => (wordpressSlug === 'give' ? -1 : 1));
if (sortedPlugins?.length) {
inform(__('Installing necessary plugins', 'extendify-local'));
for (const [index, plugin] of sortedPlugins.entries()) {
const slug = plugin?.wordpressSlug;
informDesc(
__(
`${index + 1}/${sortedPlugins.length}: ${plugin.name}`,
'extendify-local',
),
);
// Don't install if already installed
if (!installedPlugins?.some((s) => s.includes(slug))) {
await retryOperation(() => installPlugin(slug), {
maxAttempts: 2,
}).catch(console.error);
}
await retryOperation(() => activatePlugin(slug), {
maxAttempts: 2,
}).catch(console.error);
}
}
inform(__('Populating data', 'extendify-local'));
informDesc(__('Personalizing your experience', 'extendify-local'));
await prefetchAssistData();
await waitFor200Response();
inform(__('Adding page content', 'extendify-local'));
informDesc(__('Starting off with a full website', 'extendify-local'));
await new Promise((resolve) => setTimeout(resolve, 1000));
await waitFor200Response();
// Store the site vibes, colorPalette and fonts
await updateOption(
'extendify_siteStyle',
style?.siteStyle || {
vibe: 'standard',
fonts: { heading: {}, body: {} },
colorPalette: null,
},
);
const homePage = {
name: pageNames.home.title,
id: 'home',
patterns: style.patterns,
slug: 'home',
};
const blogPage = {
name: pageNames.blog.title,
id: 'blog',
patterns: [],
slug: 'blog',
};
await waitFor200Response();
if (siteProfile.aiDescription) {
informDesc(__('Creating pages with custom content', 'extendify-local'));
[homePage, ...pages].forEach((page) =>
setPagesToAnimate((previous) => [...previous, page.name]),
);
}
const pagesToCreate = [
...pages,
homePage,
hasBlogGoal ? blogPage : null,
].filter(Boolean);
const pagesWithReplacedPatterns = [];
// Run these one page at a time so we don't end up with duplicate dependency issues
for (const page of pagesToCreate) {
const updatedPage = {
...page,
patterns: await replacePlaceholderPatterns(page.patterns),
};
pagesWithReplacedPatterns.push(updatedPage);
}
// Stash the page-title pattern for use in ai page creator
const firstPattern = pagesWithReplacedPatterns?.[0]?.patterns?.[0];
const pageTitle = firstPattern?.patternTypes?.includes('page-title')
? (firstPattern?.code ?? null)
: null;
await updatePattern('launch_page_title_pattern', pageTitle);
const pagesWithCustomContent = await generateCustomPageContent(
pagesWithReplacedPatterns,
{
goals,
siteType: siteTypeUpdated.name,
siteInformation,
},
siteProfile,
);
const createdPages = await createWpPages(pagesWithCustomContent, {
stickyNav:
siteStructure === 'single-page' && siteObjective !== 'landing-page',
});
const hasBlogPattern = homePage?.patterns?.some((pattern) =>
pattern.patternTypes.includes('blog-section'),
);
if (hasBlogGoal || hasBlogPattern) {
informDesc(__('Creating blog sample data', 'extendify-local'));
await createBlogSampleData(siteStrings, siteImages);
}
await waitFor200Response();
if (siteImages?.siteImages) {
await setHelloWorldFeaturedImage(siteImages.siteImages);
}
setPagesToAnimate([]);
await waitFor200Response();
informDesc(__('Setting up site layout', 'extendify-local'));
const navPagesMultiPageSite = [
...pages,
hasBlogGoal ? blogPage : null,
homePage,
]
.filter(Boolean)
// Sorted AZ by title in all languages
.sort((a, b) => a?.name?.localeCompare(b?.name));
const pluginPages = [];
// Fetch active plugins after installing plugins
let { data: activePlugins } = await getActivePlugins();
// Add plugin related pages only if plugin is active
if (wasInstalled(activePlugins, 'woocommerce')) {
const shopPageId = await getOption('woocommerce_shop_page_id');
const shopPage = shopPageId
? await getPageById(shopPageId).catch(() => null)
: null;
if (shopPage) {
pluginPages.push(shopPage);
}
informDesc(__('Importing shop sample data', 'extendify-local'));
await importTemporaryProducts();
// If we installed any plugins above, and a partner has supported plugins
// linked to those plugins, we should install them here. For example:
// A German specific WooCommerce plugin in case WooCommerce is installed.
const partnerPlugins = await getPartnerPlugins('products').catch(
() => null,
);
if (partnerPlugins) {
informDesc(__('Installing supporting plugins', 'extendify-local'));
for (const plugin of partnerPlugins) {
if (!wasInstalled(activePlugins, plugin)) {
const maxAttempts = 2;
await retryOperation(() => installPlugin(plugin), {
maxAttempts,
}).catch(console.error);
await retryOperation(() => activatePlugin(plugin), {
maxAttempts,
}).catch(console.error);
}
}
}
}
if (wasInstalled(activePlugins, 'the-events-calendar')) {
const eventsPage = {
title: {
rendered: __('Events', 'extendify-local'),
},
slug: 'events',
link: `${homeUrl}/events`,
};
pluginPages.push(eventsPage);
}
if (wasInstalled(activePlugins, 'wpforms-lite')) {
await updateOption('wpforms_activation_redirect', 'skip');
}
if (wasInstalled(activePlugins, 'all-in-one-seo-pack')) {
await updateOption('aioseo_activation_redirect', 'skip');
}
if (wasInstalled(activePlugins, 'google-analytics-for-wordpress')) {
await updateOption(
'_transient__monsterinsights_activation_redirect',
null,
);
}
// Upload Logo
await uploadLogo(
'https://images.extendify-cdn.com/demo-content/logos/extendify-demo-logo.png',
);
await waitFor200Response();
const pagesWithLinksUpdated =
siteStructure === 'single-page'
? await updateSinglePageLinksToSections(
createdPages,
pagesWithCustomContent,
{
linkOverride: CTALink,
siteObjective,
},
)
: await updateButtonLinks(createdPages, pluginPages);
if (siteObjective !== 'landing-page') {
if (siteStructure === 'single-page') {
await addSectionLinksToNav(
navigationId,
homePage?.patterns,
pluginPages,
createdPages,
);
} else {
await addPageLinksToNav(
navigationId,
navPagesMultiPageSite,
pagesWithLinksUpdated,
pluginPages,
);
}
}
inform(__('Setting up your Site Assistant', 'extendify-local'));
informDesc(__('Helping you to succeed', 'extendify-local'));
await new Promise((resolve) => setTimeout(resolve, 1000));
await waitFor200Response();
inform(__('Your website has been created!', 'extendify-local'));
informDesc(__('Redirecting in 3, 2, 1...', 'extendify-local'));
// fire confetti here
setConfettiReady(true);
setWarnOnLeaveReady(false);
await new Promise((resolve) => setTimeout(resolve, 2500));
await waitFor200Response();
await updateOption(
'extendify_onboarding_completed',
new Date().toISOString(),
);
} catch (e) {
console.error(e);
// if the error is 4xx, we should stop trying and prompt them to reload
if (e.status >= 400 && e.status < 500) {
setWarnOnLeaveReady(false);
const alertMsg = __(
'We encountered a server error we cannot recover from. Please reload the page and try again.',
'extendify-local',
);
alert(alertMsg);
location.href = adminUrl;
}
await new Promise((resolve) => setTimeout(resolve, 2000));
return doEverything();
}
}, [
pages,
getGoalsPlugins,
style,
goals,
siteType,
siteInformation,
setPagesToAnimate,
siteStructure,
variation,
siteProfile,
siteStrings,
siteImages,
customFontFamilies,
setUserGaveConsent,
siteObjective,
CTALink,
]);
useEffect(() => {
doEverything().then(async () => {
setPage(0);
// This will trigger the post launch php functions.
await postLaunchFunctions();
window.location.replace(redirectUrl);
});
}, [doEverything, setPage, redirectUrl]);
useEffect(() => {
const documentStyles = window.getComputedStyle(document.body);
const partnerBg = documentStyles?.getPropertyValue('--ext-banner-main');
const partnerText = documentStyles?.getPropertyValue('--ext-banner-text');
if (partnerBg) {
setConfettiColors([
colord(partnerBg).darken(0.3).toHex(),
colord(partnerText).alpha(0.5).toHex(),
colord(partnerBg).lighten(0.2).toHex(),
]);
}
}, []);
useConfetti(
{
particleCount: 3,
angle: 320,
spread: 220,
origin: { x: 0, y: 0 },
colors: confettiColors,
},
2500,
confettiReady,
);
return (
<Transition
as="div"
show={isShowing}
appear={true}
enter="transition-all ease-in-out duration-500"
enterFrom="md:w-40vw md:max-w-md"
enterTo="md:w-full md:max-w-full"
className="flex shrink-0 flex-col justify-between bg-banner-main px-10 py-12 text-banner-text md:h-screen">
<div className="max-w-prose">
<div className="md:min-h-48">
{partnerLogo ? (
<div className="mb-8">
<img
style={{ maxWidth: '200px' }}
src={partnerLogo}
alt={partnerName ?? ''}
/>
</div>
) : (
<Logo className="logo mb-8 w-32 text-banner-text sm:w-40" />
)}
<div data-test="message-area">
{info.map((step, index) => {
if (!index) {
return (
<Transition
as="div"
appear={true}
show={isShowing}
enter="transition-opacity duration-1000"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-1000"
leaveFrom="opacity-100"
leaveTo="opacity-0"
className="flex items-center space-x-4 text-4xl"
key={step}>
{step}
</Transition>
);
}
})}
<div className="mt-6 flex items-center space-x-4">
<Spinner className="spin rtl:ml-3" />
{infoDesc.map((step, index) => {
if (!index) {
return (
<Transition
as="div"
appear={true}
show={isShowing}
enter="transition-opacity duration-1000"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-1000"
leaveFrom="opacity-100"
leaveTo="opacity-0"
className="text-lg"
key={step}>
{step}
</Transition>
);
}
})}
</div>
{pagesToAnimate.length > 0 ? (
<PagesSkeleton pages={pagesToAnimate} />
) : null}
</div>
</div>
</div>
</Transition>
);
};