diff --git a/composer.json b/composer.json index b8cfe748b..3d58bb239 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "drupal/acquia_cms_tour": "dev-develop", "drupal/acquia_drupal_starterkit_content_model": "dev-develop", "drupal/acquia_drupal_starterkit_headless": "dev-develop", + "drupal/acquia_drupal_starterkit_installer": "dev-develop", "drupal/acquia_drupal_starterkit_low_code": "dev-develop", "drupal/acquia_drupal_starterkit_media_model": "dev-develop", "drupal/consumer_image_styles": "^4.0", @@ -289,6 +290,15 @@ } } }, + "acquia_drupal_starterkit_installer": { + "type": "path", + "url": "./recipes/acquia_drupal_starterkit_installer", + "options": { + "versions": { + "drupal/acquia_drupal_starterkit_installer": "dev-develop" + } + } + }, "acquia_drupal_starterkit_low_code": { "type": "path", "url": "./recipes/acquia_drupal_starterkit_low_code", diff --git a/composer.lock b/composer.lock index 9909e1787..a64ab431b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cf4f58c9eaab6a8bf179aef95488b52f", + "content-hash": "5f2330b61420c4b4c4bf1cb6767a44f1", "packages": [ { "name": "acquia/acquia-cms-starterkit", @@ -3176,7 +3176,7 @@ "dist": { "type": "path", "url": "./recipes/acquia_drupal_starterkit_admin_theme", - "reference": "c47546c10a61c63ef69fd16c86803f9c8ba9c6c2" + "reference": "aa6622831585944cce6b1fd8f5fda0833c164c8f" }, "require": { "drupal/core": ">=10.3", @@ -3200,7 +3200,7 @@ "dist": { "type": "path", "url": "./recipes/acquia_drupal_starterkit_basic_html_editor", - "reference": "5b5ea122a3562d798af87541193578962cbe624f" + "reference": "6a14dca07ac402804cfef0e504beb4c17fb97487" }, "require": { "drupal/core": ">=10.3", @@ -3223,7 +3223,7 @@ "dist": { "type": "path", "url": "./recipes/acquia_drupal_starterkit_community", - "reference": "c9c7b8480c0b9210b394161d966589ff53f404ea" + "reference": "4155a0d5dab5c927c1bd500b57b2dade6bf9f62e" }, "require": { "drupal/acquia_cms_toolbar": "^1.5", @@ -3248,7 +3248,7 @@ "dist": { "type": "path", "url": "./recipes/acquia_drupal_starterkit_content_model", - "reference": "e6dc048e7033d2ea92f55b48113e3ce150e02c1a" + "reference": "8db5360414b8d49f8413871807424cfad1e02469" }, "require": { "drupal/acquia_cms_article": "^1.6", @@ -3275,7 +3275,7 @@ "dist": { "type": "path", "url": "./recipes/acquia_drupal_starterkit_full_html_editor", - "reference": "694af2f66b9786e5c084c8316ee35d38fef70f9a" + "reference": "02301358c65dd0e41edd5029e3f8b39c84de126d" }, "require": { "drupal/core": ">=10.3", @@ -3298,7 +3298,7 @@ "dist": { "type": "path", "url": "./recipes/acquia_drupal_starterkit_headless", - "reference": "f1f33130ea1722aea9a39a01ec72a20b94a3688c" + "reference": "6d4e3e5e1fd2c3fa57843628f4e5c5ea6e8f10c5" }, "require": { "drupal/acquia_cms_headless": "^1.3.17", @@ -3316,13 +3316,30 @@ "relative": true } }, + { + "name": "drupal/acquia_drupal_starterkit_installer", + "version": "dev-develop", + "dist": { + "type": "path", + "url": "./recipes/acquia_drupal_starterkit_installer", + "reference": "666fc351909021efb5f0b4fb507dac4666675503" + }, + "require": { + "drupal/core": ">=10.3" + }, + "type": "drupal-profile", + "description": "Provides install-time tweaks for Acquia Drupal Starterkit.", + "transport-options": { + "relative": true + } + }, { "name": "drupal/acquia_drupal_starterkit_low_code", "version": "dev-develop", "dist": { "type": "path", "url": "./recipes/acquia_drupal_starterkit_low_code", - "reference": "d0be078b62b2be72d8d9a47b8bda331a167ddf12" + "reference": "e9a8fa94bfb9c638202fe998ecb43176c8d3ef1c" }, "require": { "drupal/acquia_cms_site_studio": "^1.6", @@ -3346,7 +3363,7 @@ "dist": { "type": "path", "url": "./recipes/acquia_drupal_starterkit_media_model", - "reference": "6475eae278a8235deeafdef0c327468e3e30506b" + "reference": "34b6a93c83ecf17c50cfdbbc6e8732ff4452ae2d" }, "require": { "drupal/acquia_cms_audio": "^1.5", @@ -21767,6 +21784,7 @@ "drupal/acquia_cms_tour": 20, "drupal/acquia_drupal_starterkit_content_model": 20, "drupal/acquia_drupal_starterkit_headless": 20, + "drupal/acquia_drupal_starterkit_installer": 20, "drupal/acquia_drupal_starterkit_low_code": 20, "drupal/acquia_drupal_starterkit_media_model": 20, "drupal/gin": 5, diff --git a/recipes/acquia_drupal_starterkit_admin_theme/composer.json b/recipes/acquia_drupal_starterkit_admin_theme/composer.json index 1bb7fedc5..007a87a22 100644 --- a/recipes/acquia_drupal_starterkit_admin_theme/composer.json +++ b/recipes/acquia_drupal_starterkit_admin_theme/composer.json @@ -2,7 +2,7 @@ "name": "drupal/acquia_drupal_starterkit_admin_theme", "type": "drupal-recipe", "description": "Sets up a nice administrative theme and navigation.", - "version": "dev-main", + "version": "dev-develop", "require": { "drupal/core": ">=10.3", "drupal/gin": "^3-rc11", diff --git a/recipes/acquia_drupal_starterkit_basic_html_editor/composer.json b/recipes/acquia_drupal_starterkit_basic_html_editor/composer.json index f9c3971eb..a98c4cf04 100644 --- a/recipes/acquia_drupal_starterkit_basic_html_editor/composer.json +++ b/recipes/acquia_drupal_starterkit_basic_html_editor/composer.json @@ -2,7 +2,7 @@ "name": "drupal/acquia_drupal_starterkit_basic_html_editor", "type": "drupal-recipe", "description": "Enhances the Basic HTML editor with better linking and media support.", - "version": "dev-main", + "version": "dev-develop", "require": { "drupal/core": ">=10.3", "drupal/linkit": "^6.1.4" diff --git a/recipes/acquia_drupal_starterkit_community/composer.json b/recipes/acquia_drupal_starterkit_community/composer.json index ba56cc4e8..af9e5a327 100644 --- a/recipes/acquia_drupal_starterkit_community/composer.json +++ b/recipes/acquia_drupal_starterkit_community/composer.json @@ -2,6 +2,7 @@ "name": "drupal/acquia_drupal_starterkit_community", "type": "drupal-recipe", "description": "The community starter kit will install Acquia CMS. An optional content model can be added in the installation process.", + "version": "dev-develop", "require": { "drupal/core": ">=10.3", "drupal/acquia_cms_toolbar": "^1.5", diff --git a/recipes/acquia_drupal_starterkit_content_model/composer.json b/recipes/acquia_drupal_starterkit_content_model/composer.json index 9951b4c20..d45bf1e29 100644 --- a/recipes/acquia_drupal_starterkit_content_model/composer.json +++ b/recipes/acquia_drupal_starterkit_content_model/composer.json @@ -2,6 +2,7 @@ "name": "drupal/acquia_drupal_starterkit_content_model", "type": "drupal-recipe", "description": "An media model provides Audio, Document, Image< Video media types.", + "version": "dev-develop", "require": { "drupal/core": ">=10.3", "drupal/acquia_cms_article": "^1.6", diff --git a/recipes/acquia_drupal_starterkit_full_html_editor/composer.json b/recipes/acquia_drupal_starterkit_full_html_editor/composer.json index a3c9e9384..822cfb879 100644 --- a/recipes/acquia_drupal_starterkit_full_html_editor/composer.json +++ b/recipes/acquia_drupal_starterkit_full_html_editor/composer.json @@ -2,7 +2,7 @@ "name": "drupal/acquia_drupal_starterkit_full_html_editor", "type": "drupal-recipe", "description": "Enhances the Full HTML editor with better linking and media support.", - "version": "dev-main", + "version": "dev-develop", "require": { "drupal/core": ">=10.3", "drupal/linkit": "^6.1.4" diff --git a/recipes/acquia_drupal_starterkit_headless/composer.json b/recipes/acquia_drupal_starterkit_headless/composer.json index 0a591bd07..46222c16b 100644 --- a/recipes/acquia_drupal_starterkit_headless/composer.json +++ b/recipes/acquia_drupal_starterkit_headless/composer.json @@ -2,6 +2,7 @@ "name": "drupal/acquia_drupal_starterkit_headless", "type": "drupal-recipe", "description": "The community starter kit will install Acquia CMS. An optional content model can be added in the installation process.", + "version": "dev-develop", "require": { "drupal/core": ">=10.3", "drupal/acquia_cms_headless": "^1.3.17", diff --git a/recipes/acquia_drupal_starterkit_installer/acquia_drupal_starterkit_installer.info.yml b/recipes/acquia_drupal_starterkit_installer/acquia_drupal_starterkit_installer.info.yml new file mode 100644 index 000000000..5f2513d9a --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/acquia_drupal_starterkit_installer.info.yml @@ -0,0 +1,10 @@ +name: Acquia Drupal Starterkit Installer +type: profile +core_version_requirement: '>=10.3' +description: 'Provides install-time tweaks for Acquia Drupal Starterkit.' +# Acquia Drupal Starterkit isn't a distribution, but we need to use this +# `distribution` key in order to skip the installer's profile selection step. +distribution: + name: Acquia Drupal Starterkit + install: + finish_url: '/admin/dashboard/welcome' diff --git a/recipes/acquia_drupal_starterkit_installer/acquia_drupal_starterkit_installer.profile b/recipes/acquia_drupal_starterkit_installer/acquia_drupal_starterkit_installer.profile new file mode 100644 index 000000000..5142ddea5 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/acquia_drupal_starterkit_installer.profile @@ -0,0 +1,284 @@ +addPsr4('Drupal\\acquia_drupal_starterkit_installer\\', __DIR__ . '/src'); + + return [ + 'acquia_drupal_starterkit_installer_prepare_trial' => [ + 'run' => getenv('DRUPAL_CMS_TRIAL') ? INSTALL_TASK_RUN_IF_REACHED : INSTALL_TASK_SKIP, + ], + 'acquia_drupal_starterkit_installer_uninstall_myself' => [ + // As a final task, this profile should uninstall itself. + ], + ]; +} + +/** + * Implements hook_install_tasks_alter(). + */ +function acquia_drupal_starterkit_installer_install_tasks_alter(array &$tasks, array $install_state): void { + $insert_before = function (string $key, array $additions) use (&$tasks): void { + $key = array_search($key, array_keys($tasks), TRUE); + if ($key === FALSE) { + return; + } + // This isn't very clean, but it's the only way to positionally splice into + // an associative (and therefore by definition unordered) array. + $tasks_before = array_slice($tasks, 0, $key, TRUE); + $tasks_after = array_slice($tasks, $key, NULL, TRUE); + $tasks = $tasks_before + $additions + $tasks_after; + }; + $insert_before('install_settings_form', [ + 'acquia_drupal_starterkit_installer_choose_recipes' => [ + 'display_name' => t('Choose add-ons'), + 'type' => 'form', + 'run' => array_key_exists('recipes', $install_state['parameters']) ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_REACHED, + 'function' => RecipesForm::class, + ], + 'acquia_drupal_starterkit_installer_site_name_form' => [ + 'display_name' => t('Name your site'), + 'type' => 'form', + 'run' => array_key_exists('site_name', $install_state['parameters']) ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_REACHED, + 'function' => SiteNameForm::class, + ], + ]); + + // Set English as the default language; it can be changed mid-stream. We can't + // use the passed-in $install_state because it's not passed by reference. + $GLOBALS['install_state']['parameters'] += ['langcode' => 'en']; + + // The database settings form should be submitted programmatically in the + // trial experience. + $tasks['install_settings_form']['function'] = 'acquia_drupal_starterkit_installer_database_settings'; + unset($tasks['install_settings_form']['type']); + + // Submit the site configuration form programmatically. + $tasks['install_configure_form'] = [ + 'function' => 'acquia_drupal_starterkit_installer_configure_site', + ]; + + // Wrap the install_profile_modules() function, which returns a batch job, and + // add all the necessary operations to apply the chosen template recipe. + $tasks['install_profile_modules']['function'] = 'acquia_drupal_starterkit_installer_apply_recipes'; + + // Since we're using recipes, we can skip `install_profile_themes` and + // `install_install_profile`. + $tasks['install_profile_themes']['run'] = INSTALL_TASK_SKIP; + $tasks['install_install_profile']['run'] = INSTALL_TASK_SKIP; +} + +/** + * Implements hook_form_alter() for install_settings_form. + * + * @see \Drupal\Core\Installer\Form\SiteSettingsForm + */ +function acquia_drupal_starterkit_installer_form_install_settings_form_alter(array &$form): void { + // Default to SQLite, if available, because it doesn't require any additional + // configuration. + $sqlite = 'Drupal\sqlite\Driver\Database\sqlite'; + if (array_key_exists($sqlite, $form['driver']['#options']) && extension_loaded('pdo_sqlite')) { + $form['driver']['#default_value'] = $sqlite; + } +} + +/** + * Runs a batch job that applies the template and add-on recipes. + * + * @param array $install_state + * An array of information about the current installation state. + * + * @return array + * The batch job definition. + */ +function acquia_drupal_starterkit_installer_apply_recipes(array &$install_state): array { + $batch = install_profile_modules($install_state); + $batch['title'] = t('Setting up your site'); + + // If we're installing for the trial, install the drupal_cms_trial module. + if (getenv('DRUPAL_CMS_TRIAL')) { + $batch['operations'][] = ['_install_module_batch', ['drupal_cms_trial', t('Trial experience module')]]; + } + + $cookbook_path = \Drupal::root() . '/recipes'; + + foreach ($install_state['parameters']['recipes'] as $recipe) { + $recipe = Recipe::createFromDirectory($cookbook_path . '/' . $recipe); + + foreach (RecipeRunner::toBatchOperations($recipe) as $operation) { + $batch['operations'][] = $operation; + } + } + return $batch; +} + +/** + * Programmatically submits the database settings form if needed. + */ +function acquia_drupal_starterkit_installer_database_settings(array &$install_state): ?array { + $interactive = $install_state['interactive']; + // If we're installing the in-browser trial, submit the form programmatically + // with default values which, thanks to + // acquia_drupal_starterkit_installer_form_install_settings_form_alter(), should be the + // SQLite driver with its default options. + $install_state['interactive'] = getenv('DRUPAL_CMS_TRIAL') ? FALSE : $interactive; + $result = install_get_form(SiteSettingsForm::class, $install_state); + $install_state['interactive'] = $interactive; + + return $result; +} + +/** + * Programmatically executes core's site configuration form. + */ +function acquia_drupal_starterkit_installer_configure_site(array &$install_state): ?array { + $random_password = (new Random())->machineName(); + $host = \Drupal::request()->getHost(); + + $install_state['forms'] += [ + 'install_configure_form' => [ + 'site_name' => $install_state['parameters']['site_name'], + 'site_mail' => "no-reply@$host", + 'account' => [ + 'name' => 'admin', + 'mail' => "admin@$host", + 'pass' => [ + 'pass1' => $random_password, + 'pass2' => $random_password, + ], + ], + ], + ]; + // Temporarily switch to non-interactive mode and programmatically submit + // the form. + $interactive = $install_state['interactive']; + $install_state['interactive'] = FALSE; + $result = install_get_form(SiteConfigureForm::class, $install_state); + $install_state['interactive'] = $interactive; + + $messenger = \Drupal::messenger(); + // Clear all previous status messages to avoid clutter. + $messenger->deleteByType($messenger::TYPE_STATUS); + + $message = t('Make a note of your login details to access your site later:
Username: admin
Password: @password', [ + '@password' => $install_state['forms']['install_configure_form']['account']['pass']['pass1'], + ]); + $messenger->addStatus($message); + + return $result; +} + +/** + * Implements hook_library_info_alter(). + */ +function acquia_drupal_starterkit_installer_library_info_alter(array &$libraries, string $extension): void { + global $install_state; + // If a library file's path starts with `/`, the library collection system + // treats it as relative to the base path. + // @see \Drupal\Core\Asset\LibraryDiscoveryParser::buildByExtension() + $base_path = '/' . $install_state['profiles']['acquia_drupal_starterkit_installer']->getPath(); + + if ($extension === 'claro') { + $libraries['maintenance-page']['css']['theme']["$base_path/css/gin-variables.css"] = []; + $libraries['maintenance-page']['css']['theme']["$base_path/css/fonts.css"] = []; + $libraries['maintenance-page']['css']['theme']["$base_path/css/installer-styles.css"] = []; + $libraries['maintenance-page']['css']['theme']["$base_path/css/add-ons.css"] = []; + $libraries['maintenance-page']['css']['theme']["$base_path/css/language-dropdown.css"] = []; + $libraries['maintenance-page']['js']["$base_path/js/language-dropdown.js"] = []; + $libraries['maintenance-page']['dependencies'][] = 'core/once'; + } + if ($extension === 'core') { + $libraries['drupal.progress']['js']["$base_path/js/progress.js"] = []; + } +} + +/** + * Makes configuration changes needed for the in-browser trial. + */ +function acquia_drupal_starterkit_installer_prepare_trial(): void { + // Use a test mail collector, since the trial won't have access to sendmail. + \Drupal::configFactory() + ->getEditable('system.mail') + ->set('interface.default', 'test_mail_collector') + ->save(); + + // Disable CSS and JS aggregation. + \Drupal::configFactory() + ->getEditable('system.performance') + ->set('css.preprocess', FALSE) + ->set('js.preprocess', FALSE) + ->save(); + + // Enable verbose logging. + \Drupal::configFactory() + ->getEditable('system.logging') + ->set('error_level', 'verbose') + ->save(); + + // Disable things that the WebAssembly runtime doesn't (yet) support, like + // running external processes or making HTTP requests. + // @todo revisit once php-wasm maps HTTP requests from PHP to Fetch API. + \Drupal::service(ModuleInstallerInterface::class)->uninstall([ + 'automatic_updates', + 'update', + ]); + \Drupal::configFactory() + ->getEditable('project_browser.admin_settings') + ->set('allow_ui_install', FALSE) + ->save(); +} + +/** + * Uninstalls this install profile, as a final step. + * + * @see drupal_install_system() + */ +function acquia_drupal_starterkit_installer_uninstall_myself(): void { + // `drupal_install_system()` sets `profile` in `core.extension` regardless + // of whether the profile is actually installed by the module installer. + \Drupal::configFactory() + ->getEditable('core.extension') + ->clear('profile') + ->save(); +} + +/** + * Implements hook_theme_registry_alter(). + */ +function acquia_drupal_starterkit_installer_theme_registry_alter(array &$hooks): void { + global $install_state; + $installer_path = $install_state['profiles']['acquia_drupal_starterkit_installer']->getPath(); + + $hooks['install_page']['path'] = $installer_path . '/templates'; +} + +/** + * Preprocess function for all pages in the installer. + */ +function acquia_drupal_starterkit_installer_preprocess_install_page(array &$variables): void { + // Don't show the task list or the version of Drupal. + unset($variables['page']['sidebar_first'], $variables['site_version']); + + global $install_state; + $images_path = $install_state['profiles']['acquia_drupal_starterkit_installer']->getPath() . '/images'; + $images_path = \Drupal::service(FileUrlGeneratorInterface::class) + ->generateString($images_path); + $variables['images_path'] = $images_path; +} diff --git a/recipes/acquia_drupal_starterkit_installer/composer.json b/recipes/acquia_drupal_starterkit_installer/composer.json new file mode 100644 index 000000000..eab8583b2 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/composer.json @@ -0,0 +1,9 @@ +{ + "name": "drupal/acquia_drupal_starterkit_installer", + "type": "drupal-profile", + "description": "Provides install-time tweaks for Acquia Drupal Starterkit.", + "version": "dev-develop", + "require": { + "drupal/core": ">=10.3" + } +} diff --git a/recipes/acquia_drupal_starterkit_installer/css/add-ons.css b/recipes/acquia_drupal_starterkit_installer/css/add-ons.css new file mode 100644 index 000000000..a966205e2 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/css/add-ons.css @@ -0,0 +1,74 @@ +#edit-add-ons { + display: flex; + flex-wrap: wrap; + gap: 10px 20px; +} + +#edit-add-ons .form-item { + display: inline-block; + margin: 0; +} + +#edit-add-ons [type="checkbox"] { + position: absolute; + overflow: hidden; + clip: rect(1px, 1px, 1px, 1px); + width: 1px; + height: 1px; + word-wrap: normal; +} + +#edit-add-ons label { + display: inline-block; + color: #39353e; + padding: 15px 20px; + border: solid 2px #666; + font-size: 26px; + border-radius: 10px; + user-select: none; +} + +#edit-add-ons label:hover { + background-color: #f8fafe; + color: black; +} + +#edit-add-ons :checked + label { + position: relative; + background-color: var(--gin-color-primary); + border-color: var(--gin-color-primary); + color: white; +} + +#edit-add-ons :checked+label:before { + position: absolute; + top: 0; + inset-inline-start: 0; + display: block; + width: 40px; + aspect-ratio: 1; + transform: translate(-50%, -50%); /* RTL */ + content: url('../images/check-circle.svg'); + background-color: white; + border-radius: 50%; +} + +@media (forced-colors: active) { + #edit-add-ons :checked+label:before { + display: flex; + align-items: center; + justify-content: center; + content: "✓"; + border: solid 2px; + font-weight: bold; + } +} + +[dir="rtl"] #edit-add-ons :checked+label:before { + transform: translate(50%, -50%); +} + +#edit-add-ons [type="checkbox"]:focus + label { + outline: 3px solid var(--color-focus); + outline-offset: 2px; +} diff --git a/recipes/acquia_drupal_starterkit_installer/css/fonts.css b/recipes/acquia_drupal_starterkit_installer/css/fonts.css new file mode 100644 index 000000000..e646a41b1 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/css/fonts.css @@ -0,0 +1,139 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-italic-cyrillic-ext.woff2') format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* cyrillic */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-italic-cyrillic.woff2') format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* greek-ext */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-italic-greek-ext.woff2') format('woff2'); + unicode-range: U+1F00-1FFF; +} + +/* greek */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-italic-greek.woff2') format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} + +/* vietnamese */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-italic-vietnamese.woff2') format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} + +/* latin-ext */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-italic-latin-ext.woff2') format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-italic-latin.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* cyrillic-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-cryllic-ext.woff2') format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* cyrillic */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-cyrillic.woff2') format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* greek-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-greek-ext.woff2') format('woff2'); + unicode-range: U+1F00-1FFF; +} + +/* greek */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-greek.woff2') format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} + +/* vietnamese */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-vietnamese.woff2') format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} + +/* latin-ext */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-latin-ext.woff2') format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100 900; + font-display: block; + src: url('../fonts/inter-latin.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/recipes/acquia_drupal_starterkit_installer/css/gin-variables.css b/recipes/acquia_drupal_starterkit_installer/css/gin-variables.css new file mode 100644 index 000000000..a2f2f8d91 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/css/gin-variables.css @@ -0,0 +1,275 @@ +/** + * @file + * CSS variables for the Acquia Drupal Starterkit Installer. Mostly copied from Gin, with + * modifications. + */ + +:root { + + --color-focus: var(--gin-color-focus); + --gin-color-primary: rgb(var(--gin-color-primary-rgb)); + --gin-color-primary-rgb: 5, 80, 230; + --gin-color-primary-light-rgb: 205, 220, 250; + --gin-color-primary-hover: #0444c4; + --gin-color-primary-active: #043cad; + --gin-bg-app-rgb: 248, 250, 254; + --gin-bg-header: #e1eafc; + --gin-color-sticky-rgb: 235, 241, 253; + + --gin-color-title: #222330; + --gin-color-text: #222330; + --gin-color-text-light: #545560; + --gin-color-focus: rgba(0, 125, 250, .6); + --gin-color-focus-border: rgba(0, 0, 0, .2); + --gin-color-focus-neutral-rgb: rgba(0, 0, 0, .4); + --gin-color-disabled: #8d8d8d; + --gin-color-disabled-bg: #eaeaea; + --gin-color-disabled-border: #c2c2c2; + --gin-color-warning: #d8b234; + --gin-color-warning-light: #efcf64; + --gin-bg-warning: #605328; + --gin-bg-warning-light: rgba(226, 151, 0, .08); + --gin-color-danger: #cc3d3d; + --gin-color-danger-lightest: #fdd9d9; + --gin-color-danger-light: #f39b9d; + --gin-bg-danger: #583333; + --gin-bg-danger-light: rgba(222, 117, 96, .1); + --gin-color-green: #058260; + --gin-color-green-light: #32cea4; + --gin-color-green-lightest: #adebdb; + --gin-bg-green: #145242; + --gin-bg-green-light: rgba(72, 171, 123, .1); + --gin-color-info: #082538; + --gin-color-info-light: #589ac5; + --gin-bg-info: #122b3c; + --gin-status-text: #777; + --gin-status-bg: #eee; + --gin-status-success-text: #1d7c4e; + --gin-status-success-bg: #26a76930; + --gin-status-warning-text: #826b1f; + --gin-status-warning-bg: rgba(226, 151, 0, .15); + --gin-status-danger-text: #cc3d3d; + --gin-status-danger-bg: rgba(222, 117, 96, .15); + --gin-color-contextual: var(--gin-color-text); + --gin-color-contextual-text: #eee; + --gin-bg-input: #fff; + --gin-bg-layer: #fff; + --gin-bg-layer2: #edeff5; + --gin-bg-layer3: #fff; + --gin-bg-layer4: #e2e5ec; + --gin-bg-secondary: var(--gin-bg-layer); + --gin-bg-header: #eeeff3; + --gin-bg-unpublished: var(--gin-bg-danger-light); + --gin-pattern: var(--gin-border-color); + --gin-pattern-fallback: var(--gin-bg-layer2); + --gin-pattern-square: .5rem; + --gin-font: Ginter, Inter, "Helvetica Neue", BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, sans-serif; + --gin-font-size-xxs: .75rem; + --gin-font-size-xs: small; + --gin-font-size-s: .875rem; + --gin-font-size: 1rem; + --gin-font-size-m: var(--gin-font-size); + --gin-font-size-l: 1.125rem; + --gin-font-size-xl: 1.25rem; + --gin-font-size-h3: 1.5rem; + --gin-font-size-h2: 1.75rem; + --gin-font-size-h1: 1.6rem; + --gin-font-size-quote: 1.1em; + --gin-font-weight-normal: 400; + --gin-font-weight-semibold: 525; + --gin-font-weight-bold: 575; + --gin-font-weight-heavy: 625; + --gin-spacing-xxxs: .125rem; + --gin-spacing-xxs: .25rem; + --gin-spacing-xs: .5rem; + --gin-spacing-s: .75rem; + --gin-spacing-m: 1rem; + --gin-spacing-l: 1.5rem; + --gin-spacing-xl: 2rem; + --gin-spacing-xxl: 3rem; + --gin-spacing-xxxl: 4rem; + --gin-icon-color: #414247; + --gin-icon-size-close: 20px; + --gin-icon-size-toolbar-secondary: 17px; + --gin-icon-size-toolbar: 17px; + --gin-icon-size-sidebar-toggle: 21px; + --gin-border-xxs: .125rem; + --gin-border-xs: .25rem; + --gin-border-s: .375rem; + --gin-border-m: .5rem; + --gin-border-l: .75rem; + --gin-border-xl: 1rem; + --gin-border-color: #d4d4d8; + --gin-border-color-secondary: rgba(0, 0, 0, .08); + --gin-border-color-layer: rgba(0, 0, 0, .08); + --gin-border-color-layer2: #d4d4d8; + --gin-border-color-table: rgba(0, 0, 0, .1); + --gin-border-color-table-header: rgba(0, 0, 0, .3); + --gin-border-color-form-element: #8e929c; + --size-summary-border-radius: calc(var(--gin-border-m) - 1px); + --gin-easing: cubic-bezier(.19, 1, .22, 1); + --gin-transition: .15s var(--gin-easing); + --gin-transition-fast: .3s var(--gin-easing); + --gin-shadow-l1: 0 1px 2px rgb(20 45 82 / 2%), 0 3px 4px rgb(20 45 82 / 3%), 0 5px 8px rgb(20 45 82 / 4%); + --gin-shadow-l2: 0 1px 2px rgb(20 45 82 / 2%), 0 3px 4px rgb(20 45 82 / 3%), 0 5px 8px rgb(20 45 82 / 4%), 0 20px 24px rgb(20 45 82 / 12%); + --gin-height-sticky: 60px; + --gin-toolbar-width-collapsed: 66px; + --gin-toolbar-width: 256px; + --gin-toolbar-height: 0px; + --gin-toolbar-secondary-height: 0px; + --gin-toolbar-bg-level2: #edeff5; + --gin-toolbar-bg-level3: rgba(44, 45, 47, .05); + --gin-toolbar-y-offset: 0px; + --gin-toolbar-x-offset: 0px; + --gin-scroll-offset: 0px; + --gin-sticky-offset: 0px; + --gin-sidebar-small-width: 320px; + --gin-sidebar-min-width: 240px; + --gin-sidebar-width: 320px; + --gin-sidebar-max-width: 560px; + --gin-sidebar-offset: var(--gin-sidebar-width); + --gin-switch: #26a769; + --gin-shadow-button: #00000033; + --gin-color-button-text: #fff; + --gin-offset-x: var(--gin-toolbar-x-offset); + --gin-offset-y: calc(var(--gin-toolbar-y-offset) + var(--gin-sticky-offset)); + --gin-link-decoration-style: dotted; + --gin-max-line-length: 80ch; + --input-line-height: var(--gin-spacing-l); + --input-padding-horizontal: var(--gin-spacing-s); + --input-padding-vertical: var(--gin-spacing-xs); + --gin-tooltip-bg: #232429; + --jui-dialog-z-index: 1260; +} + +@media (min-width: 61em) { + :root { + --gin-font-size-h1: 1.8125rem; + --gin-font-size-quote: 1.2em; + } +} + +@media (min-width: 90em) { + :root { + --gin-font-size-h1: 2.125rem; + } +} + +@media (min-width: 61em) { + :root { + --gin-icon-size-toolbar: 20px; + } +} + +@media (min-width: 64em) { + :root { + --gin-sticky-offset: var(--gin-height-sticky); + } +} + +@media (min-width: 80em) { + :root { + --gin-sidebar-width: 360px; + } +} + +[data-gin-layout-density=small] { + --gin-spacing-density-xxs: .15625rem; + --gin-spacing-density-xs: .3125rem; + --gin-spacing-density-s: .46875rem; + --gin-spacing-density-m: .625rem; + --gin-spacing-density-l: .9375rem; + --gin-spacing-density-xl: 1.25rem; + --gin-spacing-density-xxl: 1.875rem; + --gin-spacing-density-xxxl: 2.5rem; +} + +[data-gin-layout-density=medium] { + --gin-spacing-density-xxs: .1875rem; + --gin-spacing-density-xs: .375rem; + --gin-spacing-density-s: .5625rem; + --gin-spacing-density-m: .75rem; + --gin-spacing-density-l: 1.125rem; + --gin-spacing-density-xl: 1.5rem; + --gin-spacing-density-xxl: 2.25rem; + --gin-spacing-density-xxxl: 3rem; +} + +:root { + --gin-spacing-density-xxs: .25rem; + --gin-spacing-density-xs: .5rem; + --gin-spacing-density-s: .75rem; + --gin-spacing-density-m: 1rem; + --gin-spacing-density-l: 1.5rem; + --gin-spacing-density-xl: 2rem; + --gin-spacing-density-xxl: 3rem; + --gin-spacing-density-xxxl: 4rem; +} + +.gin--dark-mode { + --gin-color-title: #fff; + --gin-color-text: #d2d3d3; + --gin-color-text-light: #9e9fa0; + --gin-shadow-button: rgba(#111, .9); + --gin-color-button-text: #111; + --gin-color-focus: rgb(81, 168, 255); + --gin-color-focus-border: rgba(0, 0, 0, .8); + --gin-color-focus-neutral-rgb: rgba(255, 255, 255, .8); + --gin-color-disabled: #646464; + --gin-color-disabled-border: #646464; + --gin-color-disabled-bg: #47474c; + --gin-color-warning: #dec15f; + --gin-bg-warning-light: rgba(222, 193, 95, .1); + --gin-color-danger: #ce6060; + --gin-color-danger-lightest: #483439; + --gin-color-green: #32cea4; + --gin-color-info: #559bca; + --gin-bg-input: var(--gin-bg-layer2); + --gin-bg-app: #1b1b1d; + --gin-bg-layer: #2a2a2d; + --gin-bg-layer2: #3b3b3f; + --gin-bg-layer3: #47474c; + --gin-bg-layer4: #19191b; + --gin-bg-secondary: var(--gin-bg-app); + --gin-bg-unpublished: var(--gin-bg-warning-light); + --gin-color-contextual: var(--gin-bg-layer3); + --gin-border-color: #43454a; + --gin-border-color-secondary: rgba(255, 255, 255, .075); + --gin-border-color-layer: rgba(0, 0, 0, .05); + --gin-border-color-layer2: #76777b; + --gin-border-color-table: #43454a; + --gin-border-color-table-header: rgba(255, 255, 255, .12); + --gin-border-color-form-element: var(--gin-border-color-layer2); + --gin-bg-header: #1b1b1d; + --gin-switch: var(--gin-color-primary); + --gin-status-text: var(--gin-color-text-light); + --gin-status-bg: rgba(255, 255, 255, .1); + --gin-status-success-text: #39b77b; + --gin-status-success-bg: #26a76930; + --gin-status-warning-text: #e8d185; + --gin-status-warning-bg: rgba(226, 151, 0, .15); + --gin-status-danger-text: #e69e9e; + --gin-status-danger-bg: rgba(222, 117, 96, .15); + --gin-shadow-l1: 0 1px 2px rgb(0 0 0 / 2%), 0 3px 4px rgb(0 0 0 / 3%), 0 5px 8px rgb(0 0 0 / 4%); + --gin-shadow-l2: 0 1px 2px rgb(0 0 0 / 2%), 0 3px 4px rgb(0 0 0 / 3%), 0 5px 8px rgb(0 0 0 / 4%), 0 20px 24px rgb(0 0 0 / 12%); + --gin-icon-color: #888; + --gin-pattern-fallback: var(--gin-bg-layer2); + --gin-pattern: var(--gin-border-color); + --gin-tooltip-bg: var(--gin-bg-layer3); +} + +@media (forced-colors: active) { + :root { + --gin-icon-color: CanvasText; + } +} + +.entity-meta { + --entity-meta-color-bg: transparent; + --entity-meta-border-color: var(--gin-border-color); +} + +.accordion { + --accordion-bg-color: transparent; + --accordion-border-color: var(--gin-border-color); +} diff --git a/recipes/acquia_drupal_starterkit_installer/css/installer-styles.css b/recipes/acquia_drupal_starterkit_installer/css/installer-styles.css new file mode 100644 index 000000000..e8f99fb23 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/css/installer-styles.css @@ -0,0 +1,171 @@ +/** + * @file + * This file specifically overrides Claro's maintenance page styling to make + * tweaks specific to the Acquia Drupal Starterkit installer. None of this needs to be + * reusable. + */ + +@view-transition { + navigation: auto; +} + +*, *::before, *::after { + box-sizing: border-box; + -webkit-font-smoothing: antialiased; +} + +a { + color: var(--gin-color-primary); +} + +.install-page { + background: linear-gradient(0.33turn, #ccbaf4, #22a5e5); +} + +.cms-installer { + display: flex; + width: 75%; + max-width: 1400px; + margin: 0 auto; + border: 1px solid transparent; + border-radius: 0.5rem; + background: #fff; + box-shadow: var(--shadow-z3); + font-family: "Inter", system-ui; +} + +.cms-installer__first { + flex: 1; + padding: 100px; +} + +.cms-installer__second { + position: relative; /* Anchor image. */ + flex: 1; +} + +.cms-installer__bg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border-radius: 0 0.5rem 0.5rem 0; + object-fit: cover; + object-position: 90% center; +} + +.cms-installer__header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + margin-bottom: 50px; +} + +.cms-installer .cms-installer__heading { + font-size: 48px; + font-weight: 900; +} + +.cms-installer .cms-installer__heading-secondary { + font-size: 36px; + opacity: 0.7; +} + +.cms-installer__language-button { + display: inline-flex; + align-items: center; + justify-content: space-between; + gap: 10px; + appearance: none; + border: 0; + background: transparent; + font-weight: 600; + cursor: pointer; + + &:before { + display: inline-block; + width: 20px; + aspect-ratio: 1; + content: ""; + background-color: currentColor; + -webkit-mask-image: url('../images/translate.svg'); + mask-image: url('../images/translate.svg'); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-size: contain; + mask-size: contain; + } +} + +.cms-installer .cms-installer__main-heading { + font-size: 62px; + font-weight: 900; + line-height: 1; +} + +.cms-installer__subhead { + font-size: 25px; + color: #484848; +} + +.cms-installer .button { + all: revert; + display: inline-flex; + align-items: center; + flex-wrap: wrap; + height: 78px; + padding-inline: 30px; + border: solid 1px transparent; + background: transparent; + font-family: inherit; + font-size: 20px; + font-weight: 400; + text-decoration: underline; + box-shadow: none; + border-radius: 5px; + cursor: pointer; +} +.cms-installer .button:focus { + outline: 3px solid var(--color-focus); + outline-offset: 2px; +} + +.cms-installer .button + .button { + margin-inline-start: 10px; +} + +.cms-installer .button--primary { + padding: calc(var(--gin-spacing-s) - 2px) var(--gin-spacing-m); + border-radius: var(--gin-border-m); + box-shadow: 0 1px 2px var(--gin-color-primary-light); + transition: var(--gin-transition); + padding-inline: 50px; + background-color: var(--gin-color-primary); + color: var(--gin-color-button-text); + text-decoration: none; +} + +.cms-installer .button--primary:active { + background-color: var(--gin-color-primary-active); +} + +.cms-installer .button--primary:hover { + background-color: var(--gin-color-primary-hover); +} + +.cms-installer__form-group { + margin-block: 50px; +} + +.progress { + width: fit-content; + margin: 50px auto; +} + +.progress__percentage { + font-size: 16px; + font-weight: 600; +} diff --git a/recipes/acquia_drupal_starterkit_installer/css/language-dropdown.css b/recipes/acquia_drupal_starterkit_installer/css/language-dropdown.css new file mode 100644 index 000000000..a7f17c98a --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/css/language-dropdown.css @@ -0,0 +1,38 @@ +.cms-installer__language { + position: relative; /* Anchor dropdown. */ +} + +.cms-installer__language-dropdown { + position: absolute; + top: 100%; + left: 50%; + z-index: 1; + translate: -50% 0; + visibility: hidden; + opacity: 0; + width: min(90vw, 1000px); + padding: 20px; + background: white; + border: solid 1px transparent; + border-radius: 0.5rem; + box-shadow: 0 40px 40px #00000088; + transition: all 0.2s; +} + +.cms-installer__language-button[aria-expanded="true"] + .cms-installer__language-dropdown { + visibility: visible; + opacity: 1; +} + +.cms-installer__language-list { + margin: 0; + padding: 0; + list-style: none; + column-width: 150px; + column-gap: 10px; + +} + +.cms-installer__list-item { + margin-bottom: 8px; +} diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-cryllic-ext.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-cryllic-ext.woff2 new file mode 100644 index 000000000..1029b2511 Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-cryllic-ext.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-cyrillic.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-cyrillic.woff2 new file mode 100644 index 000000000..4a88c023d Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-cyrillic.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-greek-ext.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-greek-ext.woff2 new file mode 100644 index 000000000..bbcbf2702 Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-greek-ext.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-greek.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-greek.woff2 new file mode 100644 index 000000000..1339c681c Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-greek.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-cyrillic-ext.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-cyrillic-ext.woff2 new file mode 100644 index 000000000..83e50e1ed Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-cyrillic-ext.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-cyrillic.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-cyrillic.woff2 new file mode 100644 index 000000000..87a694984 Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-cyrillic.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-greek-ext.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-greek-ext.woff2 new file mode 100644 index 000000000..ea4c043b8 Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-greek-ext.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-greek.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-greek.woff2 new file mode 100644 index 000000000..05fae388e Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-greek.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-latin-ext.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-latin-ext.woff2 new file mode 100644 index 000000000..b1fae490f Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-latin-ext.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-latin.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-latin.woff2 new file mode 100644 index 000000000..ac7e0de6b Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-latin.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-vietnamese.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-vietnamese.woff2 new file mode 100644 index 000000000..ddc6badf4 Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-italic-vietnamese.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-latin-ext.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-latin-ext.woff2 new file mode 100644 index 000000000..82e134726 Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-latin-ext.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-latin.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-latin.woff2 new file mode 100644 index 000000000..e0cab47a8 Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-latin.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/fonts/inter-vietnamese.woff2 b/recipes/acquia_drupal_starterkit_installer/fonts/inter-vietnamese.woff2 new file mode 100644 index 000000000..e9057325d Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/fonts/inter-vietnamese.woff2 differ diff --git a/recipes/acquia_drupal_starterkit_installer/images/check-circle.svg b/recipes/acquia_drupal_starterkit_installer/images/check-circle.svg new file mode 100644 index 000000000..bfd46c389 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/images/check-circle.svg @@ -0,0 +1 @@ + diff --git a/recipes/acquia_drupal_starterkit_installer/images/cms-installer-bg.webp b/recipes/acquia_drupal_starterkit_installer/images/cms-installer-bg.webp new file mode 100644 index 000000000..bcd3530d3 Binary files /dev/null and b/recipes/acquia_drupal_starterkit_installer/images/cms-installer-bg.webp differ diff --git a/recipes/acquia_drupal_starterkit_installer/images/translate.svg b/recipes/acquia_drupal_starterkit_installer/images/translate.svg new file mode 100644 index 000000000..de26e7ce4 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/images/translate.svg @@ -0,0 +1 @@ + diff --git a/recipes/acquia_drupal_starterkit_installer/js/language-dropdown.js b/recipes/acquia_drupal_starterkit_installer/js/language-dropdown.js new file mode 100644 index 000000000..e6943d9c7 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/js/language-dropdown.js @@ -0,0 +1,106 @@ +/** + * @file + * Creation and interactions for the language dropdown. + */ + +((Drupal, once, drupalSettings) => { + + /** + * Gets the current URL and queryStrings, sets the language, and returns the + * full URL. + * + * @param {string} langcode - The language code (e.g. 'es'). + * @returns {string} + */ + function getLanguageURL(langcode) { + const urlParams = new URLSearchParams(window.location.search); + urlParams.set('langcode', langcode); + + + return `${window.location.pathname}?${urlParams}`; + } + + /** + * Creates the element. + */ + function createElement() { + const dropdownWrapper = document.querySelector('.cms-installer__language'); + const dropdown = document.createElement('div'); + + dropdown.setAttribute('id', 'cms-installer-language-dropdown'); + dropdown.classList.add('cms-installer__language-dropdown'); + dropdown.innerHTML = ` + + `; + + const languageList = dropdown.querySelector('.cms-installer__language-list'); + + for (const langcode in drupalSettings.languages) { + const languageElement = document.createElement('li'); + languageElement.classList.add('cms-installer__list-item'); + languageElement.innerHTML = ` + ${drupalSettings.languages[langcode]} + `; + languageList.append(languageElement); + } + + dropdownWrapper.append(dropdown); + } + + /** + * Toggles the visibility of the dropdown. + * @param {Evebt} e - The click event. + */ + function toggleDropdownVisibility(e) { + const originalVisibility = e.currentTarget.getAttribute('aria-expanded') === 'true'; + e.currentTarget.setAttribute('aria-expanded', !originalVisibility); + } + + /** + * Sets up interactions (toggling visibility, etc). + */ + function setupInteractions() { + const dropdownButton = document.querySelector('.cms-installer__language-button'); + dropdownButton.setAttribute('aria-expanded', 'false'); + dropdownButton.setAttribute('aria-controls', 'cms-installer-language-dropdown'); + dropdownButton.addEventListener('click', toggleDropdownVisibility); + + // Close on ESC. + document.addEventListener('keyup', (e) => { + if (e.key === 'Escape') { + dropdownButton.setAttribute('aria-expanded', 'false'); + } + }); + + // Close when clicking outside. + document.addEventListener('click', (e) => { + if (!e.target.closest('.cms-installer__language')) { + dropdownButton.setAttribute('aria-expanded', 'false'); + } + }); + } + + /** + * Let's do this! + */ + function init() { + createElement(); + setupInteractions(); + } + + /** + * Attaches the behavior to the language wrapper. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + */ + Drupal.behaviors.installerLanguageDropdown = { + attach(context) { + once('installer-language-dropdown', '[data-drupal-selector="cms-language-dropdown"]', context).forEach( + init, + ); + }, + }; +})(Drupal, once, drupalSettings); diff --git a/recipes/acquia_drupal_starterkit_installer/js/progress.js b/recipes/acquia_drupal_starterkit_installer/js/progress.js new file mode 100644 index 000000000..c8a23f39f --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/js/progress.js @@ -0,0 +1,20 @@ +(function (Drupal) { + + /** + * Renders a client-side progress bar. + * + * This is for the Acquia Acquia Drupal Starterkit installer and is not meant to be reused. + */ + Drupal.theme.progressBar = function (id) { + const escapedId = Drupal.checkPlain(id); + return ( + `
` + + // '
 
' + + '
' + + // '
' + + // '
 
' + + '
' + ); + }; + +})(Drupal); diff --git a/recipes/acquia_drupal_starterkit_installer/src/Form/InstallerFormBase.php b/recipes/acquia_drupal_starterkit_installer/src/Form/InstallerFormBase.php new file mode 100644 index 000000000..bf13b5662 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/src/Form/InstallerFormBase.php @@ -0,0 +1,36 @@ + $language_names) { + $languages[$langcode] = $language_names[1]; + } + foreach ($install_state['translations'] as $langcode => $uri) { + $languages[$langcode] = $standard_languages[$langcode][1] ?? $langcode; + } + asort($languages); + + // The front-end JavaScript should use this list to create a widget that + // dynamically changes the URL `langcode` parameter. + $form['#attached']['drupalSettings']['languages'] = $languages; + return $form; + } + +} diff --git a/recipes/acquia_drupal_starterkit_installer/src/Form/RecipesForm.php b/recipes/acquia_drupal_starterkit_installer/src/Form/RecipesForm.php new file mode 100644 index 000000000..d6186ff0d --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/src/Form/RecipesForm.php @@ -0,0 +1,80 @@ +t('What are your top goals?'); + + $form['help'] = [ + '#prefix' => '

', + '#markup' => $this->t('You can change your mind later.'), + '#suffix' => '

', + ]; + + $options = [ + 'acquia_drupal_starterkit_community' => $this->t('Acquia Drupal Starterkit Community'), + 'acquia_drupal_starterkit_headless' => $this->t('Acquia Drupal Starterkit Headless'), + 'acquia_drupal_starterkit_low_code' => $this->t('Acquia Drupal Starterkit Low-Code'), + 'acquia_drupal_starterkit_content_model' => $this->t('Acquia Drupal Starterkit Content Model'), + 'acquia_drupal_starterkit_media_model' => $this->t('Acquia Drupal Starterkit Media Model'), + ]; + + $form['add_ons'] = [ + '#prefix' => '
', + '#suffix' => '
', + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => [], + ]; + $form['actions'] = [ + 'submit' => [ + '#type' => 'submit', + '#value' => $this->t('Next'), + '#button_type' => 'primary', + ], + 'skip' => [ + '#type' => 'submit', + '#value' => $this->t('Skip this step'), + ], + '#type' => 'actions', + ]; + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + global $install_state; + $install_state['parameters']['recipes'] = ['acquia_drupal_starterkit']; + + $pressed_button = $form_state->getTriggeringElement(); + // Only choose add-ons if the Next button was pressed. + if ($pressed_button && end($pressed_button['#array_parents']) === 'submit') { + $add_ons = $form_state->getValue('add_ons', []); + $add_ons = array_filter($add_ons); + array_push($install_state['parameters']['recipes'], ...array_values($add_ons)); + } + } + +} diff --git a/recipes/acquia_drupal_starterkit_installer/src/Form/SiteNameForm.php b/recipes/acquia_drupal_starterkit_installer/src/Form/SiteNameForm.php new file mode 100644 index 000000000..c5a533c32 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/src/Form/SiteNameForm.php @@ -0,0 +1,58 @@ +t('Give your site a name'); + $form['help']['#markup'] = $this->t('You can change this later before publishing your site.'); + + $form['site_name'] = [ + '#prefix' => '
', + '#suffix' => '
', + '#type' => 'textfield', + '#title' => $this->t('Site name'), + '#required' => TRUE, + '#default_value' => $install_state['forms']['install_configure_form']['site_name'] ?? $this->t('My awesome site'), + ]; + $form['actions'] = [ + '#type' => 'actions', + 'submit' => [ + '#type' => 'submit', + '#value' => $this->t('Next'), + '#button_type' => 'primary', + ], + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + global $install_state; + $install_state['parameters']['site_name'] = $form_state->getValue('site_name'); + } + +} diff --git a/recipes/acquia_drupal_starterkit_installer/templates/install-page.html.twig b/recipes/acquia_drupal_starterkit_installer/templates/install-page.html.twig new file mode 100644 index 000000000..b9b322a06 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/templates/install-page.html.twig @@ -0,0 +1,53 @@ +{# +/** + * @file + * Acquia Drupal Starterkit implementation to display the installation page. + * + * All available variables are mirrored in page.html.twig. + * Some may be blank but they are provided for consistency. + * + * @see template_preprocess_install_page() + */ +#} +
+
+
+

+ {{ 'Acquia Drupal'|t }} + {{ 'Starterkit'|t }} +

+
+ +
+
+ + {% if page.sidebar_first %} + + {% endif %} + +
+ {% if title %} +

{{ title }}

+ {% endif %} + {{ page.highlighted }} + {{ page.content }} +
+ + {% if page.sidebar_second %} + + {% endif %} + + {% if page.page_bottom %} +
+ {{ page.page_bottom }} +
+ {% endif %} +
+
+ {{ 'Woman quietly working on laptop'|t }} +
+
diff --git a/recipes/acquia_drupal_starterkit_installer/tests/src/Functional/CommandLineInstallTest.php b/recipes/acquia_drupal_starterkit_installer/tests/src/Functional/CommandLineInstallTest.php new file mode 100644 index 000000000..847e3f180 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/tests/src/Functional/CommandLineInstallTest.php @@ -0,0 +1,111 @@ +root = dirname((new \ReflectionClass('Drupal'))->getFileName(), 3); + $this->prepareDatabasePrefix(); + $this->sitePath = $this->root . '/' . $this->siteDirectory; + + mkdir($this->sitePath, recursive: TRUE); + + // This is needed for Drush to work properly. + if (!defined('DRUPAL_TEST_IN_CHILD_SITE')) { + define('DRUPAL_TEST_IN_CHILD_SITE', FALSE); + } + } + + /** + * {@inheritdoc} + */ + protected function tearDown(): void { + // To help with forensic debugging, only delete the site directory if the + // test passed. + if ($this->hasFailed() === FALSE) { + $file_system = new Filesystem(); + $file_system->chmod($this->sitePath, 0755); + $file_system->remove($this->sitePath); + } + parent::tearDown(); + } + + private function assertPostInstallState(): void { + // Confirm that there's no install profile. + $this->drush('core:status', options: ['field' => 'install-profile'], cd: $this->root); + $this->assertEmpty($this->getOutput()); + + // Confirm that non-core extensions are installed. + $options = [ + 'format' => 'json', + 'no-core' => TRUE, + 'status' => 'enabled', + ]; + $this->drush('pm:list', options: $options, cd: $this->root); + $this->assertNotEmpty($this->getOutputFromJSON()); + + // Confirm that Gin is the admin theme. + $this->drush('config:get', ['system.theme', 'admin'], ['format' => 'json'], cd: $this->root); + $this->assertSame('gin', $this->getOutputFromJSON('system.theme:admin')); + } + + public function testDrushSiteInstall(): void { + $options = [ + 'yes' => TRUE, + 'sites-subdir' => substr($this->siteDirectory, 6), + 'db-url' => "sqlite://$this->siteDirectory/files/.sqlite", + ]; + $this->drush('site:install', options: $options, cd: $this->root); + + $this->assertPostInstallState(); + } + + public function testCoreInstallCommand(): void { + $command = [ + PHP_BINDIR . '/php', + 'core/scripts/drupal', + 'install', + 'acquia_drupal_starterkit_installer', + ]; + $process = new Process($command, $this->root, [ + 'DRUPAL_DEV_SITE_PATH' => $this->siteDirectory, + ]); + $process->mustRun(); + $this->assertStringContainsString('Congratulations, you installed Acquia Drupal Starterkit!', $process->getErrorOutput()); + + // The core install command write-protects the site directory, which + // interferes with $this->drush(). + chmod($this->sitePath, 0755); + + $this->assertPostInstallState(); + } + +} diff --git a/recipes/acquia_drupal_starterkit_installer/tests/src/Functional/InteractiveInstallTest.php b/recipes/acquia_drupal_starterkit_installer/tests/src/Functional/InteractiveInstallTest.php new file mode 100644 index 000000000..9856dc557 --- /dev/null +++ b/recipes/acquia_drupal_starterkit_installer/tests/src/Functional/InteractiveInstallTest.php @@ -0,0 +1,188 @@ +assertSession(); + $assert_session->buttonExists('Skip this step'); + // The list of languages should be exposed to JavaScript. + $this->assertArrayHasKey('languages', $this->getDrupalSettings()); + + // Choose all the add-ons! + $this->submitForm([ + 'add_ons[drupal_cms_accessibility_tools]' => TRUE, + 'add_ons[drupal_cms_multilingual]' => TRUE, + ], 'Next'); + + // The list of languages should still be exposed to JavaScript. + $this->assertArrayHasKey('languages', $this->getDrupalSettings()); + // Now we should be asked for the site name, with a default value in place + // for the truly lazy. + $assert_session->pageTextContains('Give your site a name'); + $assert_session->elementAttributeExists('named', ['field', 'Site name'], 'required'); + $assert_session->fieldValueEquals('Site name', 'My awesome site'); + // We have to use submitForm() to ensure that batch operations, redirects, + // and so forth in the remaining install tasks get done. + $this->submitForm(['Site name' => 'Acquia Drupal Starterkit'], 'Next'); + + // Proceed to the database settings form. + parent::setUpSettings(); + } + + /** + * {@inheritdoc} + */ + protected function setUpProfile(): void { + // Nothing to do here; Acquia Drupal Starterkit marks itself as a distribution so that the + // installer will automatically select it. + } + + /** + * {@inheritdoc} + */ + protected function visitInstaller(): void { + parent::visitInstaller(); + // The task list should be hidden. + $this->assertSession()->elementNotExists('css', '.task-list'); + } + + /** + * {@inheritdoc} + */ + protected function setUpLanguage() { + // The Acquia Drupal Starterkit installer suppresses the language selection step, so + // there's nothing to do here. + } + + /** + * {@inheritdoc} + */ + protected function setUpSite(): void { + // The normal site configuration form is bypassed, so we're done. + $this->isInstalled = TRUE; + } + + /** + * Tests basic expectations of a successful Acquia Drupal Starterkit install. + */ + public function testPostInstallState(): void { + // The site name and site-wide email address should have been set. + // @see \Drupal\acquia_drupal_starterkit_installer\Form\SiteNameForm + $site_config = $this->config('system.site'); + $this->assertSame('Acquia Drupal Starterkit', $site_config->get('name')); + + $host = parse_url($this->baseUrl, PHP_URL_HOST); + $this->assertSame("no-reply@$host", $site_config->get('mail')); + + // Update Status should be installed, and user 1 should be getting its + // notifications. + $this->assertTrue($this->container->get(ModuleHandlerInterface::class)->moduleExists('update')); + $account = User::load(1); + $this->assertContains($account->getEmail(), $this->config('update.settings')->get('notification.emails')); + $this->assertContains('administrator', $account->getRoles()); + // The installer generates a random password, so change that in order to + // test logging in. + $account->setPassword('pastafazoul')->save(); + + // The installer should have uninstalled itself. + // @see acquia_drupal_starterkit_installer_uninstall_myself() + $this->assertFalse($this->container->getParameter('install_profile')); + + // Ensure that there are non-core extensions installed, which proves that + // recipes were applied during site installation. + $this->assertContribInstalled($this->container->get(ModuleExtensionList::class)); + $this->assertContribInstalled($this->container->get(ThemeExtensionList::class)); + + // Antibot prevents non-JS functional tests from logging in, so disable it. + $this->config('antibot.settings')->set('form_ids', [])->save(); + // Log out so we can test that user 1's credentials were properly saved. + $this->drupalLogout(); + + // It should be possible to log in with your email address. + $page = $this->getSession()->getPage(); + $page->fillField('name', "admin@$host"); + $page->fillField('pass', 'pastafazoul'); + $page->pressButton('Log in'); + $assert_session = $this->assertSession(); + $assert_session->addressEquals('/admin/dashboard'); + $this->drupalLogout(); + + // It should also be possible to log in with the username, which is + // defaulted to `admin` by the installer. + $page->fillField('name', 'admin'); + $page->fillField('pass', 'pastafazoul'); + $page->pressButton('Log in'); + $assert_session->addressEquals('/admin/dashboard'); + $this->drupalLogout(); + + $editor = $this->drupalCreateUser(); + $editor->addRole('content_editor')->save(); + $this->drupalLogin($editor); + + // Test basic configuration of the content types. + $node_types = $this->container->get(EntityTypeManagerInterface::class) + ->getStorage('node_type') + ->getQuery() + ->execute(); + $this->assertNotEmpty($node_types); + + foreach ($node_types as $node_type) { + $node = $this->createNode(['type' => $node_type]); + $url = $node->toUrl(); + + // Content editors should be able to clone all content types. + $this->drupalGet($url); + $this->getSession()->getPage()->clickLink('Clone'); + $assert_session->statusCodeEquals(200); + // All content types should have pretty URLs. + $this->assertNotSame('/node/' . $node->id(), $url->toString()); + } + } + + /** + * Asserts that any number of contributed extensions are installed. + * + * @param \Drupal\Core\Extension\ExtensionList $list + * An extension list. + */ + private function assertContribInstalled(ExtensionList $list): void { + $core_dir = $this->container->getParameter('app.root') . '/core'; + + foreach (array_keys($list->getAllInstalledInfo()) as $name) { + // If the extension isn't part of core, great! We're done. + if (!str_starts_with($list->getPath($name), $core_dir)) { + return; + } + } + $this->fail('No contributed extensions are installed.'); + } + +} diff --git a/recipes/acquia_drupal_starterkit_low_code/composer.json b/recipes/acquia_drupal_starterkit_low_code/composer.json index 6c2e356f0..1dcf07cb9 100644 --- a/recipes/acquia_drupal_starterkit_low_code/composer.json +++ b/recipes/acquia_drupal_starterkit_low_code/composer.json @@ -2,6 +2,7 @@ "name": "drupal/acquia_drupal_starterkit_low_code", "type": "drupal-recipe", "description": "The low-code starter kit with Site Studio and a UIkit. It provides drag and drop content authoring and low-code site building.", + "version": "dev-develop", "require": { "drupal/core": ">=10.3", "drupal/acquia_cms_site_studio": "^1.6", diff --git a/recipes/acquia_drupal_starterkit_media_model/composer.json b/recipes/acquia_drupal_starterkit_media_model/composer.json index bc06dc075..bde4852b9 100644 --- a/recipes/acquia_drupal_starterkit_media_model/composer.json +++ b/recipes/acquia_drupal_starterkit_media_model/composer.json @@ -2,6 +2,7 @@ "name": "drupal/acquia_drupal_starterkit_media_model", "type": "drupal-recipe", "description": "An media model provides Audio, Document, Image< Video media types.", + "version": "dev-develop", "require": { "drupal/core": ">=10.3", "drupal/acquia_cms_audio": "^1.5",