From a5625e69823d4ef663f9c224aeb2fabe6f56b10b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 04:31:36 +0000 Subject: [PATCH 1/6] chore(deps-dev): bump yoast/phpunit-polyfills from 2.0.0 to 3.1.1 Bumps [yoast/phpunit-polyfills](https://github.com/Yoast/PHPUnit-Polyfills) from 2.0.0 to 3.1.1. - [Release notes](https://github.com/Yoast/PHPUnit-Polyfills/releases) - [Changelog](https://github.com/Yoast/PHPUnit-Polyfills/blob/3.x/CHANGELOG.md) - [Commits](https://github.com/Yoast/PHPUnit-Polyfills/compare/2.0.0...3.1.1) --- updated-dependencies: - dependency-name: yoast/phpunit-polyfills dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- composer.lock | 79 ++++++++++++++++++++++++++------------------------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/composer.json b/composer.json index 5c9a014..84427f5 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "wp-coding-standards/wpcs": "^2.3", "squizlabs/php_codesniffer": "^3.7", "phpcompatibility/php-compatibility": "^9.3", - "yoast/phpunit-polyfills": "^2.0" + "yoast/phpunit-polyfills": "^3.1" }, "scripts": { "format-fix-exit": "\"vendor/bin/phpcbf-fix-exit-0\" --standard=phpcs.xml --report-summary --report-source -s --runtime-set testVersion 7.0- ", diff --git a/composer.lock b/composer.lock index 7252a0d..1bb3659 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3f1402152d7e4f9ecbc29193c854494a", + "content-hash": "1d2999c6ac7549d52b352485bfd0c866", "packages": [ { "name": "codeinwp/themeisle-sdk", @@ -1270,16 +1270,16 @@ }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", "shasum": "" }, "require": { @@ -1313,7 +1313,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" }, "funding": [ { @@ -1321,7 +1321,7 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2024-03-01T13:45:45+00:00" }, { "name": "sebastian/comparator", @@ -1635,16 +1635,16 @@ }, { "name": "sebastian/object-enumerator", - "version": "3.0.4", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + "reference": "ac5b293dba925751b808e02923399fb44ff0d541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541", "shasum": "" }, "require": { @@ -1680,7 +1680,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" }, "funding": [ { @@ -1688,20 +1688,20 @@ "type": "github" } ], - "time": "2020-11-30T07:40:27+00:00" + "time": "2024-03-01T13:54:02+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", "shasum": "" }, "require": { @@ -1735,7 +1735,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" }, "funding": [ { @@ -1743,20 +1743,20 @@ "type": "github" } ], - "time": "2020-11-30T07:37:18+00:00" + "time": "2024-03-01T13:56:04+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/9bfd3c6f1f08c026f542032dfb42813544f7d64c", + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c", "shasum": "" }, "require": { @@ -1798,7 +1798,7 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.2" }, "funding": [ { @@ -1806,7 +1806,7 @@ "type": "github" } ], - "time": "2020-11-30T07:34:24+00:00" + "time": "2024-03-01T14:07:30+00:00" }, { "name": "sebastian/resource-operations", @@ -2061,12 +2061,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + }, "branch-alias": { "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -2342,29 +2342,31 @@ }, { "name": "yoast/phpunit-polyfills", - "version": "2.0.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "c758753e8f9dac251fed396a73c8305af3f17922" + "reference": "e6381c62c4df51677b657fbac79b79dfce7acdab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/c758753e8f9dac251fed396a73c8305af3f17922", - "reference": "c758753e8f9dac251fed396a73c8305af3f17922", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/e6381c62c4df51677b657fbac79b79dfce7acdab", + "reference": "e6381c62c4df51677b657fbac79b79dfce7acdab", "shasum": "" }, "require": { - "php": ">=5.6", - "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" + "php": ">=7.0", + "phpunit/phpunit": "^6.4.4 || ^7.0 || ^8.0 || ^9.0 || ^11.0" }, "require-dev": { - "yoast/yoastcs": "^2.3.0" + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -2396,9 +2398,10 @@ ], "support": { "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2023-06-06T20:28:24+00:00" + "time": "2025-01-12T08:41:37+00:00" } ], "aliases": [], From aec89b3814702d784be9b95f9d5e7a27679d3aa3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 04:04:49 +0000 Subject: [PATCH 2/6] chore(deps-dev): bump squizlabs/php_codesniffer from 3.10.3 to 3.13.4 Bumps [squizlabs/php_codesniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) from 3.10.3 to 3.13.4. - [Release notes](https://github.com/PHPCSStandards/PHP_CodeSniffer/releases) - [Changelog](https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/CHANGELOG.md) - [Commits](https://github.com/PHPCSStandards/PHP_CodeSniffer/compare/3.10.3...3.13.4) --- updated-dependencies: - dependency-name: squizlabs/php_codesniffer dependency-version: 3.13.4 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- composer.lock | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 2ebcc16..1ecf290 100644 --- a/composer.lock +++ b/composer.lock @@ -1961,16 +1961,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.3", + "version": "3.13.4", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" + "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ad545ea9c1b7d270ce0fc9cbfb884161cd706119", + "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119", "shasum": "" }, "require": { @@ -2035,9 +2035,13 @@ { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" } ], - "time": "2024-09-18T10:38:58+00:00" + "time": "2025-09-05T05:47:09+00:00" }, { "name": "symfony/polyfill-ctype", From 7330dedd7325f5b2baa32fa0251dd870c5ba452c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 05:54:52 +0000 Subject: [PATCH 3/6] chore(deps): bump codeinwp/themeisle-sdk from 3.3.51 to 3.3.52 Bumps [codeinwp/themeisle-sdk](https://github.com/Codeinwp/themeisle-sdk) from 3.3.51 to 3.3.52. - [Release notes](https://github.com/Codeinwp/themeisle-sdk/releases) - [Changelog](https://github.com/Codeinwp/themeisle-sdk/blob/v3.3.52/CHANGELOG.md) - [Commits](https://github.com/Codeinwp/themeisle-sdk/compare/v3.3.51...v3.3.52) --- updated-dependencies: - dependency-name: codeinwp/themeisle-sdk dependency-version: 3.3.52 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index b1947c7..28ad44d 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.51", + "version": "3.3.52", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d" + "reference": "d1ae68cbd4f84934b4d982e9eeff317b9f4c814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/bb2a8414b0418b18c68c9ff1df3d7fb10467928d", - "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/d1ae68cbd4f84934b4d982e9eeff317b9f4c814a", + "reference": "d1ae68cbd4f84934b4d982e9eeff317b9f4c814a", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.51" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.52" }, - "time": "2026-03-30T07:58:49+00:00" + "time": "2026-05-14T19:43:56+00:00" } ], "packages-dev": [ From 5a0885ae934b035e65c99f7e62afb9bd4c212b92 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 20 May 2026 16:38:02 +0530 Subject: [PATCH 4/6] fix: remove title-tag support --- inc/Core.php | 1 - 1 file changed, 1 deletion(-) diff --git a/inc/Core.php b/inc/Core.php index 4d63594..dc644b7 100644 --- a/inc/Core.php +++ b/inc/Core.php @@ -71,7 +71,6 @@ public function setup() { add_theme_support( 'starter-content', $starter_content->get() ); add_theme_support( 'wp-block-styles' ); add_theme_support( 'automatic-feed-links' ); - add_theme_support( 'title-tag' ); add_theme_support( 'post-thumbnails' ); add_theme_support( 'editor-styles' ); add_theme_support( From 53bd7f8923f4eeba00e1cca1a133cc087ac686e8 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 20 May 2026 17:01:03 +0530 Subject: [PATCH 5/6] fix: add title-tag support for WP < 5.9 --- inc/Core.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/inc/Core.php b/inc/Core.php index dc644b7..fdc45a6 100644 --- a/inc/Core.php +++ b/inc/Core.php @@ -155,6 +155,13 @@ public function setup() { ) ); + // WP < 5.9, wp_head() does NOT auto-output , so we MUST declare title-tag support. + // WP >= 5.9, wp-includes/template-canvas.php calls wp_head() which already handles <title> natively. + // Adding title-tag support on top causes a second <title> injection when Yoast (or any SEO plugin using wp_title filters) is active. + if ( version_compare( $GLOBALS['wp_version'], '5.9', '<' ) ) { + add_theme_support( 'title-tag' ); + } + remove_theme_support( 'core-block-patterns' ); register_nav_menus( array( 'primary' => esc_html__( 'Primary Menu', 'raft' ) ) ); From b9b4e4b37267ec546530b93dd22dd41849c0c035 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani <hardeepasrani@gmail.com> Date: Wed, 3 Jun 2026 05:32:45 +0530 Subject: [PATCH 6/6] chore: compatibility for Pro version * wip * wip * [wip] * [wip] * [wip] --- .github/workflows/test-php.yml | 4 +- assets/js/wizard-promo.js | 320 ++++++++++++++++++++++++++++++ inc/Admin.php | 2 +- inc/Core.php | 36 +++- inc/Dashboard.php | 162 +++++++++++++++ inc/Pro_Promotions.php | 164 +++++++++++++++ inc/Wizard_Promo.php | 102 ++++++++++ inc/patterns/cover-background.php | 46 ++--- 8 files changed, 807 insertions(+), 29 deletions(-) create mode 100644 assets/js/wizard-promo.js create mode 100644 inc/Dashboard.php create mode 100644 inc/Pro_Promotions.php create mode 100644 inc/Wizard_Promo.php diff --git a/.github/workflows/test-php.yml b/.github/workflows/test-php.yml index a95f7d2..913a00f 100644 --- a/.github/workflows/test-php.yml +++ b/.github/workflows/test-php.yml @@ -13,7 +13,7 @@ jobs: - name: Setup PHP version uses: shivammathur/setup-php@v2 with: - php-version: "7.2" + php-version: "7.4" extensions: simplexml - name: Checkout source code uses: actions/checkout@v4 @@ -36,7 +36,7 @@ jobs: - name: Setup PHP version uses: shivammathur/setup-php@v2 with: - php-version: "7.2" + php-version: "7.4" extensions: simplexml, mysql tools: phpunit-polyfills - name: Checkout source code diff --git a/assets/js/wizard-promo.js b/assets/js/wizard-promo.js new file mode 100644 index 0000000..b061ead --- /dev/null +++ b/assets/js/wizard-promo.js @@ -0,0 +1,320 @@ +/** + * Raft Pro upsell injection for the Otter onboarding wizard. + * + * Two surfaces: + * - Sidebar nudge: appended to `.o-sidebar__content` on every step. + * - Finish card: appended to `.o-finish__container` (before its actions) + * when the wizard reaches its done state. + * + * We don't import from Otter — we treat its rendered DOM + Redux store as + * the only interfaces, so changes to Otter's internals can't break us + * unless they rename the BEM classes we target. The script is loaded only + * when `?onboarding=true` is present AND Raft Pro is inactive, so paying + * users never see either nudge. + * + * Re-injection is necessary because React owns the wizard DOM and can + * remount sections on state changes — using marker IDs to avoid duplicates. + * + * @package + */ +( function () { + 'use strict'; + + if ( + 'undefined' === typeof window.wp || + ! window.wp.data || + ! window.raftWizardPromo + ) { + return; + } + + const data = window.wp.data; + const domReady = window.wp.domReady; + const config = window.raftWizardPromo; + const strings = config.strings || {}; + const upgradeUrl = config.upgradeUrl; + + const SIDEBAR_ID = 'raft-pro-wizard-sidebar-nudge'; + const FINISH_ID = 'raft-pro-wizard-finish-card'; + + /** + * Inject inline CSS for the two surfaces. Scoped via the marker IDs so + * we don't leak styles into Otter's own components. + */ + function injectStyles() { + if ( document.getElementById( 'raft-pro-wizard-promo-style' ) ) { + return; + } + const style = document.createElement( 'style' ); + style.id = 'raft-pro-wizard-promo-style'; + style.textContent = + '' + + '#' + + SIDEBAR_ID + + ' { margin-top: 24px; padding: 16px; border: 1px dashed #C26148; border-radius: 8px; background: rgba(194, 97, 72, 0.06); }' + + '#' + + SIDEBAR_ID + + ' .raft-eyebrow { font-size: 11px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; color: #C26148; margin: 0 0 4px; }' + + '#' + + SIDEBAR_ID + + ' .raft-title { font-size: 15px; font-weight: 600; margin: 0 0 8px; color: #1e1e1e; }' + + '#' + + SIDEBAR_ID + + ' a { color: #C26148; font-weight: 600; text-decoration: none; font-size: 13px; }' + + '#' + + SIDEBAR_ID + + ' a:hover { text-decoration: underline; }' + + '#' + + FINISH_ID + + ' { margin: 24px 0; padding: 28px; border: 2px dashed #C26148; border-radius: 12px; background: rgba(194, 97, 72, 0.05); text-align: left; }' + + '#' + + FINISH_ID + + ' .raft-eyebrow { font-size: 12px; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; color: #C26148; margin: 0 0 8px; }' + + '#' + + FINISH_ID + + ' h3 { font-size: 22px; font-weight: 600; margin: 0 0 12px; color: #1e1e1e; }' + + '#' + + FINISH_ID + + ' p { font-size: 15px; line-height: 1.5; margin: 0 0 20px; color: #50575e; }' + + '#' + + FINISH_ID + + ' a.raft-cta { display: inline-block; background: #C26148; color: #fff; padding: 10px 22px; border-radius: 999px; text-decoration: none; font-weight: 600; }' + + '#' + + FINISH_ID + + ' a.raft-cta:hover { background: #AC5039; color: #fff; }'; + document.head.appendChild( style ); + } + + /** + * Build a DOM node with the given tag, attributes, and child content. + * Tiny helper to avoid the verbosity of native createElement chains. + * + * @param {string} tag + * @param {Object} attrs + * @param {string|Node|Array} children + * @return {HTMLElement} HTML element + */ + function el( tag, attrs, children ) { + const node = document.createElement( tag ); + if ( attrs ) { + Object.keys( attrs ).forEach( function ( key ) { + if ( 'className' === key ) { + node.className = attrs[ key ]; + } else { + node.setAttribute( key, attrs[ key ] ); + } + } ); + } + if ( children ) { + if ( ! Array.isArray( children ) ) { + children = [ children ]; + } + children.forEach( function ( child ) { + if ( 'string' === typeof child ) { + node.appendChild( document.createTextNode( child ) ); + } else if ( child ) { + node.appendChild( child ); + } + } ); + } + return node; + } + + /** + * Build the sidebar nudge element. Separated from `injectSidebarNudge` + * so we can build-then-position without rebuilding on every re-render. + * + * @return {HTMLElement} HTML structure for the sidebar nudge + */ + function buildSidebarNudge() { + return el( 'div', { id: SIDEBAR_ID }, [ + el( + 'p', + { className: 'raft-eyebrow' }, + strings.sidebarEyebrow || 'Want more?' + ), + el( + 'p', + { className: 'raft-title' }, + strings.sidebarTitle || 'Try Raft Pro' + ), + el( + 'a', + { + href: upgradeUrl, + target: '_blank', + rel: 'noopener noreferrer', + }, + ( strings.sidebarLink || "See what's included" ) + ' →' + ), + ] ); + } + + /** + * Make sure the nudge exists AND is the last child of the sidebar + * content area. React owns this subtree and re-mounts step-specific + * controls between wizard steps — if our node ends up sandwiched + * between `.o-sidebar__info` and the freshly-mounted controls, the + * nudge appears in the middle of the sidebar instead of at the bottom. + * + * Idempotent: if the nudge is already last child, do nothing — so + * MutationObserver loops don't re-trigger themselves. + */ + function injectSidebarNudge() { + const container = document.querySelector( '.o-sidebar__content' ); + if ( ! container ) { + return; + } + + const existing = document.getElementById( SIDEBAR_ID ); + + if ( existing && existing === container.lastElementChild ) { + return; + } + + if ( existing && existing.parentNode ) { + existing.parentNode.removeChild( existing ); + } + + container.appendChild( buildSidebarNudge() ); + } + + /** + * Inject the larger Finish-step card before its action buttons. Returns + * early if the Finish container isn't in the DOM (we're not on Finish + * step yet) or if we already injected. + */ + function injectFinishCard() { + if ( document.getElementById( FINISH_ID ) ) { + return; + } + const container = document.querySelector( '.o-finish__container' ); + if ( ! container ) { + return; + } + + const card = el( 'div', { id: FINISH_ID }, [ + el( + 'p', + { className: 'raft-eyebrow' }, + strings.finishEyebrow || 'Get even more' + ), + el( + 'h3', + null, + strings.finishTitle || 'Take it further with Raft Pro' + ), + el( + 'p', + null, + strings.finishBody || + 'Unlock 17 extra patterns, 8 style variations, 7 ready-made page templates, and a fully designed WooCommerce storefront.' + ), + el( + 'a', + { + href: upgradeUrl, + target: '_blank', + rel: 'noopener noreferrer', + className: 'raft-cta', + }, + strings.finishCta || 'Upgrade to Pro' + ), + ] ); + + // Insert before the actions row so it sits naturally above the + // CTAs rather than after them (lower visual hierarchy). + const actions = container.querySelector( '.o-finish__actions' ); + if ( actions ) { + container.insertBefore( card, actions ); + } else { + container.appendChild( card ); + } + } + + let rafPending = false; + + function tick() { + injectStyles(); + injectSidebarNudge(); + injectFinishCard(); + } + + /** + * Debounce tick() to the next animation frame. wp.data.subscribe fires + * synchronously DURING React's render phase, before commit — running + * tick() at that point can land our nodes between info and a + * not-yet-mounted Controls child. Deferring to rAF runs us after the + * commit phase, when the DOM matches React's intent. + */ + function scheduleTick() { + if ( rafPending ) { + return; + } + rafPending = true; + ( + window.requestAnimationFrame || + function ( cb ) { + return setTimeout( cb, 16 ); + } + )( function () { + rafPending = false; + tick(); + } ); + } + + let observer = null; + + /** + * Watch the wizard root for child changes. Fires AFTER React's commit + * phase, so the DOM is settled when we re-inject. Idempotent: our + * inject functions short-circuit when the nudge is already in the + * right place, so the observer doesn't loop on its own mutations. + */ + function setupObserver() { + if ( observer ) { + return true; + } + const root = + document.getElementById( 'otter-onboarding' ) || document.body; + if ( ! root ) { + return false; + } + observer = new MutationObserver( scheduleTick ); + observer.observe( root, { childList: true, subtree: true } ); + return true; + } + + function start() { + // React commit happens after subscribe fires — defer to next frame. + try { + data.subscribe( scheduleTick ); + } catch ( e ) { + // Subscribe can throw before store registers; the poll below + // covers that window. + } + + setupObserver(); + + // Initial poll covers the brief window before Otter mounts the + // wizard root (the observer needs an existing node to attach to). + let attempts = 0; + const poll = setInterval( function () { + attempts++; + if ( setupObserver() ) { + scheduleTick(); + } + if ( attempts > 30 ) { + clearInterval( poll ); + } + }, 200 ); + } + + if ( domReady ) { + domReady( start ); + } else if ( 'loading' === document.readyState ) { + document.addEventListener( 'DOMContentLoaded', start ); + } else { + start(); + } +} )(); diff --git a/inc/Admin.php b/inc/Admin.php index cff945f..bf751cb 100644 --- a/inc/Admin.php +++ b/inc/Admin.php @@ -180,7 +180,7 @@ public function render_welcome_notice() { $notice_html .= '</h1>'; - $learn_more = '<a href="https://docs.themeisle.com/article/1975-fse-onboarding" target="_blank">' . __( 'Learn More', 'raft' ) . '</a>'; + $learn_more = '<a href="https://docs.themeisle.com/otter-page-builder-blocks-extensions/fse-onboarding" target="_blank">' . __( 'Learn More', 'raft' ) . '</a>'; $notice_html .= '<p class="description">' . __( 'Install our free builder plugin for more blocks, enhanced functionality, and seamless theme setup.', 'raft' ) . ' ' . $learn_more . '</p>'; diff --git a/inc/Core.php b/inc/Core.php index 4d63594..7aaff69 100644 --- a/inc/Core.php +++ b/inc/Core.php @@ -44,6 +44,9 @@ public function __construct() { new Admin(); new Block_Patterns(); new Block_Styles(); + new Dashboard(); + new Pro_Promotions(); + new Wizard_Promo(); } /** @@ -56,6 +59,37 @@ private function run_hooks() { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ) ); add_action( 'enqueue_block_editor_assets', array( $this, 'add_editor_styles' ) ); add_filter( 'raft_strings', array( $this, 'strings' ) ); + add_filter( 'home_template_hierarchy', array( $this, 'home_falls_back_to_archive' ) ); + } + + /** + * Let the blog/posts page render through `archive.html` when no `home.html` + * is present. WP's default hierarchy for the home/posts page is + * [home, index] — archive is not in the chain — so a single archive + * customization wouldn't otherwise show up on the page assigned as Posts + * page. Inserting `archive` ahead of `index` keeps `home.html` winning + * if a child theme ever provides one, while giving the archive template + * one source of truth for all post-listing contexts. + * + * @param array $hierarchy Candidate template slugs in lookup order. + * + * @return array + */ + public function home_falls_back_to_archive( $hierarchy ) { + if ( in_array( 'archive.php', $hierarchy, true ) ) { + return $hierarchy; + } + + $index = array_search( 'index.php', $hierarchy, true ); + + if ( false === $index ) { + $hierarchy[] = 'archive.php'; + return $hierarchy; + } + + array_splice( $hierarchy, $index, 0, 'archive.php' ); + + return $hierarchy; } /** @@ -102,7 +136,7 @@ public function setup() { 'title' => __( 'Archive Cards', 'raft' ), ), 'archive-row' => array( - 'file' => RAFT_DIR . 'library/archive/archive-row.php', + 'file' => RAFT_DIR . 'library/archive/archive-rows.php', 'title' => __( 'Archive Row', 'raft' ), ), ), diff --git a/inc/Dashboard.php b/inc/Dashboard.php new file mode 100644 index 0000000..bc6bfa3 --- /dev/null +++ b/inc/Dashboard.php @@ -0,0 +1,162 @@ +<?php +/** + * Raft dashboard page. + * + * Adds an "Raft" entry under Appearance with a welcome + Pro upsell layout. + * Pure PHP/HTML; no React, no JS, no build step. + * + * @package raft + */ + +namespace Raft; + +/** + * Class Dashboard + * + * @package raft + */ +class Dashboard { + + const MENU_SLUG = 'raft-dashboard'; + const PARENT = 'themes.php'; + const UPGRADE_URL = 'https://themeisle.com/themes/raft/upgrade/'; + + /** + * Dashboard constructor. + */ + public function __construct() { + add_action( 'admin_menu', array( $this, 'register_menu' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); + } + + /** + * Adds the dashboard menu entry under Appearance. + * + * @return void + */ + public function register_menu() { + add_theme_page( + __( 'Raft', 'raft' ), + __( 'Raft', 'raft' ), + 'manage_options', + self::MENU_SLUG, + array( $this, 'render' ) + ); + } + + /** + * Inline page styles. Scoped to the dashboard screen only so it never + * leaks into other admin pages. + * + * @param string $hook Current admin hook suffix. + * + * @return void + */ + public function enqueue_styles( $hook ) { + if ( 'appearance_page_' . self::MENU_SLUG !== $hook ) { + return; + } + + wp_register_style( 'raft-dashboard', false, array(), RAFT_VERSION ); + wp_enqueue_style( 'raft-dashboard' ); + + $css = ' + .raft-dashboard { max-width: 960px; margin: 24px auto; padding: 0 20px; } + .raft-dashboard h1 { font-size: 32px; line-height: 1.2; margin: 0 0 8px; } + .raft-dashboard p.lede { font-size: 16px; color: #50575e; margin: 0 0 32px; } + .raft-dashboard__hero { background: #fff; border: 1px solid #dcdcde; border-radius: 8px; padding: 32px; display: flex; gap: 32px; align-items: center; margin-bottom: 24px; } + .raft-dashboard__hero-content { flex: 1; } + .raft-dashboard__hero-content h2 { margin-top: 0; } + .raft-dashboard__cta { display: inline-block; background: #C26148; color: #fff; padding: 10px 20px; border-radius: 999px; text-decoration: none; font-weight: 600; } + .raft-dashboard__cta:hover { background: #AC5039; color: #fff; } + .raft-dashboard__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 32px; } + .raft-dashboard__card { background: #fff; border: 1px solid #dcdcde; border-radius: 8px; padding: 24px; } + .raft-dashboard__card .count { font-size: 28px; font-weight: 700; color: #C26148; display: block; line-height: 1; margin-bottom: 8px; } + .raft-dashboard__card .label { font-size: 14px; color: #50575e; margin: 0 0 12px; } + .raft-dashboard__card .desc { font-size: 13px; color: #646970; margin: 0; } + .raft-dashboard__quick-links { background: #fff; border: 1px solid #dcdcde; border-radius: 8px; padding: 24px; } + .raft-dashboard__quick-links h3 { margin-top: 0; } + .raft-dashboard__quick-links ul { margin: 0; } + .raft-dashboard__quick-links li { margin-bottom: 8px; } + '; + + wp_add_inline_style( 'raft-dashboard', $css ); + } + + /** + * Renders the dashboard page. + * + * @return void + */ + public function render() { + $site_editor_url = admin_url( 'site-editor.php' ); + $onboarding_url = add_query_arg( 'onboarding', 'true', $site_editor_url ); + $pro_active = is_plugin_active( 'raft-pro/raft-pro.php' ); + + ?> + <div class="raft-dashboard"> + <h1><?php esc_html_e( 'Welcome to Raft', 'raft' ); ?></h1> + <p class="lede"><?php esc_html_e( 'A lightweight, full-site-editing theme. Configure once, edit visually, ship fast.', 'raft' ); ?></p> + + <?php if ( ! $pro_active ) : ?> + <div class="raft-dashboard__hero"> + <div class="raft-dashboard__hero-content"> + <h2><?php esc_html_e( 'Get more with Raft Pro', 'raft' ); ?></h2> + <p><?php esc_html_e( 'Unlock 8 additional style variations, 17 block patterns, 7 ready-to-import page templates, and a fully designed WooCommerce storefront. Upgrade your site without changing your workflow.', 'raft' ); ?></p> + <a class="raft-dashboard__cta" href="<?php echo esc_url( self::UPGRADE_URL ); ?>" target="_blank" rel="noopener noreferrer"> + <?php esc_html_e( 'Upgrade to Pro', 'raft' ); ?> + </a> + </div> + </div> + + <div class="raft-dashboard__grid"> + <div class="raft-dashboard__card"> + <span class="count">+8</span> + <p class="label"><?php esc_html_e( 'Style variations', 'raft' ); ?></p> + <p class="desc"><?php esc_html_e( 'Editorial Mono, Harbor, Clay, Plum Night, and more — each with its own typography and palette.', 'raft' ); ?></p> + </div> + <div class="raft-dashboard__card"> + <span class="count">+17</span> + <p class="label"><?php esc_html_e( 'Block patterns', 'raft' ); ?></p> + <p class="desc"><?php esc_html_e( 'Bento Features, Marquee Hero, Stats Counter, Pricing Comparison, and more.', 'raft' ); ?></p> + </div> + <div class="raft-dashboard__card"> + <span class="count">+7</span> + <p class="label"><?php esc_html_e( 'Page templates', 'raft' ); ?></p> + <p class="desc"><?php esc_html_e( 'About, Services, Contact, Pricing, Careers, Case Study, Manifesto.', 'raft' ); ?></p> + </div> + <div class="raft-dashboard__card"> + <span class="count">+8</span> + <p class="label"><?php esc_html_e( 'WooCommerce templates', 'raft' ); ?></p> + <p class="desc"><?php esc_html_e( 'Designed cart, checkout, shop, single product, archives, and a slide-out mini-cart that match Raft.', 'raft' ); ?></p> + </div> + </div> + <?php else : ?> + <div class="raft-dashboard__hero"> + <div class="raft-dashboard__hero-content"> + <h2><?php esc_html_e( 'Raft Pro is active', 'raft' ); ?></h2> + <p><?php esc_html_e( 'All Pro patterns, styles, and templates are available in the Site Editor.', 'raft' ); ?></p> + <?php + /** + * Fires inside the Pro-active hero on Appearance → Raft. + * Raft Pro hooks here to render its license status as + * an inline meta line below the hero description. + */ + do_action( 'raft_dashboard_pro_section' ); + ?> + </div> + </div> + <?php endif; ?> + + <div class="raft-dashboard__quick-links"> + <h3><?php esc_html_e( 'Get started', 'raft' ); ?></h3> + <ul> + <li><a href="<?php echo esc_url( $onboarding_url ); ?>"><?php esc_html_e( 'Run the setup wizard', 'raft' ); ?></a> — <?php esc_html_e( 'Pick a homepage layout, archive style, and page templates.', 'raft' ); ?></li> + <li><a href="<?php echo esc_url( $site_editor_url ); ?>"><?php esc_html_e( 'Open the Site Editor', 'raft' ); ?></a> — <?php esc_html_e( 'Customize templates, parts, and styles.', 'raft' ); ?></li> + <li><a href="https://docs.themeisle.com/fse-themes/raft-fse-theme" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Read the documentation', 'raft' ); ?></a></li> + </ul> + </div> + </div> + <?php + } +} diff --git a/inc/Pro_Promotions.php b/inc/Pro_Promotions.php new file mode 100644 index 0000000..b07f737 --- /dev/null +++ b/inc/Pro_Promotions.php @@ -0,0 +1,164 @@ +<?php +/** + * Pro pattern upsells in the block inserter. + * + * Registers a `raft/pro` pattern category and a handful of placeholder + * patterns named after Raft Pro's signature patterns. When inserted, each + * drops a clearly-labelled "locked" upgrade card — never deceptive about + * being a placeholder. Pattern discovery, not bait-and-switch. + * + * Skipped entirely when Raft Pro is active so paying users don't see the + * upsells alongside the real patterns. + * + * @package raft + */ + +namespace Raft; + +/** + * Class Pro_Promotions + * + * @package raft + */ +class Pro_Promotions { + + const CATEGORY_SLUG = 'raft/pro'; + const UPGRADE_URL = 'https://themeisle.com/themes/raft/upgrade/'; + + /** + * Pro_Promotions constructor. + */ + public function __construct() { + add_action( 'init', array( $this, 'register' ), 11 ); + } + + /** + * Register the Pro category + placeholder patterns. + * + * Hooked at priority 11 so it runs after Block_Patterns (priority 10) + * has registered the free categories — keeps the category list tidy. + * + * @return void + */ + public function register() { + if ( $this->is_pro_active() ) { + return; + } + + register_block_pattern_category( + self::CATEGORY_SLUG, + array( 'label' => __( 'Raft Pro', 'raft' ) ) + ); + + foreach ( $this->get_pro_patterns() as $slug => $config ) { + register_block_pattern( + 'raft/pro-' . $slug, + array( + 'title' => $config['title'], + 'description' => $config['description'], + 'categories' => array( self::CATEGORY_SLUG ), + 'content' => $this->build_upgrade_card( $config['title'], $config['description'] ), + ) + ); + } + } + + /** + * Whether Raft Pro is active. + * + * Hides upsells from users who've already upgraded — they don't want + * placeholder cards cluttering their inserter next to the real ones. + * + * @return bool + */ + private function is_pro_active(): bool { + if ( ! function_exists( 'is_plugin_active' ) ) { + include_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + return is_plugin_active( 'raft-pro/raft-pro.php' ); + } + + /** + * The Pro patterns advertised in the inserter. + * + * Curated subset of Raft Pro's catalogue — the most visually-distinctive + * ones, picked for discovery impact. Full Pro list lives in + * `raft-pro/inc/Block_Patterns.php`. + * + * @return array<string, array{title: string, description: string}> + */ + private function get_pro_patterns(): array { + return apply_filters( + 'raft_pro_promoted_patterns', + array( + 'bento-features' => array( + 'title' => __( 'Bento Features (Pro)', 'raft' ), + 'description' => __( 'Grid-style feature showcase with mixed tile sizes — Bento layout.', 'raft' ), + ), + 'marquee-hero' => array( + 'title' => __( 'Marquee Hero (Pro)', 'raft' ), + 'description' => __( 'Full-bleed hero with animated marquee text and bold typography.', 'raft' ), + ), + 'stats-counter' => array( + 'title' => __( 'Stats Counter (Pro)', 'raft' ), + 'description' => __( 'Three-column metric callouts with large numbers and short labels.', 'raft' ), + ), + 'pricing-comparison' => array( + 'title' => __( 'Pricing Comparison (Pro)', 'raft' ), + 'description' => __( 'Side-by-side pricing tiers with featured plan highlight.', 'raft' ), + ), + 'team-spotlight' => array( + 'title' => __( 'Team Spotlight (Pro)', 'raft' ), + 'description' => __( 'Team grid with circular avatars and bio cards.', 'raft' ), + ), + 'gradient-cta' => array( + 'title' => __( 'Gradient CTA (Pro)', 'raft' ), + 'description' => __( 'Full-width call-to-action with gradient background and centered copy.', 'raft' ), + ), + ) + ); + } + + /** + * The "locked" upgrade card block markup inserted in place of the real + * pattern. Reuses Raft's color/spacing presets so it sits naturally on + * any style variation. The user can delete it the moment they realize + * it's a placeholder — that's the point. + * + * @param string $pattern_title Pattern title shown in the card heading. + * @param string $description Short description shown in the card body. + * + * @return string + */ + private function build_upgrade_card( string $pattern_title, string $description ): string { + $eyebrow = esc_html__( 'Locked — Pro pattern', 'raft' ); + $heading = esc_html( $pattern_title ); + $body = esc_html( $description ); + $cta = esc_html__( 'Upgrade to Raft Pro', 'raft' ); + $upgrade_url = esc_url( self::UPGRADE_URL ); + + return <<<HTML +<!-- wp:group {"align":"wide","style":{"spacing":{"padding":{"top":"var:preset|spacing|70","bottom":"var:preset|spacing|70","left":"var:preset|spacing|50","right":"var:preset|spacing|50"},"blockGap":"var:preset|spacing|30"},"border":{"radius":"16px","width":"2px","style":"dashed","color":"var:preset|color|raft-accent"}},"backgroundColor":"raft-bg-alt","layout":{"type":"constrained","contentSize":"640px"}} --> +<div class="wp-block-group alignwide has-border-color has-raft-bg-alt-background-color has-background" style="border-style:dashed;border-width:2px;border-color:var(--wp--preset--color--raft-accent);border-radius:16px;padding-top:var(--wp--preset--spacing--70);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--70);padding-left:var(--wp--preset--spacing--50)"> + <!-- wp:paragraph {"align":"center","style":{"typography":{"textTransform":"uppercase","fontWeight":"600","letterSpacing":"2px"},"spacing":{"margin":{"top":"0","bottom":"0"}}},"textColor":"raft-accent","fontSize":"small"} --> + <p class="has-text-align-center has-raft-accent-color has-text-color has-small-font-size" style="margin-top:0;margin-bottom:0;font-weight:600;letter-spacing:2px;text-transform:uppercase">{$eyebrow}</p> + <!-- /wp:paragraph --> + <!-- wp:heading {"textAlign":"center","level":3,"style":{"spacing":{"margin":{"top":"0","bottom":"0"}}}} --> + <h3 class="wp-block-heading has-text-align-center" style="margin-top:0;margin-bottom:0">{$heading}</h3> + <!-- /wp:heading --> + <!-- wp:paragraph {"align":"center","style":{"spacing":{"margin":{"top":"0","bottom":"0"}}}} --> + <p class="has-text-align-center" style="margin-top:0;margin-bottom:0">{$body}</p> + <!-- /wp:paragraph --> + <!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} --> + <div class="wp-block-buttons"> + <!-- wp:button {"style":{"border":{"radius":"999px"}}} --> + <div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="{$upgrade_url}" style="border-radius:999px" target="_blank" rel="noopener noreferrer">{$cta}</a></div> + <!-- /wp:button --> + </div> + <!-- /wp:buttons --> +</div> +<!-- /wp:group --> +HTML; + } +} diff --git a/inc/Wizard_Promo.php b/inc/Wizard_Promo.php new file mode 100644 index 0000000..4b720af --- /dev/null +++ b/inc/Wizard_Promo.php @@ -0,0 +1,102 @@ +<?php +/** + * Onboarding wizard upsells. + * + * Otter's onboarding wizard ships as a black box — Raft doesn't fork it. + * Instead, when the wizard URL is loaded (`site-editor.php?onboarding=true`) + * AND Raft Pro isn't active, we enqueue a small vanilla-JS script that + * subscribes to Otter's data store and injects two upsell surfaces into the + * rendered React tree: + * + * 1. A persistent "Try Pro" nudge inside the sidebar (every step). + * 2. A larger Pro promo card on the Finish step, above the actions. + * + * Both are skipped entirely when Pro is active — no risk of double-promoting + * to existing Pro users. All strings + the upgrade URL are localized so we + * never hardcode anything in JS that translators can't reach. + * + * @package raft + */ + +namespace Raft; + +/** + * Class Wizard_Promo + * + * @package raft + */ +class Wizard_Promo { + + const SCRIPT_HANDLE = 'raft-wizard-promo'; + const UPGRADE_URL = 'https://themeisle.com/themes/raft/upgrade/'; + + /** + * Wizard_Promo constructor. + */ + public function __construct() { + add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue' ), 20 ); + } + + /** + * Enqueue the promo script only when the wizard is actually being rendered. + * + * @param string $hook Current admin screen hook. + * + * @return void + */ + public function maybe_enqueue( $hook ) { + if ( 'site-editor.php' !== $hook ) { + return; + } + + // Wizard is invoked via `?onboarding=true`. Otter's own enqueue uses + // the same gate, so we mirror that check rather than running on the + // regular site-editor screen. + if ( ! isset( $_GET['onboarding'] ) || 'true' !== $_GET['onboarding'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return; + } + + if ( $this->is_pro_active() ) { + return; + } + + wp_enqueue_script( + self::SCRIPT_HANDLE, + RAFT_URL . 'assets/js/wizard-promo.js', + array( 'wp-data', 'wp-element', 'wp-dom-ready' ), + RAFT_VERSION, + true + ); + + wp_localize_script( + self::SCRIPT_HANDLE, + 'raftWizardPromo', + array( + 'upgradeUrl' => self::UPGRADE_URL, + 'strings' => array( + 'sidebarEyebrow' => __( 'Want more?', 'raft' ), + 'sidebarTitle' => __( 'Try Raft Pro', 'raft' ), + 'sidebarLink' => __( 'See what\'s included', 'raft' ), + 'finishEyebrow' => __( 'Get even more', 'raft' ), + 'finishTitle' => __( 'Take it further with Raft Pro', 'raft' ), + 'finishBody' => __( 'Unlock 17 extra patterns, 8 style variations, 7 ready-made page templates, and a fully designed WooCommerce storefront.', 'raft' ), + 'finishCta' => __( 'Upgrade to Pro', 'raft' ), + ), + ) + ); + } + + /** + * Whether Raft Pro is active. Standard plugin-active check, with the + * plugin.php include guard for early hooks. + * + * @return bool + */ + private function is_pro_active(): bool { + if ( ! function_exists( 'is_plugin_active' ) ) { + include_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + return is_plugin_active( 'raft-pro/raft-pro.php' ); + } +} diff --git a/inc/patterns/cover-background.php b/inc/patterns/cover-background.php index 4e038e1..761d424 100644 --- a/inc/patterns/cover-background.php +++ b/inc/patterns/cover-background.php @@ -22,36 +22,32 @@ 'title' => __( 'Cover with Background', 'raft' ), 'categories' => array( 'raft/heroes_page_titles' ), 'content' => ' - <!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"0","left":"0","top":"0","bottom":"0"},"margin":{"top":"0","bottom":"0"}}},"layout":{"type":"constrained"}} --> - <div class="wp-block-group alignfull" style="margin-top:0;margin-bottom:0;padding-top:0;padding-right:0;padding-bottom:0;padding-left:0"> - <!-- wp:cover {"url":"' . esc_url( $images[0] ) . '","dimRatio":60,"overlayColor":"black","minHeight":600,"minHeightUnit":"px","align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|80","right":"var:preset|spacing|40","bottom":"var:preset|spacing|80","left":"var:preset|spacing|40"},"blockGap":"var:preset|spacing|30"}}} --> - <div class="wp-block-cover alignfull" style="padding-top:var(--wp--preset--spacing--80);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--80);padding-left:var(--wp--preset--spacing--40);min-height:600px"> - <span aria-hidden="true" class="wp-block-cover__background has-black-background-color has-background-dim-60 has-background-dim"></span> - <img class="wp-block-cover__image-background" alt="" src="' . esc_url( $images[0] ) . '" data-object-fit="cover"/> - <div class="wp-block-cover__inner-container"> - <!-- wp:group {"style":{"spacing":{"blockGap":"var:preset|spacing|50"}},"textColor":"raft-fg-alt","layout":{"inherit":true,"type":"constrained"}} --> - <div class="wp-block-group has-raft-fg-alt-color has-text-color"> - <!-- wp:heading {"level":1} --> - <h1 class="wp-block-heading">' . esc_html( $raft_strings['hero_title'] ) . '</h1> - <!-- /wp:heading --> + <!-- wp:cover {"url":"' . esc_url( $images[0] ) . '","dimRatio":60,"overlayColor":"black","minHeight":600,"minHeightUnit":"px","align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|80","right":"var:preset|spacing|40","bottom":"var:preset|spacing|80","left":"var:preset|spacing|40"},"margin":{"top":"0","bottom":"0"},"blockGap":"var:preset|spacing|30"}}} --> + <div class="wp-block-cover alignfull" style="margin-top:0;margin-bottom:0;padding-top:var(--wp--preset--spacing--80);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--80);padding-left:var(--wp--preset--spacing--40);min-height:600px"> + <span aria-hidden="true" class="wp-block-cover__background has-black-background-color has-background-dim-60 has-background-dim"></span> + <img class="wp-block-cover__image-background" alt="" src="' . esc_url( $images[0] ) . '" data-object-fit="cover"/> + <div class="wp-block-cover__inner-container"> + <!-- wp:group {"style":{"spacing":{"blockGap":"var:preset|spacing|50"}},"textColor":"raft-fg-alt","layout":{"inherit":true,"type":"constrained"}} --> + <div class="wp-block-group has-raft-fg-alt-color has-text-color"> + <!-- wp:heading {"level":1} --> + <h1 class="wp-block-heading">' . esc_html( $raft_strings['hero_title'] ) . '</h1> + <!-- /wp:heading --> - <!-- wp:paragraph {"fontSize":"medium"} --> - <p class="has-medium-font-size">' . esc_html( $raft_strings['short_text'] ) . '</p> - <!-- /wp:paragraph --> + <!-- wp:paragraph {"fontSize":"medium"} --> + <p class="has-medium-font-size">' . esc_html( $raft_strings['short_text'] ) . '</p> + <!-- /wp:paragraph --> - <!-- wp:buttons --> - <div class="wp-block-buttons"> - <!-- wp:button --> - <div class="wp-block-button"><a class="wp-block-button__link wp-element-button">' . esc_html( $raft_strings['button_text_2'] ) . '</a></div> - <!-- /wp:button --> - </div> - <!-- /wp:buttons --> + <!-- wp:buttons --> + <div class="wp-block-buttons"> + <!-- wp:button --> + <div class="wp-block-button"><a class="wp-block-button__link wp-element-button">' . esc_html( $raft_strings['button_text_2'] ) . '</a></div> + <!-- /wp:button --> </div> - <!-- /wp:group --> + <!-- /wp:buttons --> </div> + <!-- /wp:group --> </div> - <!-- /wp:cover --> </div> - <!-- /wp:group --> + <!-- /wp:cover --> ', );