-
-
- stepUrlForId($id);
- }
-
- if ($this->controller && method_exists($this->controller, 'stepUrlForId')) {
- return $this->controller->stepUrlForId($id);
- }
-
- $position = array_search($id, ['welcome', 'concepts', 'content', 'teaser', 'finish'], true);
-
- return $this->controller->stepUrl(false === $position ? 1 : $position + 1);
- }
-
- private function postTypesSettingsUrl()
- {
- return admin_url('admin.php?page=presspermit-settings&pp_tab=core');
- }
-
- private function teaserUrl()
- {
- if (current_user_can('pp_manage_teaser') && presspermit()->moduleActive('teaser')) {
- return admin_url('admin.php?page=presspermit-posts-teaser');
- }
-
- return admin_url('admin.php?page=presspermit-settings&pp_tab=modules');
- }
-
- private function urlPath()
- {
- return defined('PRESSPERMIT_URLPATH') ? PRESSPERMIT_URLPATH : plugins_url('', PRESSPERMIT_FILE);
- }
-
- private function asset($file)
- {
- return [
- 'path' => PRESSPERMIT_ABSPATH . '/common/img/welcome/' . $file,
- 'url' => $this->urlPath() . '/common/img/welcome/' . $file,
- ];
- }
-
- private function canManageGroups()
- {
- return current_user_can('pp_manage_permissions')
- || current_user_can('pp_edit_groups')
- || presspermit()->groups()->anyGroupManager();
- }
-
- private function enabledPostTypeCount()
- {
- $enabled = presspermit()->getEnabledPostTypes();
- return is_array($enabled) ? count($enabled) : 0;
- }
-
- private function exceptionCount()
- {
- global $wpdb;
-
- if (null !== $this->exceptionCount) {
- return $this->exceptionCount;
- }
-
- if (empty($wpdb->ppc_exceptions) || empty($wpdb->ppc_exception_items)) {
- return 0;
- }
-
- // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom Permissions tables; memoized per request to avoid stale checklist state after permission changes.
- $this->exceptionCount = (int) $wpdb->get_var(
- "SELECT COUNT(DISTINCT i.item_id) FROM $wpdb->ppc_exception_items AS i"
- . " INNER JOIN $wpdb->ppc_exceptions AS e ON e.exception_id = i.exception_id"
- . " WHERE e.for_item_source = 'post' AND e.via_item_source = 'post'"
- . ' AND i.item_id > 0'
- );
-
- return $this->exceptionCount;
- }
-
- private function postPermissionsUrl()
- {
- $enabled = (array) presspermit()->getEnabledPostTypes();
- $postTypes = [];
-
- foreach ($enabled as $key => $value) {
- $postType = is_string($value) ? $value : (is_string($key) ? $key : '');
-
- if ($postType && ! in_array($postType, $postTypes, true)) {
- $postTypes[] = $postType;
- }
- }
-
- foreach (['post', 'page'] as $preferred) {
- if (in_array($preferred, $postTypes, true)) {
- return 'post' === $preferred
- ? admin_url('edit.php')
- : add_query_arg('post_type', 'page', admin_url('edit.php'));
- }
- }
-
- foreach ($postTypes as $postType) {
- if (post_type_exists($postType)) {
- return add_query_arg('post_type', sanitize_key($postType), admin_url('edit.php'));
- }
- }
-
- return $this->postTypesSettingsUrl();
- }
-}
diff --git a/classes/PublishPress/Permissions/UI/WelcomePostTypes.php b/classes/PublishPress/Permissions/UI/WelcomePostTypes.php
deleted file mode 100644
index c800a84f2..000000000
--- a/classes/PublishPress/Permissions/UI/WelcomePostTypes.php
+++ /dev/null
@@ -1,140 +0,0 @@
- true, 'show_ui' => true], 'objects');
-
- $omit = array_merge(
- (array) apply_filters('presspermit_unfiltered_post_types', ['wp_block']),
- array_keys((array) apply_filters('presspermit_hidden_post_types', []))
- );
-
- $types = array_diff_key($types, array_fill_keys($omit, true));
- $first = ['post' => 0, 'page' => 1, 'attachment' => 2];
-
- uasort($types, function ($a, $b) use ($first) {
- $rankA = isset($first[$a->name]) ? $first[$a->name] : 99;
- $rankB = isset($first[$b->name]) ? $first[$b->name] : 99;
-
- return $rankA === $rankB ? strcasecmp(self::label($a), self::label($b)) : $rankA - $rankB;
- });
-
- return $types;
- }
-
- /** @return string */
- public static function label($type)
- {
- if (isset($type->labels_pp->name)) {
- return (string) $type->labels_pp->name;
- }
-
- return isset($type->labels->name) ? (string) $type->labels->name : (string) $type->name;
- }
-
- /**
- * Types Permissions always keeps on, keyed by name.
- *
- * @return array
- */
- public static function locked()
- {
- return array_filter((array) apply_filters('presspermit_locked_post_types', ['nav_menu' => 'nav_menu']));
- }
-
- /**
- * Enabled state as the Settings screen reports it, keyed by post type name.
- *
- * @return array
- */
- public static function enabled()
- {
- return (array) apply_filters('presspermit_enabled_post_types', presspermit()->getOption(self::OPTION));
- }
-
- /**
- * Whether the current user may change the setting here. A network-wide value belongs to the network admin.
- *
- * @return bool
- */
- public static function canSave()
- {
- if (! current_user_can('pp_manage_settings')) {
- return false;
- }
-
- return ! (is_multisite() && in_array(self::OPTION, (array) presspermit()->netwide_options, true));
- }
-
- /**
- * @param array $posted Submitted checkbox values keyed by post type name.
- * @return bool True when the submission was accepted.
- */
- public static function save($posted)
- {
- if (! self::canSave()) {
- return false;
- }
-
- $selected = [];
- foreach ((array) $posted as $name => $value) {
- if (is_string($name) && $value) {
- $selected[] = sanitize_key($name);
- }
- }
-
- $pp = presspermit();
- $current = $pp->getOption(self::OPTION);
- $current = is_array($current) ? $current : [];
-
- $value = self::applyChoices($current, array_keys(self::choices()), array_keys(self::locked()), $selected);
-
- if ($value !== $current) {
- $pp->updateOption(self::OPTION, $value);
- }
-
- return true;
- }
-
- /**
- * Merge a submission into the stored setting.
- *
- * Only types the guide offered can change, so a forged field name never creates or alters an entry.
- * Types left unticked are stored as '0', matching the Settings screen, and locked types stay on.
- *
- * @param array $current Stored setting.
- * @param string[] $allowed Post types offered by the guide.
- * @param string[] $locked Post types that must stay enabled.
- * @param string[] $selected Post types ticked in the submission.
- * @return array
- */
- public static function applyChoices(array $current, array $allowed, array $locked, array $selected)
- {
- $selected = array_flip(array_intersect($selected, $allowed));
-
- foreach ($allowed as $name) {
- $current[$name] = (isset($selected[$name]) || in_array($name, $locked, true)) ? '1' : '0';
- }
-
- return $current;
- }
-}
diff --git a/classes/PublishPress/PermissionsHooksAdmin.php b/classes/PublishPress/PermissionsHooksAdmin.php
index e1e88d7c2..21404b665 100644
--- a/classes/PublishPress/PermissionsHooksAdmin.php
+++ b/classes/PublishPress/PermissionsHooksAdmin.php
@@ -71,14 +71,6 @@ function($query) {
add_action('presspermit_trigger_cache_flush', [$this, 'wpeCacheFlush']);
add_action('presspermit_activate', [$this, 'actPluginSettingsUpdated']);
add_action('shutdown', [$this, 'actConfigUpdateFollowup']);
-
- add_action('init', [$this, 'actRegisterWelcomeExperience'], 20);
- }
-
- public function actRegisterWelcomeExperience()
- {
- require_once(PRESSPERMIT_CLASSPATH . '/UI/WelcomeExperience.php');
- new Permissions\UI\WelcomeExperience();
}
public function init()
diff --git a/common/css/welcome-permissions.css b/common/css/welcome-permissions.css
deleted file mode 100644
index 635ff5321..000000000
--- a/common/css/welcome-permissions.css
+++ /dev/null
@@ -1,655 +0,0 @@
-/*
- * PublishPress Permissions welcome guide.
- *
- * Presentation for the shared PublishPress Welcome library, matched to the PublishPress Hub: a quiet
- * lavender gradient card, a small uppercase eyebrow, a bold headline, flat controls with 8px corners,
- * and the penguin illustration in a white panel.
- */
-
-/* ------------------------------------------------------------------ tokens */
-
-.ppw-root,
-.ppw-notice,
-.ppw-checklist {
- --ppw-line: #e4e1ef;
- --ppw-panel: linear-gradient(135deg, #ffffff 0%, #f8f6fd 55%, #f1edfb 100%);
-}
-
-/* ----------------------------------------------------------------- controls */
-
-.ppw-root .ppw-btn,
-.ppw-notice .ppw-btn,
-.ppw-checklist .ppw-btn {
- border-radius: 8px;
-}
-
-.ppw-root .ppw-btn.ppw-btn-primary,
-.ppw-notice .ppw-btn.ppw-btn-primary {
- box-shadow: none;
-}
-
-.ppw-root .ppw-btn-lg {
- min-height: 44px;
- padding: 0 22px;
- font-size: 14px;
-}
-
-/* --------------------------------------------------------------------- hero */
-
-.ppw-step-welcome .ppw-hero.has-art {
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
- gap: 48px;
- padding: 44px 48px;
- border: 1px solid var(--ppw-line);
- border-radius: 16px;
- background: var(--ppw-panel);
- box-shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 24px 48px -32px rgba(var(--ppw-accent-rgb), .35);
-}
-
-.ppw-step-welcome .ppw-eyebrow {
- margin-bottom: 14px;
- padding: 0;
- border-radius: 0;
- background: none;
- color: var(--ppw-accent);
- font-size: 11px;
- font-weight: 700;
- letter-spacing: .09em;
- text-transform: uppercase;
-}
-
-.ppw-root .ppw-step-welcome .ppw-hero h1 {
- font-size: 32px;
- font-weight: 700;
- line-height: 1.18;
- letter-spacing: -.02em;
-}
-
-.ppw-root .ppw-step-welcome .ppw-lede {
- margin-top: 12px;
- font-size: 15px;
- line-height: 1.6;
-}
-
-.ppw-step-welcome .ppw-hero-actions {
- margin-top: 26px;
-}
-
-.ppw-step-welcome .ppw-hero-art {
- display: block;
- min-height: 0;
- padding: 0;
- overflow: hidden;
- border: 1px solid #ebe8f5;
- border-radius: 12px;
- background: #fff;
-}
-
-.ppw-step-welcome .ppw-hero-art .ppw-figure img {
- width: 100%;
- height: auto;
-}
-
-/* ----------------------------------------------------------------- concepts */
-
-.ppw-step-concepts .ppw-concepts {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- max-width: 760px;
- margin: 0 auto;
-}
-
-.ppw-step-concepts .ppw-concept {
- padding: 14px 14px 26px;
-}
-
-.ppw-step-concepts .ppw-concept-art {
- height: 272px;
- min-height: 0;
- padding: 20px;
- border-radius: 12px;
- background: var(--ppw-panel);
-}
-
-.ppw-roles-art {
- display: flex;
- align-items: flex-end;
- justify-content: center;
- gap: 18px;
-}
-
-.ppw-person {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 12px;
-}
-
-.ppw-avatar {
- display: block;
- width: 72px;
- height: 72px;
- border-radius: 50%;
- box-shadow: 0 0 0 3px #fff, 0 8px 16px -8px rgba(var(--ppw-accent-rgb), .55);
-}
-
-.ppw-person-role {
- padding: 4px 12px;
- border-radius: 999px;
- background: #fff;
- box-shadow: 0 1px 2px rgba(15, 23, 42, .1);
- color: var(--ppw-ink);
- font-size: 12px;
- font-weight: 650;
-}
-
-.ppw-permissions-card {
- width: 100%;
- max-width: 252px;
- padding: 16px 18px 12px;
- border-radius: 14px;
- background: #fff;
- box-shadow: 0 18px 34px -16px rgba(var(--ppw-accent-rgb), .45), 0 1px 3px rgba(15, 23, 42, .08);
-}
-
-.ppw-permissions-card-head {
- display: flex;
- align-items: center;
- gap: 12px;
- padding-bottom: 12px;
- border-bottom: 1px solid #eceef5;
-}
-
-.ppw-permissions-lock.dashicons {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 36px;
- height: 36px;
- border-radius: 10px;
- background: linear-gradient(145deg, var(--ppw-accent), var(--ppw-accent-dark));
- color: #fff;
- font-size: 19px;
-}
-
-.ppw-permissions-card-title {
- display: flex;
- flex-direction: column;
- gap: 1px;
-}
-
-.ppw-permissions-card-title strong {
- color: var(--ppw-ink);
- font-size: 13px;
-}
-
-.ppw-permissions-card-title span {
- color: var(--ppw-muted);
- font-size: 11px;
-}
-
-.ppw-permissions-row {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 0;
- border-bottom: 1px solid #f1f2f7;
-}
-
-.ppw-permissions-row:last-child {
- border-bottom: 0;
-}
-
-.ppw-permissions-avatar {
- width: 24px;
- height: 24px;
- border-radius: 50%;
- background: linear-gradient(145deg, #e3dff2, #c9c1e4);
-}
-
-.ppw-permissions-name {
- flex: 1;
- color: #1e293b;
- font-size: 12.5px;
- font-weight: 600;
-}
-
-.ppw-permissions-switch {
- position: relative;
- width: 34px;
- height: 20px;
- border-radius: 999px;
- background: #d5d9e4;
-}
-
-.ppw-permissions-switch::after {
- position: absolute;
- top: 3px;
- left: 3px;
- width: 14px;
- height: 14px;
- border-radius: 50%;
- background: #fff;
- box-shadow: 0 1px 2px rgba(15, 23, 42, .25);
- content: '';
-}
-
-.ppw-permissions-switch.is-on {
- background: var(--ppw-accent);
-}
-
-.ppw-permissions-switch.is-on::after {
- left: 17px;
-}
-
-/* ------------------------------------------------------------ content step */
-
-.ppw-step-content .ppw-heading {
- margin-bottom: 32px;
-}
-
-.ppw-post-types {
- max-width: 760px;
- margin: 0 auto;
-}
-
-.ppw-post-types fieldset {
- min-width: 0;
- margin: 0;
- padding: 0;
- border: 0;
-}
-
-.ppw-type-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
- gap: 14px;
-}
-
-.ppw-type-choice {
- position: relative;
- display: flex;
- cursor: pointer;
-}
-
-.ppw-root .ppw-type-choice input[type="checkbox"] {
- position: absolute;
- width: 1px;
- height: 1px;
- margin: -1px;
- padding: 0;
- overflow: hidden;
- border: 0;
- clip: rect(0, 0, 0, 0);
- opacity: 0;
-}
-
-.ppw-type-card {
- display: flex;
- flex: 1;
- align-items: center;
- gap: 14px;
- min-width: 0;
- min-height: 76px;
- padding: 14px 16px;
- border: 1px solid var(--ppw-line);
- border-radius: 12px;
- background: #fff;
- transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
-}
-
-.ppw-type-choice:hover .ppw-type-card {
- border-color: rgba(var(--ppw-accent-rgb), .45);
-}
-
-.ppw-type-icon.dashicons {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- border-radius: 10px;
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent);
- font-size: 20px;
-}
-
-.ppw-type-copy {
- display: flex;
- flex: 1;
- flex-direction: column;
- gap: 2px;
- min-width: 0;
-}
-
-.ppw-type-copy strong {
- color: var(--ppw-ink);
- font-size: 14.5px;
- font-weight: 650;
-}
-
-.ppw-type-copy small {
- color: var(--ppw-muted);
- font-size: 12.5px;
-}
-
-.ppw-type-check {
- flex: none;
- width: 22px;
- height: 22px;
- border: 2px solid #cdd2de;
- border-radius: 50%;
- background: #fff center / 14px no-repeat;
- transition: border-color .15s ease, background-color .15s ease;
-}
-
-.ppw-type-choice input:checked + .ppw-type-card {
- border-color: var(--ppw-accent);
- background: #faf9fe;
- box-shadow: 0 0 0 3px var(--ppw-accent-soft);
-}
-
-.ppw-type-choice input:checked + .ppw-type-card .ppw-type-check {
- border-color: var(--ppw-accent);
- background-color: var(--ppw-accent);
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6.2l2 2 4-4.4' fill='none' stroke='%23fff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
-}
-
-.ppw-type-choice input:disabled + .ppw-type-card {
- cursor: not-allowed;
- opacity: .75;
-}
-
-.ppw-type-choice input:focus-visible + .ppw-type-card {
- outline: 2px solid var(--ppw-accent);
- outline-offset: 2px;
-}
-
-.ppw-root .ppw-setup-note {
- margin-top: 20px;
- color: var(--ppw-muted);
- font-size: 13px;
- text-align: center;
-}
-
-.ppw-setup-actions {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: center;
- gap: 14px 24px;
- margin-top: 24px;
-}
-
-.ppw-setup-actions.is-centered {
- flex-direction: column;
-}
-
-/* ------------------------------------------------------------- teaser step */
-
-.ppw-step-teaser .ppw-lesson-art {
- min-width: 0;
-}
-
-.ppw-teaser-mock {
- overflow: hidden;
- border: 1px solid var(--ppw-line);
- border-radius: 14px;
- background: #fff;
- box-shadow: var(--ppw-shadow);
-}
-
-.ppw-mock-bar {
- display: flex;
- align-items: center;
- gap: 6px;
- height: 34px;
- padding: 0 14px;
- border-bottom: 1px solid var(--ppw-line);
- background: #f8f9fc;
-}
-
-.ppw-mock-bar i {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background: #ff6b6b;
-}
-
-.ppw-mock-bar i:nth-child(2) {
- background: #fbbf24;
-}
-
-.ppw-mock-bar i:nth-child(3) {
- background: #34d399;
-}
-
-.ppw-mock-bar span {
- flex: 1;
- max-width: 260px;
- margin-inline-start: 14px;
- padding: 3px 12px;
- overflow: hidden;
- border: 1px solid var(--ppw-line);
- border-radius: 999px;
- background: #fff;
- color: var(--ppw-muted);
- font-size: 11px;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.ppw-mock-page {
- display: flex;
- flex-direction: column;
- gap: 10px;
- padding: 28px 30px 30px;
-}
-
-.ppw-mock-tag {
- align-self: flex-start;
- padding: 3px 10px;
- border-radius: 999px;
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent-dark);
- font-size: 11px;
- font-weight: 700;
-}
-
-.ppw-mock-title {
- color: var(--ppw-ink);
- font-size: 20px;
- line-height: 1.25;
- letter-spacing: -.01em;
-}
-
-.ppw-root .ppw-mock-excerpt {
- font-size: 13.5px;
- line-height: 1.6;
-}
-
-.ppw-mock-lines {
- display: block;
-}
-
-.ppw-mock-lines i {
- display: block;
- height: 8px;
- margin-top: 8px;
- border-radius: 4px;
- background: linear-gradient(90deg, #e6e8f0, #f3f4f8);
-}
-
-.ppw-mock-lines i:nth-child(2) {
- width: 88%;
- opacity: .7;
-}
-
-.ppw-mock-lines i:nth-child(3) {
- width: 62%;
- opacity: .4;
-}
-
-.ppw-mock-gate {
- display: flex;
- align-items: center;
- gap: 14px;
- margin-top: 14px;
- padding: 14px 16px;
- border: 1px solid rgba(var(--ppw-accent-rgb), .25);
- border-radius: 12px;
- background: #faf9fe;
-}
-
-.ppw-mock-lock.dashicons {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 36px;
- height: 36px;
- border-radius: 10px;
- background: var(--ppw-accent);
- color: #fff;
- font-size: 18px;
-}
-
-.ppw-mock-gate-copy {
- display: flex;
- flex: 1;
- flex-direction: column;
- gap: 2px;
- min-width: 0;
- color: var(--ppw-muted);
- font-size: 12px;
-}
-
-.ppw-mock-gate-copy strong {
- color: var(--ppw-ink);
- font-size: 13px;
-}
-
-.ppw-mock-button {
- flex: none;
- padding: 8px 14px;
- border-radius: 8px;
- background: var(--ppw-accent);
- color: #fff;
- font-size: 12px;
- font-weight: 650;
-}
-
-/* -------------------------------------------------------------- email step */
-
-.ppw-capture-art .ppw-figure {
- border-color: #ebe8f5;
- border-radius: 14px;
- background: #fff;
- box-shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 24px 48px -32px rgba(var(--ppw-accent-rgb), .35);
-}
-
-.ppw-capabilities-promo {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 14px 16px;
- max-width: 560px;
- margin: 28px auto 0;
- padding: 16px 18px;
- border: 1px solid var(--ppw-line);
- border-radius: 14px;
- background: var(--ppw-panel);
-}
-
-.ppw-capture-layout.has-art .ppw-capabilities-promo {
- margin-inline-start: 0;
-}
-
-.ppw-capabilities-icon.dashicons {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- border-radius: 10px;
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent);
- font-size: 20px;
-}
-
-.ppw-capabilities-copy {
- display: flex;
- flex: 1;
- flex-direction: column;
- gap: 2px;
- min-width: min(100%, 180px);
- color: var(--ppw-muted);
- font-size: 13px;
- line-height: 1.5;
-}
-
-.ppw-capabilities-copy strong {
- color: var(--ppw-ink);
- font-size: 14px;
-}
-
-/* ------------------------------------------------------------------ finish */
-
-.ppw-root .ppw-promo {
- max-width: 880px;
- border-radius: 16px;
-}
-
-.ppw-root .ppw-promo ul {
- grid-template-columns: repeat(2, minmax(0, 1fr));
-}
-
-/* -------------------------------------------------------------- responsive */
-
-@media (max-width: 960px) {
- .ppw-step-welcome .ppw-hero.has-art {
- grid-template-columns: minmax(0, 1fr);
- gap: 32px;
- padding: 32px;
- }
-}
-
-@media (max-width: 782px) {
- /* Brand, step count and exit stay on one row. */
- .ppw-root .ppw-topbar {
- grid-template-columns: minmax(0, 1fr) auto auto;
- gap: 12px;
- }
-
- .ppw-step-welcome .ppw-hero.has-art {
- padding: 24px 20px;
- }
-
- .ppw-step-concepts .ppw-concepts {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .ppw-mock-page {
- padding: 22px 20px 24px;
- }
-
- .ppw-mock-gate {
- flex-wrap: wrap;
- }
-}
-
-@media (max-width: 480px) {
- /* The progress bar already shows where you are; give the brand name the room. */
- .ppw-root .ppw-step-label {
- display: none;
- }
-}
-
-@media (prefers-reduced-motion: reduce) {
- .ppw-type-card,
- .ppw-type-check {
- transition: none;
- }
-}
diff --git a/common/img/welcome/concept-exceptions.svg b/common/img/welcome/concept-exceptions.svg
deleted file mode 100644
index 0e1a1e40f..000000000
--- a/common/img/welcome/concept-exceptions.svg
+++ /dev/null
@@ -1,53 +0,0 @@
-
diff --git a/common/img/welcome/concept-roles.svg b/common/img/welcome/concept-roles.svg
deleted file mode 100644
index ab2ec010b..000000000
--- a/common/img/welcome/concept-roles.svg
+++ /dev/null
@@ -1,41 +0,0 @@
-
diff --git a/common/img/welcome/penguins.png b/common/img/welcome/penguins.png
deleted file mode 100644
index c5c5486e6..000000000
Binary files a/common/img/welcome/penguins.png and /dev/null differ
diff --git a/lib/composer.json b/lib/composer.json
index 37856b510..43896e1ba 100644
--- a/lib/composer.json
+++ b/lib/composer.json
@@ -9,18 +9,11 @@
"platform-check": false,
"autoloader-suffix": "PressPermit"
},
- "repositories": [
- {
- "type": "vcs",
- "url": "https://github.com/publishpress/library-publishpress-welcome"
- }
- ],
"require": {
"php": ">=7.2.5",
"publishpress/pimple-pimple": "^3.5.0.9",
"publishpress/psr-container": "^2.0.1.9",
"publishpress/instance-protection": "^2.0",
- "publishpress/publishpress-welcome": "dev-codex/welcome-feedback",
"publishpress/wordpress-reviews": "^1.1",
"publishpress/wordpress-version-notices": "^2.1"
}
diff --git a/lib/composer.lock b/lib/composer.lock
index 8858a0025..bd021450d 100644
--- a/lib/composer.lock
+++ b/lib/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": "23c70fa6aa3d0741d2a70c35ded34860",
+ "content-hash": "9e0491b02bfcb557a25294364568f2db",
"packages": [
{
"name": "publishpress/instance-protection",
@@ -231,95 +231,6 @@
},
"time": "2025-12-09T15:18:16+00:00"
},
- {
- "name": "publishpress/publishpress-welcome",
- "version": "dev-codex/welcome-feedback",
- "source": {
- "type": "git",
- "url": "git@github.com:publishpress/library-publishpress-welcome.git",
- "reference": "e0898f44a4d40a343459fb77ebc83727014e6b69"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/publishpress/library-publishpress-welcome/zipball/e0898f44a4d40a343459fb77ebc83727014e6b69",
- "reference": "e0898f44a4d40a343459fb77ebc83727014e6b69",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2.5"
- },
- "require-dev": {
- "overtrue/phplint": "^9.5",
- "publishpress/version-loader-generator": "^1.3.1"
- },
- "type": "library",
- "extra": {
- "generator": {
- "lib-class-test": "class_exists('PublishPress\\Welcome\\WelcomeController')",
- "src-dir": "src",
- "namespace": "PublishPress\\Welcome",
- "action-register-priority": "-200",
- "action-initialize-priority": "-190",
- "generate-test-file": false
- }
- },
- "autoload": {
- "files": [
- "src/include.php"
- ]
- },
- "scripts": {
- "generate-files": [
- "vendor/bin/version-loader-generator"
- ],
- "post-install-cmd": [
- "@generate-files"
- ],
- "post-update-cmd": [
- "@generate-files"
- ],
- "check:lint": [
- "phplint --no-cache src tests"
- ],
- "test:unit": [
- "php tests/run.php"
- ],
- "test:integration": [
- "php tests/integration/version-loader.php"
- ],
- "test": [
- "@check:lint",
- "@test:unit",
- "@test:integration"
- ],
- "build": [
- "bash bin/build.sh"
- ],
- "check:release": [
- "php bin/check-release.php"
- ],
- "make:pot": [
- "bash bin/make-pot.sh"
- ]
- },
- "license": [
- "GPL-3.0-or-later"
- ],
- "authors": [
- {
- "name": "PublishPress",
- "email": "help@publishpress.com",
- "homepage": "https://publishpress.com/",
- "role": "Developer"
- }
- ],
- "description": "Reusable, configurable welcome and onboarding experiences for PublishPress WordPress plugins.",
- "support": {
- "source": "https://github.com/publishpress/library-publishpress-welcome/tree/codex/welcome-feedback",
- "issues": "https://github.com/publishpress/library-publishpress-welcome/issues"
- },
- "time": "2026-10-21T10:50:19+00:00"
- },
{
"name": "publishpress/wordpress-reviews",
"version": "1.2.2",
@@ -463,9 +374,7 @@
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": {
- "publishpress/publishpress-welcome": 20
- },
+ "stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
diff --git a/lib/vendor/composer/autoload_files.php b/lib/vendor/composer/autoload_files.php
index 72c33a27d..736be8d0e 100644
--- a/lib/vendor/composer/autoload_files.php
+++ b/lib/vendor/composer/autoload_files.php
@@ -8,7 +8,6 @@
return array(
'3b1e1688e889525de91ac2456aba9efd' => $vendorDir . '/publishpress/psr-container/lib/include.php',
'24b27b1b9a32bf58eda571c3e5ae3480' => $vendorDir . '/publishpress/pimple-pimple/lib/include.php',
- 'bc707a56594837f83dcc83b3274ae09e' => $vendorDir . '/publishpress/publishpress-welcome/src/include.php',
'41c664bd04a95c2d6a2f2a3e00f06593' => $vendorDir . '/publishpress/wordpress-reviews/ReviewsController.php',
'a61bc28a742b9f9f2fd5ef4d2d1e2037' => $vendorDir . '/publishpress/wordpress-version-notices/src/include.php',
);
diff --git a/lib/vendor/composer/autoload_static.php b/lib/vendor/composer/autoload_static.php
index d76a522c7..754acf98a 100644
--- a/lib/vendor/composer/autoload_static.php
+++ b/lib/vendor/composer/autoload_static.php
@@ -9,7 +9,6 @@ class ComposerStaticInitPressPermit
public static $files = array (
'3b1e1688e889525de91ac2456aba9efd' => __DIR__ . '/..' . '/publishpress/psr-container/lib/include.php',
'24b27b1b9a32bf58eda571c3e5ae3480' => __DIR__ . '/..' . '/publishpress/pimple-pimple/lib/include.php',
- 'bc707a56594837f83dcc83b3274ae09e' => __DIR__ . '/..' . '/publishpress/publishpress-welcome/src/include.php',
'41c664bd04a95c2d6a2f2a3e00f06593' => __DIR__ . '/..' . '/publishpress/wordpress-reviews/ReviewsController.php',
'a61bc28a742b9f9f2fd5ef4d2d1e2037' => __DIR__ . '/..' . '/publishpress/wordpress-version-notices/src/include.php',
);
diff --git a/lib/vendor/composer/installed.json b/lib/vendor/composer/installed.json
index 9a77265c9..5c702c0a0 100644
--- a/lib/vendor/composer/installed.json
+++ b/lib/vendor/composer/installed.json
@@ -234,98 +234,6 @@
},
"install-path": "../publishpress/psr-container"
},
- {
- "name": "publishpress/publishpress-welcome",
- "version": "dev-codex/welcome-feedback",
- "version_normalized": "dev-codex/welcome-feedback",
- "source": {
- "type": "git",
- "url": "git@github.com:publishpress/library-publishpress-welcome.git",
- "reference": "e0898f44a4d40a343459fb77ebc83727014e6b69"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/publishpress/library-publishpress-welcome/zipball/e0898f44a4d40a343459fb77ebc83727014e6b69",
- "reference": "e0898f44a4d40a343459fb77ebc83727014e6b69",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2.5"
- },
- "require-dev": {
- "overtrue/phplint": "^9.5",
- "publishpress/version-loader-generator": "^1.3.1"
- },
- "time": "2026-10-21T10:50:19+00:00",
- "type": "library",
- "extra": {
- "generator": {
- "lib-class-test": "class_exists('PublishPress\\Welcome\\WelcomeController')",
- "src-dir": "src",
- "namespace": "PublishPress\\Welcome",
- "action-register-priority": "-200",
- "action-initialize-priority": "-190",
- "generate-test-file": false
- }
- },
- "installation-source": "dist",
- "autoload": {
- "files": [
- "src/include.php"
- ]
- },
- "scripts": {
- "generate-files": [
- "vendor/bin/version-loader-generator"
- ],
- "post-install-cmd": [
- "@generate-files"
- ],
- "post-update-cmd": [
- "@generate-files"
- ],
- "check:lint": [
- "phplint --no-cache src tests"
- ],
- "test:unit": [
- "php tests/run.php"
- ],
- "test:integration": [
- "php tests/integration/version-loader.php"
- ],
- "test": [
- "@check:lint",
- "@test:unit",
- "@test:integration"
- ],
- "build": [
- "bash bin/build.sh"
- ],
- "check:release": [
- "php bin/check-release.php"
- ],
- "make:pot": [
- "bash bin/make-pot.sh"
- ]
- },
- "license": [
- "GPL-3.0-or-later"
- ],
- "authors": [
- {
- "name": "PublishPress",
- "email": "help@publishpress.com",
- "homepage": "https://publishpress.com/",
- "role": "Developer"
- }
- ],
- "description": "Reusable, configurable welcome and onboarding experiences for PublishPress WordPress plugins.",
- "support": {
- "source": "https://github.com/publishpress/library-publishpress-welcome/tree/codex/welcome-feedback",
- "issues": "https://github.com/publishpress/library-publishpress-welcome/issues"
- },
- "install-path": "../publishpress/publishpress-welcome"
- },
{
"name": "publishpress/wordpress-reviews",
"version": "1.2.2",
diff --git a/lib/vendor/publishpress/publishpress-welcome/LICENSE b/lib/vendor/publishpress/publishpress-welcome/LICENSE
deleted file mode 100644
index 3b4ecb358..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/LICENSE
+++ /dev/null
@@ -1,8 +0,0 @@
-GNU GENERAL PUBLIC LICENSE
-Version 3, 29 June 2007
-
-Copyright (C) 2007 Free Software Foundation, Inc.
-
-This project is distributed under the terms of the GNU General Public
-License version 3 or (at your option) any later version. The complete
-license text is available at https://www.gnu.org/licenses/gpl-3.0.txt.
diff --git a/lib/vendor/publishpress/publishpress-welcome/README.md b/lib/vendor/publishpress/publishpress-welcome/README.md
deleted file mode 100644
index 350b4d7d1..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/README.md
+++ /dev/null
@@ -1,147 +0,0 @@
-# PublishPress Welcome
-
-Reusable welcome tours, activation notices and setup checklists for PublishPress WordPress plugins.
-
-The library owns the shared presentation, navigation and per-user state. Each consumer supplies its own translated content, branding, routes, capability checks, product artwork and completion callbacks.
-
-## Installation
-
-```bash
-composer require publishpress/publishpress-welcome
-```
-
-Load Composer's autoloader in the plugin's main context. The generated version loader selects the newest bundled copy on `plugins_loaded`. Register consumer experiences at priority `-20` or later.
-
-```php
-add_action('plugins_loaded', function () {
- $config = new \PublishPress\Welcome\ExperienceConfig([
- 'id' => 'example-plugin',
- 'page_slug' => 'example-welcome',
- 'parent_slug' => 'example-settings',
- 'page_title' => __('Welcome to Example', 'example-plugin'),
- 'capability' => 'manage_options',
- 'exit_url' => admin_url('admin.php?page=example-settings'),
- 'checklist_pages' => ['example-settings'],
- ]);
-
- $steps = [
- new \PublishPress\Welcome\Step\HeroStep('welcome', [
- 'title' => __('Welcome to Example', 'example-plugin'),
- 'lede' => __('Learn the essentials in a few short steps.', 'example-plugin'),
- ]),
- ];
-
- (new \PublishPress\Welcome\WelcomeController($config, $steps))->register();
-}, -20);
-```
-
-Consumers may implement `PublishPress\Welcome\Contract\StepInterface` for product-specific step markup. Custom renderers are responsible for escaping their own HTML.
-
-For a small consumer-owned step, use `new CallbackStep('content', $renderer, true)` from `PublishPress\Welcome\Step`. The renderer receives `RenderContext`; `true` means it supplies its own navigation, such as a Save button. Keep nonce checks, capability checks and saving in the consuming plugin. Use `$context->stepUrlForId('next-step')` to navigate without depending on numeric positions that optional or filtered steps can change.
-
-Concept cards accept an `art_callback` instead of an image. Email capture accepts an `asset` (`url` and optional `path`) and an `after_form_callback` receiving `RenderContext` for an optional resource link outside the subscription form. Set `personas` to an empty array to omit the persona question, and `copy.confirmation_note` to an empty string when confirmation is already explained in the disclosure. Consent is unchecked unless the plugin sets `consent_prechecked`, and it is only ever sent when the user clicks Subscribe.
-
-## Translations
-
-All library strings use the `publishpress-welcome` text domain. `languages/publishpress-welcome.pot` is the translation template; regenerate it after changing strings with `composer make:pot` (needs GNU gettext). Place `publishpress-welcome-{locale}.mo` files in `languages/` and they load automatically. Consumers pass their own translated copy for everything plugin-specific.
-
-## Display modes
-
-Set `mode` in `ExperienceConfig` to choose the layout:
-
-| Mode | Look |
-| --- | --- |
-| `screen` (default) | A card inside the normal WordPress admin, like Yoast or Site Kit. |
-| `fullscreen` | Covers the admin menu and toolbar with a wizard-style stage, top bar and sticky footer, like Elementor, WooCommerce or WPForms. |
-
-Other presentation options: `brand_name`, `logo` (`url`, `alt`, `show_name`, or `dashicon`), `notice_icon_url` and `accent_color` (any hex colour; everything is derived from it). Set `allow_mode_override` to `true` during development to preview either layout with `&ppw_mode=screen|fullscreen` on the welcome URL.
-
-Built-in steps: `HeroStep`, `FeaturesStep` (icon grid), `ConceptsStep`, `LessonStep` (screenshot with numbered pins) and `FinishStep`. A step may define `hasOwnNavigation()` returning `true` to hide the shared footer's "Next" button when it renders its own call to action.
-
-## Email capture
-
-An optional, skippable step asks "what best describes you?" and, with explicit consent, for an email address. It is **enabled by default** and inserted just before the final step. The consent box is unticked unless the plugin sets `consent_prechecked` to `true`, nothing is sent until the user clicks Subscribe, and "No thanks" is always available.
-
-Configure it with the `email_capture` option, or pass `false` to turn it off:
-
-```php
-'email_capture' => [
- 'position' => 'before_last', // or 'after_first', or a 1-based step number
- 'privacy_url' => 'https://example.com/privacy/',
- 'consent_prechecked' => false, // true renders the consent box ticked; the user must still click Subscribe
- 'personas' => ['blogger' => __('Blogger', 'example'), 'agency' => __('Agency', 'example')],
- 'copy' => ['title' => __('Stay in the loop', 'example')],
- 'handler' => function (array $payload) { return true; }, // replaces the HTTP request
-],
-```
-
-Consumers can also adjust it at runtime with the `publishpress_welcome_{id}_email_capture` filter, and the outgoing data with `publishpress_welcome_{id}_subscribe_payload`.
-
-If delivery fails (endpoint down, timeout), the subscription is queued and retried in the background with a growing delay (5 minutes up to a day, five attempts, discarded after 7 days), so the user never sees an error and the lead is not lost. Retries run through WP-Cron and, as a fallback, on admin requests. Queued addresses are included in the personal data export and erasure.
-
-The subscribe/decline choice is stored per user in plugin-independent options, so a person who already answered in one PublishPress plugin is not asked again by another on the same site (`decline_snooze_days` can re-ask after a decline; `0` never does). Register experiences on `init` so the current user is known.
-
-Delivery uses `endpoint` (JSON `POST`), defaulting to `https://connect.publishpress.com/v1/subscribe`. Set an empty endpoint or `EmailSubscriber::PLACEHOLDER_ENDPOINT` for an explicit dry run. Do not release a plugin with the live default until the connector is deployed and its real forwarding checks pass. HTTP acceptance is not confirmation: MailerPress owns the confirmation email and the user must click its link before receiving campaigns.
-
-The payload contains `email`, `consent: true`, `consent_statement`, `plugin`, `persona` and `locale`; no client consent wording or timestamp is sent. Default wording uses `welcome-email-v1`. A `copy.consent_label` override must be paired with a distinct `consent_statement` ID registered with its exact wording in the connector repository, or email capture is disabled. Permanent HTTP 4xx errors (except 408/429) are dropped, while 429/503 `Retry-After` delays are respected. Unknown payload filter keys and license keys are excluded from HTTP and retry storage in this version. Retryable delivery failures remain queued locally; their addresses are included in privacy export/erasure.
-
-## What's new
-
-Show release highlights once per version, after an update:
-
-```php
-'whats_new' => [
- 'version' => '4.9.1',
- 'title' => __('What is new in Example 4.9.1', 'example'),
- 'items' => [['title' => __('Faster lists', 'example'), 'body' => __('Large tables load twice as fast.', 'example')]],
- 'url' => 'https://example.com/changelog/',
-],
-```
-
-At most three items are shown, in a dismissible card on the plugin's own screens (`notice_pages`). It stays out of the way: it never appears next to the welcome notice, expires after `notice_days`, and is skipped for anyone who has just finished or dismissed the guide.
-
-## WordPress.org compliance
-
-The defaults follow the [plugin guidelines](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/) for plugins hosted on WordPress.org:
-
-- **Guideline 11 (no admin hijacking).** The activation notice is dismissible, appears only on the plugin's own screens (`notice_pages`, defaulting to `checklist_pages`; the Dashboard needs `notice_on_dashboard`), hides itself once the guide is finished and stops after `notice_days` (14) days. The only redirect happens once, right after a confirmed first install.
-- **Guideline 7 (consent and external services).** Nothing contacts a server without a click on Subscribe with the consent box ticked. The box is unticked by default; a plugin that sets `consent_prechecked` is responsible for confirming that is acceptable for its audience. Declining or skipping sends nothing. The consuming plugin must also document the service in its `readme.txt`, for example:
-
-```
-== External services ==
-This plugin can send your email address and the answer to "what best describes you?" to PublishPress
-(publishpress.com) if you opt in on the optional welcome screen. Nothing is sent unless you tick the consent
-box and click Subscribe. Privacy policy: https://publishpress.com/privacy-policy/
-```
-
-## First-install enrollment
-
-The library never assumes that activation means a first installation. A consumer should set the configured enrollment and redirect options only when its own installation logic confirms a new install:
-
-```php
-update_option('example-plugin_welcome_enrolled', 1, false);
-update_option('example-plugin_welcome_redirect_pending', 1, false);
-```
-
-Existing sites can always open the registered welcome page manually without being enrolled for automatic notices or redirects.
-
-## Version loading
-
-`src/include.php` and `src/VersionLoader.php` are generated by `publishpress/version-loader-generator`. Do not edit those files manually. Every bundled copy registers at `plugins_loaded` priority `-200`; the newest initializes at `-190`.
-
-## CI and releases
-
-GitHub Actions checks pull requests into `development`, `master` and `release-*`, pushes to those branches, every tag, published releases, and merge queues. The `Required checks` job passes only after lint, metadata checks, PHP 7.2–8.5 runtime tests and the package build all succeed. Configure this status check as required in branch protection to block failing merges.
-
-For local checks (development tools require PHP 8.4 with DOM, mbstring and ZIP):
-
-```bash
-composer install
-composer validate --strict --no-check-version
-composer test
-composer build
-```
-
-The build produces `build/publishpress-welcome.zip`, verifies runtime assets are present and development files are excluded, then installs the ZIP in a temporary consumer directory and exercises the generated version loader through Composer's autoloader. CI retains this verified ZIP as a workflow artifact; it does not automatically publish a release.
-
-Before releasing, update `composer.json`'s version, run `composer update --lock` to refresh the lockfile and regenerate loaders, and commit all changes. Open a PR into the release branch and require green CI before merging. Tag that tested commit with the same version (for example, `0.1.0` or `v0.1.0`). Tag and release checks reject mismatched versions. Run `composer check:release -- v0.1.0` locally to check a tag before pushing. Checks triggered by a published release validate it after publication; they cannot prevent someone manually publishing an unchecked release in GitHub's UI.
diff --git a/lib/vendor/publishpress/publishpress-welcome/assets/css/welcome.css b/lib/vendor/publishpress/publishpress-welcome/assets/css/welcome.css
deleted file mode 100644
index 77c964a81..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/assets/css/welcome.css
+++ /dev/null
@@ -1,1652 +0,0 @@
-/*
- * PublishPress Welcome
- *
- * Two layouts share one component set:
- * .ppw-mode-screen the guide sits inside the WordPress admin chrome
- * .ppw-mode-fullscreen the guide covers the admin chrome (setup-wizard style)
- *
- * Consumers recolour everything through --ppw-accent / --ppw-accent-rgb / --ppw-accent-dark,
- * which the library sets from the "accent_color" option.
- */
-
-.ppw-root,
-.ppw-notice,
-.ppw-checklist {
- --ppw-accent: #655997;
- --ppw-accent-rgb: 101, 89, 151;
- --ppw-accent-dark: #4e4575;
- --ppw-accent-soft: rgba(var(--ppw-accent-rgb), .10);
- --ppw-accent-ring: rgba(var(--ppw-accent-rgb), .28);
- --ppw-ink: #0f172a;
- --ppw-text: #475569;
- --ppw-muted: #64748b;
- --ppw-line: #e6e8ef;
- --ppw-surface: #ffffff;
- --ppw-canvas: #f6f7fb;
- --ppw-radius: 16px;
- --ppw-shadow: 0 1px 2px rgba(15, 23, 42, .05), 0 12px 32px -12px rgba(15, 23, 42, .14);
- --ppw-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-
- box-sizing: border-box;
- font-family: var(--ppw-font);
- color: var(--ppw-text);
-}
-
-.ppw-root *,
-.ppw-root *::before,
-.ppw-root *::after,
-.ppw-notice *,
-.ppw-checklist * {
- box-sizing: border-box;
-}
-
-/* ----------------------------------------------------------- accessibility */
-
-.ppw-sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- margin: -1px;
- padding: 0;
- overflow: hidden;
- border: 0;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
-}
-
-.ppw-skip-link {
- position: absolute;
- z-index: 100001;
- inset-block-start: 8px;
- inset-inline-start: 8px;
- padding: 10px 16px;
- border-radius: 8px;
- background: var(--ppw-ink);
- color: #fff;
- font-size: 14px;
- font-weight: 600;
- text-decoration: none;
- transform: translateY(-200%);
-}
-
-.ppw-skip-link:focus {
- color: #fff;
- outline: 2px solid var(--ppw-accent);
- outline-offset: 2px;
- transform: none;
-}
-
-.ppw-stage:focus {
- outline: none;
-}
-
-/* ---------------------------------------------------------------- buttons */
-
-.ppw-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- min-height: 42px;
- padding: 0 20px;
- border: 1px solid transparent;
- border-radius: 999px;
- font-family: inherit;
- font-size: 14px;
- font-weight: 600;
- line-height: 1;
- text-decoration: none;
- white-space: nowrap;
- cursor: pointer;
- transition: background-color .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease, transform .15s ease;
-}
-
-.ppw-btn:focus-visible,
-.ppw-text-link:focus-visible,
-.ppw-back:focus-visible,
-.ppw-skip:focus-visible,
-.ppw-exit:focus-visible,
-.ppw-step-dot:focus-visible,
-.ppw-next-steps a:focus-visible {
- outline: 2px solid var(--ppw-accent);
- outline-offset: 3px;
- box-shadow: none;
-}
-
-.ppw-btn.ppw-btn-primary {
- background: var(--ppw-accent);
- border-color: var(--ppw-accent);
- box-shadow: 0 6px 16px -6px rgba(var(--ppw-accent-rgb), .7);
- color: #fff;
-}
-
-.ppw-btn.ppw-btn-primary:hover,
-.ppw-btn.ppw-btn-primary:focus {
- background: var(--ppw-accent-dark);
- border-color: var(--ppw-accent-dark);
- color: #fff;
- transform: translateY(-1px);
-}
-
-.ppw-btn.ppw-btn-outline {
- background: var(--ppw-surface);
- border-color: var(--ppw-line);
- color: var(--ppw-ink);
-}
-
-.ppw-btn.ppw-btn-outline:hover,
-.ppw-btn-outline:focus {
- border-color: var(--ppw-accent);
- color: var(--ppw-accent);
-}
-
-.ppw-btn.ppw-btn-ghost {
- background: transparent;
- color: var(--ppw-muted);
-}
-
-.ppw-btn.ppw-btn-ghost:hover,
-.ppw-btn-ghost:focus {
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent);
-}
-
-.ppw-btn.ppw-btn-light {
- background: #fff;
- color: var(--ppw-accent-dark);
-}
-
-.ppw-btn.ppw-btn-light:hover,
-.ppw-btn.ppw-btn-light:focus {
- background: rgba(255, 255, 255, .88);
- color: var(--ppw-accent-dark);
-}
-
-.ppw-btn-lg {
- min-height: 52px;
- padding: 0 28px;
- font-size: 15px;
-}
-
-.ppw-btn-arrow {
- transition: transform .15s ease;
-}
-
-.ppw-btn:hover .ppw-btn-arrow {
- transform: translateX(3px);
-}
-
-a.ppw-text-link,
-a.ppw-back,
-a.ppw-skip,
-a.ppw-exit {
- color: var(--ppw-muted);
- font-size: 13px;
- font-weight: 600;
- text-decoration: none;
- transition: color .15s ease;
-}
-
-a.ppw-text-link:hover,
-a.ppw-back:hover,
-a.ppw-skip:hover,
-a.ppw-exit:hover,
-a.ppw-text-link:focus,
-a.ppw-back:focus,
-a.ppw-skip:focus,
-a.ppw-exit:focus {
- color: var(--ppw-accent);
-}
-
-.ppw-back::before {
- margin-inline-end: 8px;
- content: '\2190';
-}
-
-/* Win against third-party admin link styles inside notices and cards. */
-
-.ppw-notice a,
-.ppw-checklist a,
-.ppw-root a {
- text-decoration: none;
-}
-
-.ppw-notice a.ppw-text-link,
-.ppw-checklist a.ppw-text-link {
- color: var(--ppw-muted);
-}
-
-.ppw-notice a.ppw-text-link:hover,
-.ppw-checklist a.ppw-text-link:hover {
- color: var(--ppw-accent);
-}
-
-.ppw-notice a.ppw-btn-primary,
-.ppw-notice a.ppw-btn-primary:hover {
- color: #fff;
-}
-
-.ppw-checklist a.ppw-btn-outline {
- color: var(--ppw-ink);
-}
-
-.ppw-checklist a.ppw-btn-outline:hover {
- color: var(--ppw-accent);
-}
-
-.ppw-checklist a.ppw-btn-ghost {
- color: var(--ppw-muted);
-}
-
-/* ----------------------------------------------------------------- frame */
-
-.ppw-root {
- color: var(--ppw-text);
-}
-
-.ppw-frame {
- display: flex;
- flex-direction: column;
- background: var(--ppw-surface);
-}
-
-.ppw-root h1,
-.ppw-root h2 {
- margin: 0;
- padding: 0;
- color: var(--ppw-ink);
- font-family: var(--ppw-font);
- letter-spacing: -.02em;
-}
-
-.ppw-root h1 {
- font-size: 34px;
- font-weight: 750;
- line-height: 1.15;
-}
-
-.ppw-root h2 {
- font-size: 17px;
- font-weight: 650;
- line-height: 1.3;
-}
-
-.ppw-root p {
- margin: 0;
- color: var(--ppw-text);
- font-size: 15px;
- line-height: 1.65;
-}
-
-.ppw-root .ppw-lede {
- margin-top: 14px;
- color: var(--ppw-muted);
- font-size: 17px;
- line-height: 1.6;
-}
-
-/* Top bar */
-
-.ppw-topbar {
- display: grid;
- grid-template-columns: 1fr auto 1fr;
- align-items: center;
- gap: 20px;
- min-height: 68px;
- padding: 0 28px;
-}
-
-.ppw-brand {
- display: flex;
- align-items: center;
- gap: 10px;
- min-width: 0;
- color: var(--ppw-ink);
-}
-
-.ppw-brand-logo {
- display: block;
- width: auto;
- max-width: 180px;
- height: 30px;
-}
-
-.ppw-brand-icon.dashicons {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 36px;
- height: 36px;
- border-radius: 10px;
- background: var(--ppw-accent);
- color: #fff;
- font-size: 20px;
-}
-
-.ppw-brand-name {
- overflow: hidden;
- font-size: 15px;
- font-weight: 700;
- letter-spacing: -.01em;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.ppw-steps {
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.ppw-step-dot {
- display: block;
- width: 10px;
- height: 10px;
- border-radius: 999px;
- background: #d7dbe6;
- transition: width .25s ease, background-color .25s ease;
-}
-
-.ppw-step-dot:hover {
- background: rgba(var(--ppw-accent-rgb), .5);
-}
-
-.ppw-step-dot.is-done {
- background: rgba(var(--ppw-accent-rgb), .55);
-}
-
-.ppw-step-dot.is-current {
- width: 28px;
- background: var(--ppw-accent);
-}
-
-.ppw-step-label {
- display: none;
-}
-
-.ppw-exit {
- display: inline-flex;
- align-items: center;
- justify-self: end;
- gap: 10px;
-}
-
-.ppw-exit-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 34px;
- height: 34px;
- border-radius: 999px;
- background: var(--ppw-canvas);
- font-size: 22px;
- font-weight: 400;
- line-height: 1;
- transition: background-color .15s ease;
-}
-
-.ppw-exit:hover .ppw-exit-icon {
- background: var(--ppw-accent-soft);
-}
-
-/* Progress bar */
-
-.ppw-bar {
- display: block;
- flex: 1;
- height: 4px;
- overflow: hidden;
- background: #eceef5;
-}
-
-.ppw-bar-fill {
- display: block;
- height: 100%;
- border-radius: inherit;
- background: linear-gradient(90deg, rgba(var(--ppw-accent-rgb), .75), var(--ppw-accent));
- transition: width .4s cubic-bezier(.4, 0, .2, 1);
-}
-
-.ppw-root > .ppw-frame > .ppw-bar {
- flex: none;
-}
-
-/* Stage */
-
-.ppw-stage {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 56px 40px;
-}
-
-.ppw-body {
- width: 100%;
- max-width: 1040px;
- animation: ppw-rise .45s cubic-bezier(.2, .7, .2, 1) both;
-}
-
-@keyframes ppw-rise {
- from {
- opacity: 0;
- transform: translateY(10px);
- }
- to {
- opacity: 1;
- transform: none;
- }
-}
-
-.ppw-heading {
- max-width: 640px;
- margin: 0 auto 40px;
- text-align: center;
-}
-
-/* Footer navigation */
-
-.ppw-navigation {
- display: grid;
- grid-template-columns: 1fr auto 1fr;
- align-items: center;
- gap: 20px;
- min-height: 76px;
- padding: 0 28px;
- border-top: 1px solid var(--ppw-line);
- background: rgba(255, 255, 255, .9);
-}
-
-.ppw-nav-start {
- display: flex;
- align-items: center;
-}
-
-.ppw-nav-end {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: 20px;
-}
-
-.ppw-root .ppw-reassurance {
- margin: 0;
- color: #64748b;
- font-size: 12.5px;
- text-align: center;
-}
-
-/* ------------------------------------------------------- mode: full screen */
-
-body.ppw-is-fullscreen {
- overflow: hidden;
-}
-
-body.ppw-is-fullscreen #wpadminbar,
-body.ppw-is-fullscreen #adminmenumain,
-body.ppw-is-fullscreen #wpfooter {
- display: none;
-}
-
-.ppw-mode-fullscreen {
- position: fixed;
- z-index: 100000;
- inset: 0;
- overflow-y: auto;
- background:
- radial-gradient(1000px 520px at 85% -10%, rgba(var(--ppw-accent-rgb), .10), transparent 60%),
- radial-gradient(800px 480px at -10% 110%, rgba(var(--ppw-accent-rgb), .07), transparent 60%),
- #fbfbfe;
-}
-
-.ppw-mode-fullscreen .ppw-frame {
- min-height: 100%;
- background: transparent;
-}
-
-.ppw-mode-fullscreen .ppw-topbar {
- background: rgba(255, 255, 255, .72);
- backdrop-filter: blur(8px);
-}
-
-.ppw-mode-fullscreen .ppw-navigation {
- position: sticky;
- bottom: 0;
-}
-
-/* --------------------------------------------------------- mode: in screen */
-
-.wrap.ppw-mode-screen {
- max-width: 1120px;
- margin: 28px auto 48px;
- padding: 0;
- padding-inline-end: 20px;
-}
-
-.ppw-mode-screen .ppw-frame {
- overflow: hidden;
- border: 1px solid var(--ppw-line);
- border-radius: 20px;
- box-shadow: var(--ppw-shadow);
-}
-
-.ppw-mode-screen .ppw-topbar {
- border-bottom: 1px solid var(--ppw-line);
-}
-
-.ppw-mode-screen .ppw-stage {
- padding: 48px 48px 56px;
- background:
- radial-gradient(700px 320px at 100% 0%, rgba(var(--ppw-accent-rgb), .07), transparent 65%),
- var(--ppw-surface);
-}
-
-.ppw-mode-screen .ppw-navigation {
- background: #fafbfd;
-}
-
-.ppw-mode-screen .ppw-exit-label {
- display: none;
-}
-
-/* ------------------------------------------------------------------ hero */
-
-.ppw-hero {
- display: grid;
- grid-template-columns: minmax(0, 1fr);
- align-items: center;
- gap: 56px;
-}
-
-.ppw-hero.has-art {
- grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
-}
-
-.ppw-hero:not(.has-art) {
- max-width: 680px;
- margin: 0 auto;
- text-align: center;
-}
-
-.ppw-hero:not(.has-art) .ppw-hero-actions,
-.ppw-hero:not(.has-art) .ppw-highlights {
- justify-content: center;
-}
-
-.ppw-root .ppw-hero h1 {
- font-size: 44px;
- line-height: 1.08;
- letter-spacing: -.03em;
-}
-
-.ppw-eyebrow {
- display: inline-block;
- margin-bottom: 20px;
- padding: 7px 14px;
- border-radius: 999px;
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent-dark);
- font-size: 13px;
- font-weight: 650;
-}
-
-.ppw-root .ppw-hero-body {
- margin-top: 14px;
-}
-
-.ppw-highlights {
- display: flex;
- flex-wrap: wrap;
- gap: 10px 22px;
- margin: 24px 0 0;
- padding: 0;
- list-style: none;
-}
-
-.ppw-highlights li {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- margin: 0;
- color: var(--ppw-ink);
- font-size: 14px;
- font-weight: 550;
-}
-
-.ppw-highlights li::before {
- display: inline-block;
- flex: none;
- width: 18px;
- height: 18px;
- border-radius: 50%;
- background: var(--ppw-accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6.2l2 2 4-4.4' fill='none' stroke='%23fff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat;
- content: '';
-}
-
-.ppw-hero-actions,
-.ppw-finish {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 14px 24px;
- margin-top: 32px;
-}
-
-.ppw-hero-art {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 400px;
- padding: 32px;
- overflow: hidden;
- border-radius: 28px;
- background:
- radial-gradient(circle at 20% 15%, rgba(255, 255, 255, .55), transparent 45%),
- linear-gradient(145deg, rgba(var(--ppw-accent-rgb), .22), rgba(var(--ppw-accent-rgb), .08) 60%, rgba(var(--ppw-accent-rgb), .18));
-}
-
-.ppw-hero-art .ppw-figure {
- max-width: 100%;
- border: 0;
- background: transparent;
- box-shadow: none;
-}
-
-/* -------------------------------------------------------------- features */
-
-.ppw-features {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 32px 40px;
- margin: 0;
- padding: 0;
- list-style: none;
-}
-
-.ppw-features.ppw-cols-2 {
- grid-template-columns: repeat(2, minmax(0, 1fr));
-}
-
-.ppw-feature {
- display: flex;
- align-items: flex-start;
- gap: 16px;
- margin: 0;
-}
-
-.ppw-feature-icon.dashicons {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 44px;
- height: 44px;
- border-radius: 12px;
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent);
- font-size: 22px;
-}
-
-.ppw-feature-copy {
- display: flex;
- flex-direction: column;
- gap: 4px;
-}
-
-.ppw-feature-copy strong {
- color: var(--ppw-ink);
- font-size: 15px;
- font-weight: 650;
-}
-
-.ppw-feature-copy span {
- color: var(--ppw-muted);
- font-size: 14px;
- line-height: 1.55;
-}
-
-/* -------------------------------------------------------------- concepts */
-
-.ppw-concepts {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
- gap: 24px;
-}
-
-.ppw-concept {
- display: flex;
- flex-direction: column;
- padding: 14px 14px 26px;
- border: 1px solid var(--ppw-line);
- border-radius: var(--ppw-radius);
- background: var(--ppw-surface);
- transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
-}
-
-.ppw-concept:hover {
- border-color: rgba(var(--ppw-accent-rgb), .35);
- box-shadow: var(--ppw-shadow);
- transform: translateY(-3px);
-}
-
-.ppw-concept .ppw-figure {
- margin-bottom: 20px;
-}
-
-.ppw-concept h2,
-.ppw-concept p {
- padding: 0 12px;
-}
-
-.ppw-concept h2 {
- display: flex;
- align-items: center;
- gap: 10px;
- margin-bottom: 8px;
- font-size: 18px;
-}
-
-.ppw-concept-number {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border-radius: 50%;
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent-dark);
- font-size: 12px;
- font-weight: 700;
-}
-
-.ppw-concept p {
- font-size: 14px;
-}
-
-.ppw-root .ppw-aside {
- max-width: 640px;
- margin: 32px auto 0;
- padding: 16px 20px;
- border-radius: 12px;
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent-dark);
- font-size: 14px;
- text-align: center;
-}
-
-/* ---------------------------------------------------------------- lesson */
-
-.ppw-lesson {
- display: grid;
- grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
- align-items: center;
- gap: 56px;
-}
-
-.ppw-lesson-copy h1 {
- font-size: 32px;
-}
-
-.ppw-callouts {
- display: grid;
- gap: 14px;
- margin: 28px 0 0;
- padding: 0;
- list-style: none;
- counter-reset: ppw-callout;
-}
-
-.ppw-callouts li {
- display: flex;
- align-items: flex-start;
- gap: 14px;
- margin: 0;
- color: var(--ppw-ink);
- font-size: 15px;
- font-weight: 500;
- line-height: 1.5;
- counter-increment: ppw-callout;
-}
-
-.ppw-callouts li::before {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- border-radius: 50%;
- background: var(--ppw-accent);
- color: #fff;
- font-size: 13px;
- font-weight: 700;
- content: counter(ppw-callout);
-}
-
-.ppw-root .ppw-link-out {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 12px 16px;
- margin: 32px 0 0;
-}
-
-.ppw-link-note {
- color: #64748b;
- font-size: 12.5px;
-}
-
-/* Figures: screenshots get a browser frame, diagrams are drawn edge to edge. */
-
-.ppw-figure {
- position: relative;
- overflow: hidden;
- border: 1px solid var(--ppw-line);
- border-radius: 14px;
- background: var(--ppw-surface);
- box-shadow: var(--ppw-shadow);
-}
-
-.ppw-lesson-art .ppw-figure {
- padding-top: 30px;
-}
-
-.ppw-lesson-art .ppw-figure::before {
- position: absolute;
- top: 0;
- inset-inline: 0;
- height: 30px;
- border-bottom: 1px solid var(--ppw-line);
- background:
- radial-gradient(circle 4px at 16px 15px, #ff6b6b 98%, transparent),
- radial-gradient(circle 4px at 30px 15px, #fbbf24 98%, transparent),
- radial-gradient(circle 4px at 44px 15px, #34d399 98%, transparent),
- #f8f9fc;
- content: '';
-}
-
-.ppw-figure img {
- display: block;
- width: 100%;
- height: auto;
-}
-
-.ppw-figure-placeholder {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 220px;
- padding: 24px;
- background: repeating-linear-gradient(135deg, #f8f9fc, #f8f9fc 10px, #f2f4f9 10px, #f2f4f9 20px);
- color: #64748b;
- font-size: 13px;
- text-align: center;
-}
-
-.ppw-pin {
- position: absolute;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- border: 2px solid #fff;
- border-radius: 50%;
- background: var(--ppw-accent);
- box-shadow: 0 0 0 5px var(--ppw-accent-ring), 0 4px 10px rgba(15, 23, 42, .2);
- color: #fff;
- font-size: 13px;
- font-weight: 700;
-}
-
-.ppw-pin-1 { top: 22%; left: 16%; }
-.ppw-pin-2 { top: 46%; left: 48%; }
-.ppw-pin-3 { right: 14%; bottom: 16%; }
-
-/* ---------------------------------------------------------------- finish */
-
-.ppw-finish-head {
- max-width: 620px;
- margin: 0 auto;
- text-align: center;
-}
-
-.ppw-success {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 72px;
- height: 72px;
- margin-bottom: 24px;
- border-radius: 50%;
- background: #dcfce7;
- box-shadow: 0 0 0 10px #f0fdf4;
-}
-
-.ppw-success-mark {
- display: block;
- width: 28px;
- height: 28px;
- background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5' fill='none' stroke='%2316a34a' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
-}
-
-.ppw-root .ppw-next-steps-title {
- max-width: 560px;
- margin: 40px auto 14px;
- color: var(--ppw-muted);
- font-size: 12px;
- font-weight: 700;
- letter-spacing: .08em;
- text-transform: uppercase;
-}
-
-.ppw-next-steps {
- display: grid;
- gap: 10px;
- max-width: 560px;
- margin: 0 auto;
- padding: 0;
- list-style: none;
- counter-reset: ppw-next;
-}
-
-.ppw-next-steps li {
- margin: 0;
- counter-increment: ppw-next;
-}
-
-.ppw-next-steps a {
- display: flex;
- align-items: center;
- gap: 14px;
- padding: 14px 18px;
- border: 1px solid var(--ppw-line);
- border-radius: 14px;
- background: var(--ppw-surface);
- color: var(--ppw-ink);
- font-size: 15px;
- font-weight: 550;
- text-decoration: none;
- transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
-}
-
-.ppw-next-steps a::before {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- border-radius: 50%;
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent-dark);
- font-size: 13px;
- font-weight: 700;
- content: counter(ppw-next);
-}
-
-.ppw-next-steps a:hover {
- border-color: rgba(var(--ppw-accent-rgb), .45);
- box-shadow: var(--ppw-shadow);
- color: var(--ppw-ink);
- transform: translateY(-1px);
-}
-
-.ppw-next-label {
- flex: 1;
-}
-
-.ppw-next-arrow {
- color: var(--ppw-accent);
- transition: transform .15s ease;
-}
-
-.ppw-next-steps a:hover .ppw-next-arrow {
- transform: translateX(3px);
-}
-
-.ppw-finish {
- justify-content: center;
- margin-top: 36px;
-}
-
-.ppw-promo {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 32px;
- max-width: 760px;
- margin: 44px auto 0;
- padding: 28px 32px;
- border-radius: 20px;
- background: linear-gradient(135deg, var(--ppw-accent), var(--ppw-accent-dark));
- color: #fff;
-}
-
-.ppw-root .ppw-promo h2 {
- margin-bottom: 12px;
- color: #fff;
- font-size: 18px;
-}
-
-.ppw-promo ul {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, auto));
- gap: 6px 28px;
- margin: 0;
- padding: 0;
- list-style: none;
-}
-
-.ppw-promo li {
- margin: 0;
- color: rgba(255, 255, 255, .88);
- font-size: 13.5px;
-}
-
-.ppw-promo li::before {
- margin-inline-end: 8px;
- content: '\2713';
- font-weight: 700;
-}
-
-/* ----------------------------------------------------------- email capture */
-
-.ppw-capture {
- display: grid;
- gap: 26px;
- max-width: 560px;
- margin: 0 auto;
-}
-
-.ppw-capture .ppw-field {
- display: grid;
- gap: 10px;
- margin: 0;
- padding: 0;
- border: 0;
-}
-
-.ppw-capture .ppw-field > label,
-.ppw-capture legend {
- padding: 0;
- color: var(--ppw-ink);
- font-size: 14px;
- font-weight: 650;
-}
-
-.ppw-personas {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
-}
-
-.ppw-persona {
- position: relative;
- cursor: pointer;
-}
-
-.ppw-persona input {
- position: absolute;
- opacity: 0;
- pointer-events: none;
-}
-
-.ppw-persona span {
- display: inline-block;
- padding: 10px 18px;
- border: 1px solid var(--ppw-line);
- border-radius: 999px;
- background: var(--ppw-surface);
- color: var(--ppw-ink);
- font-size: 14px;
- font-weight: 550;
- transition: border-color .15s ease, background-color .15s ease, color .15s ease;
-}
-
-.ppw-persona:hover span {
- border-color: rgba(var(--ppw-accent-rgb), .5);
-}
-
-.ppw-persona input:checked + span {
- border-color: var(--ppw-accent);
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent-dark);
-}
-
-.ppw-persona input:focus-visible + span {
- outline: 2px solid var(--ppw-accent);
- outline-offset: 2px;
-}
-
-.ppw-root .ppw-input {
- width: 100%;
- min-height: 48px;
- padding: 0 16px;
- border: 1px solid var(--ppw-line);
- border-radius: 12px;
- background: var(--ppw-surface);
- box-shadow: none;
- color: var(--ppw-ink);
- font-size: 15px;
-}
-
-.ppw-root .ppw-input:focus {
- border-color: var(--ppw-accent);
- box-shadow: 0 0 0 4px var(--ppw-accent-ring);
- outline: 0;
-}
-
-.ppw-consent {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- color: var(--ppw-text);
- font-size: 14px;
- line-height: 1.55;
- cursor: pointer;
-}
-
-.ppw-root .ppw-consent input[type="checkbox"] {
- flex: none;
- width: 20px;
- height: 20px;
- margin: 1px 0 0;
- border-color: #b8bfce;
- border-radius: 6px;
-}
-
-.ppw-root .ppw-consent input[type="checkbox"]:checked {
- border-color: var(--ppw-accent);
- background: var(--ppw-accent);
-}
-
-.ppw-root .ppw-consent input[type="checkbox"]:checked::before {
- margin: 0;
- width: 18px;
- height: 18px;
- content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 10.5l3.2 3.2L15 6.8' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
-}
-
-.ppw-root .ppw-disclosure a {
- color: var(--ppw-accent);
-}
-
-.ppw-root .ppw-disclosure {
- margin: -8px 0 0;
- color: #64748b;
- font-size: 12.5px;
- line-height: 1.55;
-}
-
-.ppw-root .ppw-capture-error {
- padding: 12px 16px;
- border: 1px solid #fecaca;
- border-radius: 12px;
- background: #fef2f2;
- color: #b91c1c;
- font-size: 14px;
-}
-
-.ppw-capture-actions {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 14px 24px;
-}
-
-.ppw-root .ppw-link-button {
- padding: 4px;
- border: 0;
- background: none;
- box-shadow: none;
- color: var(--ppw-muted);
- font-family: inherit;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- transition: color .15s ease;
-}
-
-.ppw-root .ppw-link-button:hover,
-.ppw-root .ppw-link-button:focus-visible {
- color: var(--ppw-accent);
-}
-
-/* ---------------------------------------------------------------- notice */
-
-.notice.ppw-notice {
- margin: 20px 0 8px;
- margin-inline-end: 20px;
- padding: 0;
- border: 1px solid var(--ppw-line);
- border-inline-start: 4px solid var(--ppw-accent);
- border-radius: 14px;
- background: var(--ppw-surface);
- box-shadow: var(--ppw-shadow);
-}
-
-.ppw-notice-inner {
- display: flex;
- align-items: center;
- gap: 18px;
- padding: 18px 22px;
-}
-
-.ppw-notice-icon {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 46px;
- height: 46px;
- overflow: hidden;
- border-radius: 12px;
- background: var(--ppw-accent-soft);
- color: var(--ppw-accent);
-}
-
-.ppw-notice-icon .dashicons {
- width: 24px;
- height: 24px;
- font-size: 24px;
-}
-
-.ppw-notice-icon img {
- display: block;
- width: 28px;
- height: 28px;
- object-fit: contain;
-}
-
-.ppw-notice-copy {
- display: flex;
- flex: 1;
- flex-direction: column;
- gap: 2px;
-}
-
-.ppw-notice-copy strong {
- color: var(--ppw-ink);
- font-size: 15px;
- font-weight: 650;
-}
-
-.ppw-notice-copy span {
- color: var(--ppw-muted);
- font-size: 13.5px;
- line-height: 1.5;
-}
-
-.ppw-notice-actions {
- display: flex;
- flex: none;
- align-items: center;
- gap: 18px;
-}
-
-.ppw-whatsnew-list {
- display: grid;
- gap: 6px;
- margin: 8px 0 0;
- padding: 0;
- list-style: none;
-}
-
-.ppw-whatsnew-list li {
- display: block;
- margin: 0;
- color: var(--ppw-muted);
- font-size: 13.5px;
- line-height: 1.5;
-}
-
-.ppw-whatsnew-list li strong {
- color: var(--ppw-ink);
- font-size: 13.5px;
- font-weight: 650;
-}
-
-.ppw-whatsnew-list li strong::after {
- content: ' \2013 ';
- color: var(--ppw-muted);
- font-weight: 400;
-}
-
-.ppw-whatsnew-list li strong:last-child::after {
- content: '';
-}
-
-.ppw-notice .ppw-notice-icon .dashicons-star-filled {
- color: #f59e0b;
-}
-
-/* ------------------------------------------------------------- checklist */
-
-.ppw-checklist {
- clear: both;
- min-width: 0;
- max-width: 100%;
- margin: 20px 0 8px;
- margin-inline-end: 20px;
- overflow: hidden;
- border: 1px solid var(--ppw-line);
- border-radius: 16px;
- background: var(--ppw-surface);
- box-shadow: var(--ppw-shadow);
-}
-
-.ppw-checklist-head {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 18px;
- padding: 18px 22px;
-}
-
-.ppw-ring {
- --ppw-percent: 0;
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 52px;
- height: 52px;
- border-radius: 50%;
- background: conic-gradient(var(--ppw-accent) calc(var(--ppw-percent) * 1%), #eceef5 0);
-}
-
-.ppw-ring-value {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- border-radius: 50%;
- background: var(--ppw-surface);
- color: var(--ppw-ink);
- font-size: 11.5px;
- font-weight: 700;
-}
-
-.ppw-checklist-title {
- display: flex;
- flex: 1;
- min-width: min(100%, 180px);
- flex-direction: column;
- gap: 10px;
-}
-
-.ppw-checklist h2 {
- margin: 0;
- color: var(--ppw-ink);
- font-size: 16px;
- font-weight: 650;
-}
-
-.ppw-checklist .ppw-bar {
- flex: none;
- height: 6px;
- max-width: 320px;
- border-radius: 999px;
-}
-
-.ppw-tasks {
- margin: 0;
- padding: 0;
- border-top: 1px solid var(--ppw-line);
- list-style: none;
-}
-
-.ppw-tasks li {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 16px;
- margin: 0;
- padding: 14px 22px;
- border-bottom: 1px solid var(--ppw-line);
-}
-
-.ppw-tasks li:last-child {
- border-bottom: 0;
-}
-
-.ppw-checkmark {
- display: inline-flex;
- flex: none;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border: 2px solid #cdd2de;
- border-radius: 50%;
-}
-
-.ppw-tasks li.is-complete .ppw-checkmark {
- border-color: var(--ppw-accent);
- background: var(--ppw-accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6.2l2 2 4-4.4' fill='none' stroke='%23fff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 14px no-repeat;
-}
-
-.ppw-task-copy {
- display: flex;
- flex: 1;
- min-width: min(100%, 160px);
- overflow-wrap: anywhere;
- flex-direction: column;
- gap: 2px;
-}
-
-.ppw-task-label {
- color: var(--ppw-ink);
- font-size: 14.5px;
- font-weight: 600;
-}
-
-.ppw-tasks li.is-complete .ppw-task-label {
- color: var(--ppw-muted);
- text-decoration: line-through;
- text-decoration-color: #cbd5e1;
-}
-
-.ppw-task-detail {
- color: var(--ppw-muted);
- font-size: 12.5px;
-}
-
-.ppw-checklist .ppw-btn {
- min-height: 36px;
- padding: 0 16px;
- font-size: 13px;
-}
-
-/* ------------------------------------------------------------ responsive */
-
-.ppw-concept-art {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 180px;
- margin-bottom: 20px;
-}
-
-.ppw-capture-layout.has-art {
- display: grid;
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
- align-items: center;
- gap: 40px;
-}
-
-.ppw-capture-layout.has-art .ppw-heading {
- text-align: start;
- margin-bottom: 24px;
-}
-
-.ppw-capture-content,
-.ppw-capture-art {
- min-width: 0;
-}
-
-.ppw-capture-art img {
- display: block;
- max-width: 100%;
- height: auto;
-}
-
-@media (max-width: 960px) {
- .ppw-capture-layout.has-art {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .ppw-capture-art {
- display: none;
- }
- .ppw-hero.has-art,
- .ppw-lesson {
- grid-template-columns: minmax(0, 1fr);
- gap: 36px;
- }
-
- .ppw-hero-art {
- min-height: 260px;
- }
-
- .ppw-features,
- .ppw-concepts {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-}
-
-@media (max-width: 782px) {
- .wrap.ppw-mode-screen {
- padding-inline-end: 10px;
- }
-
- .ppw-topbar,
- .ppw-navigation {
- padding-right: 16px;
- padding-left: 16px;
- }
-
- .ppw-topbar {
- grid-template-columns: minmax(0, 1fr) auto auto;
- gap: 10px;
- }
-
- .ppw-brand-logo {
- max-width: 110px;
- height: auto;
- max-height: 28px;
- }
-
- .ppw-brand-name {
- font-size: 12px;
- }
-
- .ppw-steps {
- display: none;
- }
-
- .ppw-step-label {
- display: block;
- color: var(--ppw-muted);
- font-size: 12px;
- font-weight: 600;
- }
-
- .ppw-exit-label {
- display: none;
- }
-
- .ppw-stage,
- .ppw-mode-screen .ppw-stage {
- padding: 32px 20px 40px;
- }
-
- .ppw-root h1,
- .ppw-root .ppw-hero h1 {
- font-size: 28px;
- }
-
- .ppw-features,
- .ppw-features.ppw-cols-2,
- .ppw-concepts {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .ppw-navigation {
- grid-template-columns: auto 1fr;
- padding-top: 12px;
- padding-bottom: 12px;
- }
-
- .ppw-root .ppw-reassurance {
- display: none;
- }
-
- .ppw-promo {
- flex-direction: column;
- align-items: flex-start;
- }
-
- .ppw-promo ul {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .ppw-notice-inner {
- flex-wrap: wrap;
- }
-
- .ppw-notice-copy {
- flex-basis: calc(100% - 70px);
- }
-
- .ppw-checklist-head {
- flex-wrap: wrap;
- }
-
- .ppw-tasks li {
- flex-wrap: wrap;
- }
-}
-
-@media (prefers-reduced-motion: reduce) {
- .ppw-body,
- .ppw-bar-fill,
- .ppw-btn,
- .ppw-btn-arrow,
- .ppw-concept,
- .ppw-step-dot,
- .ppw-next-steps a {
- animation: none;
- transition: none;
- }
-}
-
-/* ------------------------------------------------------------------- RTL */
-
-/* Layout uses logical properties, so only directional glyphs and gradients need mirroring.
- * Screenshot pins stay physical on purpose: they point at coordinates inside an unmirrored image. */
-
-body.rtl .ppw-back::before {
- content: '\2192';
-}
-
-body.rtl .ppw-btn-arrow,
-body.rtl .ppw-next-arrow {
- display: inline-block;
- transform: scaleX(-1);
-}
-
-body.rtl .ppw-btn:hover .ppw-btn-arrow {
- transform: scaleX(-1) translateX(3px);
-}
-
-body.rtl .ppw-next-steps a:hover .ppw-next-arrow {
- transform: scaleX(-1) translateX(3px);
-}
-
-body.rtl .ppw-bar-fill {
- background: linear-gradient(270deg, rgba(var(--ppw-accent-rgb), .75), var(--ppw-accent));
-}
diff --git a/lib/vendor/publishpress/publishpress-welcome/composer.json b/lib/vendor/publishpress/publishpress-welcome/composer.json
deleted file mode 100644
index 962fdae8a..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/composer.json
+++ /dev/null
@@ -1,65 +0,0 @@
-{
- "name": "publishpress/publishpress-welcome",
- "description": "Reusable, configurable welcome and onboarding experiences for PublishPress WordPress plugins.",
- "type": "library",
- "version": "0.1.0",
- "license": "GPL-3.0-or-later",
- "authors": [
- {
- "name": "PublishPress",
- "email": "help@publishpress.com",
- "homepage": "https://publishpress.com/",
- "role": "Developer"
- }
- ],
- "minimum-stability": "stable",
- "prefer-stable": true,
- "require": {
- "php": ">=7.2.5"
- },
- "require-dev": {
- "publishpress/version-loader-generator": "^1.3.1",
- "overtrue/phplint": "^9.5"
- },
- "config": {
- "preferred-install": "dist",
- "allow-plugins": {
- "dealerdirect/phpcodesniffer-composer-installer": true
- }
- },
- "autoload": {
- "files": [
- "src/include.php"
- ]
- },
- "scripts": {
- "generate-files": "vendor/bin/version-loader-generator",
- "post-install-cmd": [
- "@generate-files"
- ],
- "post-update-cmd": [
- "@generate-files"
- ],
- "check:lint": "phplint --no-cache src tests",
- "test:unit": "php tests/run.php",
- "test:integration": "php tests/integration/version-loader.php",
- "test": [
- "@check:lint",
- "@test:unit",
- "@test:integration"
- ],
- "build": "bash bin/build.sh",
- "check:release": "php bin/check-release.php",
- "make:pot": "bash bin/make-pot.sh"
- },
- "extra": {
- "generator": {
- "lib-class-test": "class_exists('PublishPress\\Welcome\\WelcomeController')",
- "src-dir": "src",
- "namespace": "PublishPress\\Welcome",
- "action-register-priority": "-200",
- "action-initialize-priority": "-190",
- "generate-test-file": false
- }
- }
-}
diff --git a/lib/vendor/publishpress/publishpress-welcome/languages/publishpress-welcome.pot b/lib/vendor/publishpress/publishpress-welcome/languages/publishpress-welcome.pot
deleted file mode 100644
index 20e315c3b..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/languages/publishpress-welcome.pot
+++ /dev/null
@@ -1,229 +0,0 @@
-# PublishPress Welcome translation template.
-# Copyright (C) PublishPress
-# This file is distributed under the GPL-3.0-or-later license.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: PublishPress Welcome 0.1.0\n"
-"Report-Msgid-Bugs-To: https://github.com/publishpress/library-publishpress-"
-"welcome/issues\n"
-"POT-Creation-Date: 2026-10-19 23:18+0700\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-msgid "Welcome! Would you like a quick tour?"
-msgstr ""
-
-msgid ""
-"We will show you where the important settings are. Nothing is changed while "
-"you read."
-msgstr ""
-
-msgid "Finish setting up the plugin"
-msgstr ""
-
-msgid "Setup is complete"
-msgstr ""
-
-msgid "Site owner"
-msgstr ""
-
-msgid "Writer or editor"
-msgstr ""
-
-msgid "Agency or freelancer"
-msgstr ""
-
-msgid "Developer"
-msgstr ""
-
-msgid "Something else"
-msgstr ""
-
-msgid "Stay in the loop"
-msgstr ""
-
-msgid ""
-"Tell us a little about how you use WordPress and we will send tips and "
-"offers that fit. This is optional, and everything works if you skip it."
-msgstr ""
-
-msgid "What best describes you?"
-msgstr ""
-
-msgid "Email address"
-msgstr ""
-
-msgid ""
-"Yes, send me tips, product news and offers tailored to how I use this "
-"plugin. I can unsubscribe at any time."
-msgstr ""
-
-msgid "Privacy policy"
-msgstr ""
-
-#, php-format
-msgid ""
-"Your email address and your answer are sent to %s when you subscribe. "
-"Nothing is sent if you skip."
-msgstr ""
-
-msgid "Subscribe"
-msgstr ""
-
-msgid "No thanks"
-msgstr ""
-
-msgid "Continue"
-msgstr ""
-
-msgid "Check your inbox"
-msgstr ""
-
-msgid ""
-"Please confirm your email using the link from PublishPress before receiving "
-"tips and offers."
-msgstr ""
-
-msgid ""
-"Check your inbox for a confirmation link after subscribing. Confirm your "
-"email before receiving tips and offers."
-msgstr ""
-
-msgid ""
-"We could not accept this subscription. Please try again later or choose \"No "
-"thanks\"."
-msgstr ""
-
-msgid "Please enter a valid email address."
-msgstr ""
-
-msgid ""
-"Please tick the box to confirm you want to hear from us, or choose \"No "
-"thanks\"."
-msgstr ""
-
-msgid "Back"
-msgstr ""
-
-msgid "Next"
-msgstr ""
-
-msgid "Skip"
-msgstr ""
-
-msgid "Exit guide"
-msgstr ""
-
-msgid "Guide steps"
-msgstr ""
-
-msgid "Guide navigation"
-msgstr ""
-
-msgid "Skip to the guide content"
-msgstr ""
-
-msgid "(opens in a new tab)"
-msgstr ""
-
-msgid "Completed:"
-msgstr ""
-
-msgid "Not completed:"
-msgstr ""
-
-msgid "See the full changelog"
-msgstr ""
-
-msgid "Finish"
-msgstr ""
-
-msgid "Start the tour"
-msgstr ""
-
-msgid "Continue the tour"
-msgstr ""
-
-msgid "Dismiss"
-msgstr ""
-
-msgid "Hide"
-msgstr ""
-
-msgid "Do it"
-msgstr ""
-
-msgid "View"
-msgstr ""
-
-msgid "Replay the guide"
-msgstr ""
-
-#, php-format
-msgid "Step %1$s of %2$s"
-msgstr ""
-
-msgid "This guide does not change any setting on your site."
-msgstr ""
-
-msgid "PublishPress welcome guide"
-msgstr ""
-
-msgid "Pending subscription (delivery will be retried)"
-msgstr ""
-
-msgid "Email subscription choice"
-msgstr ""
-
-msgid "Answer to \"what best describes you?\""
-msgstr ""
-
-msgid "Suggested text for the optional PublishPress welcome guide:"
-msgstr ""
-
-msgid ""
-"If you opt in on the welcome guide, we receive your email address, your "
-"answer to \"what best describes you?\" and an identifier for the consent "
-"statement shown to you. PublishPress records the receive time and asks you "
-"to confirm your email before sending tips and offers. Nothing is sent unless "
-"you choose Subscribe with the consent box ticked. The guide also stores your "
-"progress and your choice on this site; both are included in the personal "
-"data export and removed by the erasure tool, and you can unsubscribe at any "
-"time."
-msgstr ""
-
-#. translators: 1: step number, 2: step ID
-#, php-format
-msgid "Step %1$d (%2$s)"
-msgstr ""
-
-#. translators: 1: number of views, 2: number of exits
-#, php-format
-msgid "%1$d views, %2$d exits"
-msgstr ""
-
-msgid "Completed the guide"
-msgstr ""
-
-msgid "Subscribed by email"
-msgstr ""
-
-msgid "Declined the email step"
-msgstr ""
-
-msgid "A welcome experience must retain at least one step."
-msgstr ""
-
-msgid "Filtered welcome steps must implement StepInterface."
-msgstr ""
-
-msgid "You are not allowed to access this page."
-msgstr ""
-
-#. translators: %s: experience ID
-#, php-format
-msgid "PublishPress welcome guide: %s"
-msgstr ""
diff --git a/lib/vendor/publishpress/publishpress-welcome/src/ChecklistTask.php b/lib/vendor/publishpress/publishpress-welcome/src/ChecklistTask.php
deleted file mode 100644
index 5460bb70b..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/src/ChecklistTask.php
+++ /dev/null
@@ -1,55 +0,0 @@
-id = $id;
- $this->label = (string) $label;
- $this->url = (string) $url;
- $this->completeCallback = $completeCallback;
- $this->detailCallback = $detailCallback;
- }
-
- public function getId()
- {
- return $this->id;
- }
-
- public function getLabel()
- {
- return $this->label;
- }
-
- public function getUrl()
- {
- return $this->url;
- }
-
- public function isComplete()
- {
- return (bool) call_user_func($this->completeCallback);
- }
-
- public function getDetail()
- {
- return $this->detailCallback ? (string) call_user_func($this->detailCallback) : '';
- }
-}
diff --git a/lib/vendor/publishpress/publishpress-welcome/src/Contract/ChecklistTaskInterface.php b/lib/vendor/publishpress/publishpress-welcome/src/Contract/ChecklistTaskInterface.php
deleted file mode 100644
index efd8c9785..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/src/Contract/ChecklistTaskInterface.php
+++ /dev/null
@@ -1,21 +0,0 @@
- */
- private static $senders = [];
-
- /** @var bool */
- private static $hooked = false;
-
- /**
- * Register how to deliver for an experience. Items of unregistered experiences are left untouched.
- */
- public static function registerSender($experience, callable $sender)
- {
- self::$senders[(string) $experience] = $sender;
-
- if (self::$hooked) {
- return;
- }
-
- self::$hooked = true;
-
- add_action(self::CRON_HOOK, [__CLASS__, 'process']);
- add_action('admin_init', [__CLASS__, 'maybeProcess']);
- }
-
- /**
- * Queue a payload whose first delivery attempt has just failed.
- */
- public static function enqueue($experience, array $payload, $now = null, $result = null)
- {
- if (! self::retryable($result)) {
- return;
- }
- $now = null === $now ? time() : (int) $now;
- $queue = self::load();
-
- $queue[] = [
- 'experience' => (string) $experience,
- 'payload' => EmailSubscriber::contractPayload($payload),
- 'attempts' => 1,
- 'created' => $now,
- 'next' => $now + self::retryDelay(1, $result),
- ];
-
- // Keep the newest entries if the queue ever grows unreasonably.
- $queue = array_slice($queue, -self::MAX_ITEMS);
-
- self::save($queue);
- self::schedule($queue);
- }
-
- /** @return int Seconds to wait after the given number of failed attempts. */
- public static function backoffSeconds($attempts)
- {
- $index = min(max((int) $attempts, 1), count(self::BACKOFF)) - 1;
-
- return self::BACKOFF[$index];
- }
-
- /** Permanent client errors cannot be fixed by retrying. */
- public static function retryable($result)
- {
- $data = is_wp_error($result) ? $result->get_error_data() : null;
- $status = is_array($data) && isset($data['http_status']) ? (int) $data['http_status'] : 0;
- return $status < 400 || $status >= 500 || in_array($status, [408, 429], true);
- }
-
- private static function retryDelay($attempts, $result)
- {
- $data = is_wp_error($result) ? $result->get_error_data() : null;
- $retry = is_array($data) && isset($data['retry_after']) ? (int) $data['retry_after'] : 0;
- return max(self::backoffSeconds($attempts), min(86400, max(0, $retry)));
- }
-
- /** @return array */
- public static function pending()
- {
- return self::load();
- }
-
- /** Throttled entry point for admin requests. */
- public static function maybeProcess()
- {
- if (! self::load() || get_transient(self::LOCK_TRANSIENT)) {
- return;
- }
-
- set_transient(self::LOCK_TRANSIENT, 1, 300);
- self::process();
- }
-
- /**
- * Attempt every due item once.
- *
- * @param int|null $now Unix time, injectable for tests.
- * @return int Number of items delivered.
- */
- public static function process($now = null)
- {
- $now = null === $now ? time() : (int) $now;
- $queue = self::load();
- $kept = [];
- $delivered = 0;
-
- foreach ($queue as $item) {
- if (($now - (int) $item['created']) >= self::MAX_AGE || (int) $item['attempts'] >= self::MAX_ATTEMPTS) {
- continue;
- }
-
- if (! isset(self::$senders[$item['experience']]) || (int) $item['next'] > $now) {
- $kept[] = $item;
- continue;
- }
-
- $result = call_user_func(self::$senders[$item['experience']], EmailSubscriber::contractPayload($item['payload']));
-
- if (true === $result) {
- $delivered++;
- continue;
- }
-
- if (! self::retryable($result)) {
- continue;
- }
-
- $item['attempts'] = (int) $item['attempts'] + 1;
-
- if ($item['attempts'] >= self::MAX_ATTEMPTS) {
- continue;
- }
-
- $item['payload'] = EmailSubscriber::contractPayload($item['payload']);
- $item['next'] = $now + self::retryDelay($item['attempts'], $result);
- $kept[] = $item;
- }
-
- self::save($kept);
- self::schedule($kept);
-
- return $delivered;
- }
-
- /** @return array Queued payloads that belong to the address. */
- public static function itemsForEmail($email)
- {
- return array_values(array_filter(self::load(), function ($item) use ($email) {
- return isset($item['payload']['email']) && 0 === strcasecmp((string) $item['payload']['email'], (string) $email);
- }));
- }
-
- /** @return bool Whether anything was removed. */
- public static function eraseByEmail($email)
- {
- $queue = self::load();
- $kept = array_values(array_filter($queue, function ($item) use ($email) {
- return ! (isset($item['payload']['email']) && 0 === strcasecmp((string) $item['payload']['email'], (string) $email));
- }));
-
- if (count($kept) === count($queue)) {
- return false;
- }
-
- self::save($kept);
-
- return true;
- }
-
- private static function schedule(array $queue)
- {
- if (! $queue) {
- return;
- }
-
- $next = min(array_map(function ($item) {
- return (int) $item['next'];
- }, $queue));
-
- if (! wp_next_scheduled(self::CRON_HOOK)) {
- wp_schedule_single_event(max($next, time() + 60), self::CRON_HOOK);
- }
- }
-
- private static function load()
- {
- $queue = get_option(self::OPTION, []);
-
- return is_array($queue) ? $queue : [];
- }
-
- private static function save(array $queue)
- {
- if (! $queue) {
- delete_option(self::OPTION);
- return;
- }
-
- update_option(self::OPTION, array_values($queue), false);
- }
-}
diff --git a/lib/vendor/publishpress/publishpress-welcome/src/EmailSubscriber.php b/lib/vendor/publishpress/publishpress-welcome/src/EmailSubscriber.php
deleted file mode 100644
index 78a73948c..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/src/EmailSubscriber.php
+++ /dev/null
@@ -1,106 +0,0 @@
-settings = $settings;
- }
-
- /**
- * @param array $payload
- * @return true|\WP_Error
- */
- public function send(array $payload)
- {
- $settings = ExperienceConfig::mergeEmailCapture($this->settings);
- if (! empty($settings['configuration_error']) || empty($settings['enabled'])) {
- return new \WP_Error('publishpress_welcome_subscribe_failed', 'Email capture configuration is invalid or disabled.', ['http_status' => 400]);
- }
- if (! isset($payload['email']) || ! is_string($payload['email']) || true !== (isset($payload['consent']) ? $payload['consent'] : null)
- || ! isset($payload['consent_statement']) || $settings['consent_statement'] !== $payload['consent_statement']) {
- return new \WP_Error('publishpress_welcome_subscribe_failed', 'Subscription payload is missing valid email, consent or statement ID.', ['http_status' => 400]);
- }
- // Keep legacy client text/time and unknown filter values out of requests and retry storage.
- $payload = self::contractPayload($payload);
-
- if (! empty($this->settings['handler']) && is_callable($this->settings['handler'])) {
- $result = call_user_func($this->settings['handler'], $payload);
-
- return $this->normalize($result);
- }
-
- $endpoint = isset($this->settings['endpoint']) ? (string) $this->settings['endpoint'] : '';
-
- if ('' === $endpoint || self::PLACEHOLDER_ENDPOINT === $endpoint) {
- if (defined('WP_DEBUG') && WP_DEBUG) {
- // The address is deliberately not logged.
- error_log('[publishpress-welcome] Dry run: no subscription endpoint is configured, nothing was sent.');
- }
-
- return true;
- }
-
- $response = wp_remote_post(
- $endpoint,
- [
- 'timeout' => 5,
- 'redirection' => 0,
- 'limit_response_size' => 4096,
- 'headers' => ['Content-Type' => 'application/json', 'User-Agent' => 'PublishPress-Welcome/1.0'],
- 'body' => wp_json_encode($payload),
- ]
- );
-
- if (is_wp_error($response)) {
- return $response;
- }
-
- $code = (int) wp_remote_retrieve_response_code($response);
-
- if ($code < 200 || $code >= 300) {
- $retryAfter = wp_remote_retrieve_header($response, 'retry-after');
- $delay = 0;
- if (is_string($retryAfter) && strlen($retryAfter) <= 128 && in_array($code, [429, 503], true)) {
- $delay = ctype_digit($retryAfter) ? (float) $retryAfter : (float) strtotime($retryAfter) - time();
- $delay = (int) max(0, min(86400, $delay));
- }
- return new \WP_Error('publishpress_welcome_subscribe_failed', sprintf('Subscription endpoint returned HTTP %d.', $code), ['http_status' => $code, 'retry_after' => $delay]);
- }
-
- return true;
- }
-
- public static function contractPayload(array $payload)
- {
- return array_intersect_key($payload, array_flip(['email', 'consent', 'consent_statement', 'plugin', 'persona', 'locale', 'plan_claim']));
- }
-
- private function normalize($result)
- {
- if (true === $result) {
- return true;
- }
-
- if (is_wp_error($result)) {
- return $result;
- }
-
- return new \WP_Error('publishpress_welcome_subscribe_failed', 'The subscription handler reported a failure.');
- }
-}
diff --git a/lib/vendor/publishpress/publishpress-welcome/src/ExperienceConfig.php b/lib/vendor/publishpress/publishpress-welcome/src/ExperienceConfig.php
deleted file mode 100644
index 14f1d1a93..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/src/ExperienceConfig.php
+++ /dev/null
@@ -1,242 +0,0 @@
- $values['page_title'],
- 'show_in_menu' => false,
- 'mode' => self::MODE_SCREEN,
- 'allow_mode_override' => false,
- 'brand_name' => '',
- 'logo' => [],
- 'accent_color' => self::DEFAULT_ACCENT,
- 'styles' => [],
- 'enrollment_option' => $id . '_welcome_enrolled',
- 'redirect_option' => $id . '_welcome_redirect_pending',
- 'user_option_prefix' => $id . '_welcome',
- 'notice_enabled' => true,
- 'notice_icon_url' => '',
- // Screens (?page= slugs) the notice may appear on. Empty means the checklist pages, i.e. the plugin's own screens.
- 'notice_pages' => [],
- // Also allow the main Dashboard. Off by default: WordPress.org asks that promotional notices stay limited in scope.
- 'notice_on_dashboard' => false,
- // The notice stops appearing this many days after it was first shown. 0 keeps it until dismissed.
- 'notice_days' => 14,
- // Release highlights shown once per version: ['version', 'title', 'items' => [['title', 'body']], 'url', 'url_label'].
- 'whats_new' => false,
- 'email_capture' => [],
- 'notice_title' => __('Welcome! Would you like a quick tour?', 'publishpress-welcome'),
- 'notice_body' => __('We will show you where the important settings are. Nothing is changed while you read.', 'publishpress-welcome'),
- 'checklist_enabled' => true,
- 'checklist_pages' => [],
- 'checklist_title' => __('Finish setting up the plugin', 'publishpress-welcome'),
- 'checklist_complete_title' => __('Setup is complete', 'publishpress-welcome'),
- 'exit_url' => admin_url(),
- 'labels' => [],
- ];
-
- $values['id'] = $id;
- $values['page_slug'] = sanitize_key($values['page_slug']);
- $values['labels'] = array_merge($this->defaultLabels(), isset($values['labels']) ? $values['labels'] : []);
- $values['checklist_pages'] = array_map('sanitize_key', isset($values['checklist_pages']) ? (array) $values['checklist_pages'] : []);
- $values['notice_pages'] = array_map('sanitize_key', isset($values['notice_pages']) ? (array) $values['notice_pages'] : []);
- if (! $values['notice_pages']) {
- $values['notice_pages'] = $values['checklist_pages'];
- }
- $values['styles'] = isset($values['styles']) ? (array) $values['styles'] : [];
- $values['mode'] = self::normalizeMode(isset($values['mode']) ? $values['mode'] : self::MODE_SCREEN);
- $values['logo'] = isset($values['logo']) ? (array) $values['logo'] : [];
- $values['email_capture'] = self::mergeEmailCapture(isset($values['email_capture']) ? $values['email_capture'] : []);
-
- $this->values = array_merge($defaults, $values);
- }
-
- /** @return mixed */
- public function get($key)
- {
- return array_key_exists($key, $this->values) ? $this->values[$key] : null;
- }
-
- /** @return string */
- public function label($key)
- {
- $labels = $this->values['labels'];
-
- return isset($labels[$key]) ? (string) $labels[$key] : '';
- }
-
- /**
- * Accepts only the two supported display modes and falls back to the in-screen layout.
- *
- * @return string
- */
- public static function normalizeMode($mode)
- {
- return self::MODE_FULLSCREEN === $mode ? self::MODE_FULLSCREEN : self::MODE_SCREEN;
- }
-
- /**
- * CSS custom properties derived from the accent colour. Invalid colours fall back to the default.
- *
- * @return string
- */
- public function styleVariables()
- {
- $hex = self::normalizeHex($this->values['accent_color']);
- list($r, $g, $b) = self::hexToRgb($hex);
-
- $dark = sprintf('#%02x%02x%02x', (int) ($r * .78), (int) ($g * .78), (int) ($b * .78));
-
- return sprintf('--ppw-accent:%1$s;--ppw-accent-rgb:%2$d,%3$d,%4$d;--ppw-accent-dark:%5$s;', $hex, $r, $g, $b, $dark);
- }
-
- /**
- * Defaults for the optional email step. Pass `false` to disable it, or an array of overrides.
- * A consumer can also adjust the final array through the "publishpress_welcome_{id}_email_capture" filter.
- *
- * @param array|bool $overrides
- * @return array
- */
- public static function mergeEmailCapture($overrides)
- {
- if (false === $overrides) {
- $overrides = ['enabled' => false];
- }
-
- $defaults = [
- 'enabled' => true,
- // "before_last", "after_first", or a 1-based step position.
- 'position' => 'before_last',
- // Days before asking again after "No thanks". 0 means never ask again (in any bundled plugin).
- 'decline_snooze_days' => 0,
- // Callable receiving the payload array; returns true, false or WP_Error. Replaces the HTTP request.
- 'handler' => null,
- 'endpoint' => EmailSubscriber::DEFAULT_ENDPOINT,
- 'consent_statement' => 'welcome-email-v1',
- // Render the consent checkbox ticked. Off by default; the user still has to click Subscribe to send anything.
- 'consent_prechecked' => false,
- 'recipient_name' => 'PublishPress',
- 'privacy_url' => '',
- 'personas' => [
- 'site-owner' => __('Site owner', 'publishpress-welcome'),
- 'content-team' => __('Writer or editor', 'publishpress-welcome'),
- 'agency' => __('Agency or freelancer', 'publishpress-welcome'),
- 'developer' => __('Developer', 'publishpress-welcome'),
- 'other' => __('Something else', 'publishpress-welcome'),
- ],
- 'copy' => [
- 'title' => __('Stay in the loop', 'publishpress-welcome'),
- 'lede' => __('Tell us a little about how you use WordPress and we will send tips and offers that fit. This is optional, and everything works if you skip it.', 'publishpress-welcome'),
- 'persona_label' => __('What best describes you?', 'publishpress-welcome'),
- 'email_label' => __('Email address', 'publishpress-welcome'),
- 'consent_label' => __('Yes, send me tips, product news and offers tailored to how I use this plugin. I can unsubscribe at any time.', 'publishpress-welcome'),
- 'privacy_label' => __('Privacy policy', 'publishpress-welcome'),
- 'disclosure' => __('Your email address and your answer are sent to %s when you subscribe. Nothing is sent if you skip.', 'publishpress-welcome'),
- 'submit' => __('Subscribe', 'publishpress-welcome'),
- 'decline' => __('No thanks', 'publishpress-welcome'),
- 'continue' => __('Continue', 'publishpress-welcome'),
- 'success_title' => __('Check your inbox', 'publishpress-welcome'),
- 'success_body' => __('Please confirm your email using the link from PublishPress before receiving tips and offers.', 'publishpress-welcome'),
- 'confirmation_note' => __('Check your inbox for a confirmation link after subscribing. Confirm your email before receiving tips and offers.', 'publishpress-welcome'),
- 'error_delivery' => __('We could not accept this subscription. Please try again later or choose "No thanks".', 'publishpress-welcome'),
- 'error_email' => __('Please enter a valid email address.', 'publishpress-welcome'),
- 'error_consent' => __('Please tick the box to confirm you want to hear from us, or choose "No thanks".', 'publishpress-welcome'),
- ],
- ];
-
- $overrides = is_array($overrides) ? $overrides : [];
- $merged = array_merge($defaults, $overrides);
- $merged['copy'] = array_merge($defaults['copy'], isset($overrides['copy']) ? (array) $overrides['copy'] : []);
- $merged['personas'] = isset($overrides['personas']) ? (array) $overrides['personas'] : $defaults['personas'];
-
- $statement = $merged['consent_statement'];
- $customWording = $merged['copy']['consent_label'] !== $defaults['copy']['consent_label'];
- if (! is_string($statement) || ! preg_match('/^[a-z0-9-]{1,32}$/D', $statement)
- || ($customWording && 'welcome-email-v1' === $statement)) {
- $merged['enabled'] = false;
- $merged['configuration_error'] = 'invalid_consent_statement';
- }
-
- return $merged;
- }
-
- /** @return string */
- private static function normalizeHex($color)
- {
- $color = is_string($color) ? strtolower(trim($color)) : '';
-
- if (! preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/', $color)) {
- return self::DEFAULT_ACCENT;
- }
-
- if (4 === strlen($color)) {
- $color = '#' . $color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3];
- }
-
- return $color;
- }
-
- /** @return int[] */
- private static function hexToRgb($hex)
- {
- return [hexdec(substr($hex, 1, 2)), hexdec(substr($hex, 3, 2)), hexdec(substr($hex, 5, 2))];
- }
-
- /** @return array */
- public function all()
- {
- return $this->values;
- }
-
- private function defaultLabels()
- {
- return [
- 'back' => __('Back', 'publishpress-welcome'),
- 'next' => __('Next', 'publishpress-welcome'),
- 'skip' => __('Skip', 'publishpress-welcome'),
- 'exit' => __('Exit guide', 'publishpress-welcome'),
- 'step_nav' => __('Guide steps', 'publishpress-welcome'),
- 'guide_nav' => __('Guide navigation', 'publishpress-welcome'),
- 'skip_content' => __('Skip to the guide content', 'publishpress-welcome'),
- 'new_tab' => __('(opens in a new tab)', 'publishpress-welcome'),
- 'task_done' => __('Completed:', 'publishpress-welcome'),
- 'task_todo' => __('Not completed:', 'publishpress-welcome'),
- 'changelog' => __('See the full changelog', 'publishpress-welcome'),
- 'finish' => __('Finish', 'publishpress-welcome'),
- 'start' => __('Start the tour', 'publishpress-welcome'),
- 'continue' => __('Continue the tour', 'publishpress-welcome'),
- 'dismiss' => __('Dismiss', 'publishpress-welcome'),
- 'hide' => __('Hide', 'publishpress-welcome'),
- 'do_it' => __('Do it', 'publishpress-welcome'),
- 'view' => __('View', 'publishpress-welcome'),
- 'replay' => __('Replay the guide', 'publishpress-welcome'),
- 'step_format' => __('Step %1$s of %2$s', 'publishpress-welcome'),
- 'reassurance' => __('This guide does not change any setting on your site.', 'publishpress-welcome'),
- ];
- }
-}
diff --git a/lib/vendor/publishpress/publishpress-welcome/src/NoticePolicy.php b/lib/vendor/publishpress/publishpress-welcome/src/NoticePolicy.php
deleted file mode 100644
index 0e338b18e..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/src/NoticePolicy.php
+++ /dev/null
@@ -1,46 +0,0 @@
-= (int) $days * 86400;
- }
-
- /**
- * Whether the current admin screen is one the notice may appear on.
- *
- * @param string $currentPage The ?page= slug, or an empty string.
- * @param string[] $pages Allowed page slugs.
- * @param bool $onDashboard Whether the main Dashboard is also allowed.
- * @param bool $isDashboard Whether the current screen is the Dashboard.
- * @return bool
- */
- public static function allowsScreen($currentPage, array $pages, $onDashboard, $isDashboard)
- {
- if ($isDashboard) {
- return (bool) $onDashboard;
- }
-
- return '' !== $currentPage && in_array($currentPage, $pages, true);
- }
-}
diff --git a/lib/vendor/publishpress/publishpress-welcome/src/Privacy.php b/lib/vendor/publishpress/publishpress-welcome/src/Privacy.php
deleted file mode 100644
index 0e6cb4690..000000000
--- a/lib/vendor/publishpress/publishpress-welcome/src/Privacy.php
+++ /dev/null
@@ -1,179 +0,0 @@
- Experience ID => user option prefix. */
- private static $experiences = [];
-
- /** @var bool */
- private static $hooked = false;
-
- /**
- * Register an experience so its stored data is included in exports and erasures.
- */
- public static function track($id, $userOptionPrefix)
- {
- self::$experiences[(string) $id] = (string) $userOptionPrefix;
-
- if (self::$hooked) {
- return;
- }
-
- self::$hooked = true;
-
- add_filter('wp_privacy_personal_data_exporters', [__CLASS__, 'registerExporter']);
- add_filter('wp_privacy_personal_data_erasers', [__CLASS__, 'registerEraser']);
- add_action('admin_init', [__CLASS__, 'addPolicyContent']);
- }
-
- public static function registerExporter(array $exporters)
- {
- $exporters['publishpress-welcome'] = [
- 'exporter_friendly_name' => __('PublishPress welcome guide', 'publishpress-welcome'),
- 'callback' => [__CLASS__, 'exporter'],
- ];
-
- return $exporters;
- }
-
- public static function registerEraser(array $erasers)
- {
- $erasers['publishpress-welcome'] = [
- 'eraser_friendly_name' => __('PublishPress welcome guide', 'publishpress-welcome'),
- 'callback' => [__CLASS__, 'eraser'],
- ];
-
- return $erasers;
- }
-
- public static function exporter($emailAddress)
- {
- $user = get_user_by('email', $emailAddress);
- $items = $user ? self::exportItems((int) $user->ID) : [];
-
- foreach (EmailQueue::itemsForEmail($emailAddress) as $index => $queued) {
- $items[] = [
- 'group_id' => 'publishpress-welcome',
- 'group_label' => __('PublishPress welcome guide', 'publishpress-welcome'),
- 'item_id' => 'publishpress-welcome-pending-' . $index,
- 'data' => [
- ['name' => __('Pending subscription (delivery will be retried)', 'publishpress-welcome'), 'value' => (string) $queued['payload']['email']],
- ],
- ];
- }
-
- return ['data' => $items, 'done' => true];
- }
-
- public static function eraser($emailAddress)
- {
- $user = get_user_by('email', $emailAddress);
- $removed = $user ? self::erase((int) $user->ID) : false;
- $removed = EmailQueue::eraseByEmail($emailAddress) || $removed;
-
- return ['items_removed' => $removed, 'items_retained' => false, 'messages' => [], 'done' => true];
- }
-
- /**
- * @param int $userId
- * @return array Export groups in the format WordPress expects.
- */
- public static function exportItems($userId)
- {
- $items = [];
- $shared = [];
-
- $status = SharedProfile::status($userId);
- if ('' !== $status) {
- $shared[] = ['name' => __('Email subscription choice', 'publishpress-welcome'), 'value' => $status];
- }
-
- $persona = SharedProfile::persona($userId);
- if ('' !== $persona) {
- $shared[] = ['name' => __('Answer to "what best describes you?"', 'publishpress-welcome'), 'value' => $persona];
- }
-
- if ($shared) {
- $items[] = [
- 'group_id' => 'publishpress-welcome',
- 'group_label' => __('PublishPress welcome guide', 'publishpress-welcome'),
- 'item_id' => 'publishpress-welcome-profile-' . $userId,
- 'data' => $shared,
- ];
- }
-
- foreach (self::$experiences as $id => $prefix) {
- $data = [];
-
- foreach (self::EXPERIENCE_SUFFIXES as $suffix) {
- $value = get_user_option($prefix . '_' . $suffix, $userId);
-
- if (false !== $value && '' !== $value && null !== $value) {
- $data[] = ['name' => $id . ' ' . $suffix, 'value' => (string) $value];
- }
- }
-
- if ($data) {
- $items[] = [
- 'group_id' => 'publishpress-welcome',
- 'group_label' => __('PublishPress welcome guide', 'publishpress-welcome'),
- 'item_id' => 'publishpress-welcome-' . $id . '-' . $userId,
- 'data' => $data,
- ];
- }
- }
-
- return $items;
- }
-
- /**
- * Remove everything this library stores for the user.
- *
- * @param int $userId
- * @return bool Whether anything was removed.
- */
- public static function erase($userId)
- {
- $removed = false;
-
- foreach ([SharedProfile::STATUS_OPTION, SharedProfile::PERSONA_OPTION, SharedProfile::DECLINED_AT_OPTION] as $option) {
- if (false !== get_user_option($option, $userId)) {
- delete_user_option($userId, $option);
- $removed = true;
- }
- }
-
- foreach (self::$experiences as $prefix) {
- foreach (self::EXPERIENCE_SUFFIXES as $suffix) {
- $option = $prefix . '_' . $suffix;
-
- if (false !== get_user_option($option, $userId)) {
- delete_user_option($userId, $option);
- $removed = true;
- }
- }
- }
-
- return $removed;
- }
-
- public static function addPolicyContent()
- {
- if (! function_exists('wp_add_privacy_policy_content')) {
- return;
- }
-
- $content = '
' . esc_html__('Suggested text for the optional PublishPress welcome guide:', 'publishpress-welcome') . '
'
- . '
' . esc_html__('If you opt in on the welcome guide, we receive your email address, your answer to "what best describes you?" and an identifier for the consent statement shown to you. PublishPress records the receive time and asks you to confirm your email before sending tips and offers. Nothing is sent unless you choose Subscribe with the consent box ticked. The guide also stores your progress and your choice on this site; both are included in the personal data export and removed by the erasure tool, and you can unsubscribe at any time.', 'publishpress-welcome') . '