From ad564160a5b0e2e0a4948c6575814d773f359ba9 Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Tue, 21 Apr 2026 11:54:42 +1000 Subject: [PATCH 01/28] Create and register paywall config --- .gitignore | 1 + .../plugins/memberful-wp/memberful-wp.php | 1 + .../plugins/memberful-wp/src/options.php | 60 ++++++++-------- .../plugins/memberful-wp/src/paywall.php | 8 +++ .../memberful-wp/src/paywall/config.php | 61 ++++++++++++++++ .../memberful-wp/src/paywall/sanitizer.php | 71 +++++++++++++++++++ 6 files changed, 173 insertions(+), 29 deletions(-) create mode 100644 wordpress/wp-content/plugins/memberful-wp/src/paywall.php create mode 100644 wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php create mode 100644 wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php diff --git a/.gitignore b/.gitignore index c0b8df13..cf79054b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +.idea # Manage wordpress/wp-content/plugins/memberful-wp and ignore everything else # from the wordpress/ folder. diff --git a/wordpress/wp-content/plugins/memberful-wp/memberful-wp.php b/wordpress/wp-content/plugins/memberful-wp/memberful-wp.php index 5ba9d8db..daeff845 100755 --- a/wordpress/wp-content/plugins/memberful-wp/memberful-wp.php +++ b/wordpress/wp-content/plugins/memberful-wp/memberful-wp.php @@ -46,6 +46,7 @@ require_once MEMBERFUL_DIR . '/src/widgets.php'; require_once MEMBERFUL_DIR . '/src/endpoints.php'; require_once MEMBERFUL_DIR . '/src/marketing_content.php'; +require_once MEMBERFUL_DIR . '/src/paywall.php'; require_once MEMBERFUL_DIR . '/src/content_filter.php'; require_once MEMBERFUL_DIR . '/src/search_filter.php'; require_once MEMBERFUL_DIR . '/src/entities.php'; diff --git a/wordpress/wp-content/plugins/memberful-wp/src/options.php b/wordpress/wp-content/plugins/memberful-wp/src/options.php index 0f16f406..39a611c8 100755 --- a/wordpress/wp-content/plugins/memberful-wp/src/options.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/options.php @@ -1,35 +1,37 @@ NULL, - 'memberful_client_secret' => NULL, - 'memberful_site' => NULL, - 'memberful_custom_domain' => NULL, - 'memberful_api_key' => NULL, - 'memberful_webhook_secret' => NULL, - 'memberful_products' => array(), - 'memberful_subscriptions' => array(), - 'memberful_acl' => array(), - 'memberful_embed_enabled' => FALSE, - 'memberful_error_log' => array(), - 'memberful_role_active_customer' => 'subscriber', - 'memberful_role_inactive_customer' => 'subscriber', - 'memberful_plan_role_mappings' => array(), - 'memberful_use_per_plan_roles' => FALSE, - 'memberful_posts_available_to_any_registered_user' => array(), - 'memberful_hide_admin_toolbar' => TRUE, - 'memberful_block_dashboard_access' => TRUE, - 'memberful_filter_account_menu_items' => TRUE, - 'memberful_auto_sync_display_names' => FALSE, - 'memberful_show_protected_content_in_search' => FALSE, - 'memberful_use_global_marketing' => FALSE, - 'memberful_use_global_snippets' => TRUE, - 'memberful_global_marketing_override' => TRUE, - 'memberful_global_marketing_content' => '', - 'memberful_ad_provider_settings' => array() - ); +function memberful_wp_all_options(): array { + return array( + 'memberful_client_id' => null, + 'memberful_client_secret' => null, + 'memberful_site' => null, + 'memberful_custom_domain' => null, + 'memberful_api_key' => null, + 'memberful_webhook_secret' => null, + 'memberful_products' => array(), + 'memberful_subscriptions' => array(), + 'memberful_acl' => array(), + 'memberful_embed_enabled' => false, + 'memberful_error_log' => array(), + 'memberful_role_active_customer' => 'subscriber', + 'memberful_role_inactive_customer' => 'subscriber', + 'memberful_plan_role_mappings' => array(), + 'memberful_use_per_plan_roles' => false, + 'memberful_posts_available_to_any_registered_user' => array(), + 'memberful_hide_admin_toolbar' => true, + 'memberful_block_dashboard_access' => true, + 'memberful_filter_account_menu_items' => true, + 'memberful_auto_sync_display_names' => false, + 'memberful_show_protected_content_in_search' => false, + 'memberful_use_global_marketing' => false, + 'memberful_use_global_snippets' => true, + 'memberful_global_marketing_override' => true, + 'memberful_global_marketing_content' => '', + 'memberful_ad_provider_settings' => array(), + Memberful_Paywall_Config::OPTION_KEY => array(), + Memberful_Paywall_Config::LEGACY_FLAG_KEY => false, + ); } /** diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php new file mode 100644 index 00000000..89bbe3aa --- /dev/null +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php @@ -0,0 +1,8 @@ + 'builder', + 'layout' => 'card', + 'heading' => esc_html__( 'Subscribe to keep reading', 'memberful' ), + 'heading_tag' => 'h2', + 'subheading' => esc_html__( 'This post is for paying subscribers.', 'memberful' ), + 'subheading_tag' => 'p', + 'features' => array(), + 'button_label' => esc_html__( 'Subscribe', 'memberful' ), + 'subscribe_url' => '', + 'sign_in_url' => '', + 'brand_color' => '#2f80ed', + 'button_shape' => 'rounded', + 'custom_css' => '', + ); + } + + /** + * Read the stored config merged over defaults. + * + * @return array + */ + public static function get(): array { + $stored = get_option( self::OPTION_KEY, array() ); + + if ( ! is_array( $stored ) ) { + $stored = array(); + } + + return wp_parse_args( $stored, self::defaults() ); + } + + /** + * Validate, sanitize, and persist a config payload. + * + * @param array $input Raw input (typically from the options form). + * + * @return bool True when the option was updated, false when unchanged or on failure. + */ + public static function save( array $input ): bool { + $clean = Memberful_Paywall_Sanitizer::sanitize( $input, self::defaults() ); + + return update_option( self::OPTION_KEY, $clean ); + } +} diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php new file mode 100644 index 00000000..82eec48e --- /dev/null +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php @@ -0,0 +1,71 @@ + array( 'builder', 'custom_html' ), + 'layout' => array( 'simple', 'card', 'banner' ), + 'heading_tag' => array( 'h1', 'h2', 'h3' ), + 'subheading_tag' => array( 'p', 'h3', 'h4' ), + 'button_shape' => array( 'pill', 'rounded', 'square' ), + ); + + foreach ( $enums as $key => $allowed ) { + if ( isset( $input[ $key ] ) && in_array( $input[ $key ], $allowed, true ) ) { + $clean[ $key ] = $input[ $key ]; + } + } + + foreach ( array( 'heading', 'subheading', 'button_label' ) as $key ) { + if ( isset( $input[ $key ] ) ) { + $clean[ $key ] = sanitize_text_field( (string) $input[ $key ] ); + } + } + + if ( isset( $input['features'] ) ) { + $features = is_array( $input['features'] ) + ? $input['features'] + : preg_split( "/\r\n|\n|\r/", (string) $input['features'] ); + + $features = array_map( 'sanitize_text_field', (array) $features ); + $features = array_map( 'trim', $features ); + $features = array_values( array_filter( $features, 'strlen' ) ); + + $clean['features'] = $features; + } + + foreach ( array( 'subscribe_url', 'sign_in_url' ) as $key ) { + if ( isset( $input[ $key ] ) ) { + $clean[ $key ] = esc_url_raw( (string) $input[ $key ] ); + } + } + + if ( isset( $input['brand_color'] ) ) { + $color = sanitize_hex_color( (string) $input['brand_color'] ); + if ( null !== $color && '' !== $color ) { + $clean['brand_color'] = $color; + } + } + + if ( isset( $input['custom_css'] ) ) { + $clean['custom_css'] = wp_strip_all_tags( (string) $input['custom_css'] ); + } + + return $clean; + } +} From 28c461e2d9dac5d4b3723b730649af8086168ace Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Tue, 21 Apr 2026 12:14:43 +1000 Subject: [PATCH 02/28] Render function + three layout templates --- .../plugins/memberful-wp/src/paywall.php | 3 +- .../memberful-wp/src/paywall/renderer.php | 260 ++++++++++++++++++ 2 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php index 89bbe3aa..b184a094 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php @@ -5,4 +5,5 @@ * @package memberful-wp */ require_once MEMBERFUL_DIR . '/src/paywall/sanitizer.php'; -require_once MEMBERFUL_DIR . '/src/paywall/config.php'; \ No newline at end of file +require_once MEMBERFUL_DIR . '/src/paywall/config.php'; +require_once MEMBERFUL_DIR . '/src/paywall/renderer.php'; \ No newline at end of file diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php new file mode 100644 index 00000000..7ad224a6 --- /dev/null +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php @@ -0,0 +1,260 @@ +%s%s', + esc_attr( $layout ), + esc_attr( self::wrapper_style( $config ) ), + $body, + self::custom_css_block( $config ) + ); + } + + /** + * Render the "simple" layout. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function render_simple( array $config ): string { + return self::heading_block( $config ) + . self::subheading_block( $config ) + . self::features_block( $config ) + . '
' . self::primary_cta( $config ) . '
'; + } + + /** + * Render the "card" layout. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function render_card( array $config ): string { + return '
' + . self::heading_block( $config ) + . self::subheading_block( $config ) + . self::features_block( $config ) + . '
' + . self::primary_cta( $config ) + . self::secondary_cta( $config ) + . '
' + . '
'; + } + + /** + * Render the "banner" layout. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function render_banner( array $config ): string { + return '
' + . self::heading_block( $config ) + . self::subheading_block( $config ) + . self::features_block( $config ) + . '
' + . '
' . self::primary_cta( $config ) . '
'; + } + + /** + * Heading element with the configured tag. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function heading_block( array $config ): string { + $tag = in_array( $config['heading_tag'], self::HEADING_TAGS, true ) ? $config['heading_tag'] : 'h2'; + return sprintf( + '<%1$s class="memberful-paywall__heading">%2$s', + tag_escape( $tag ), + esc_html( $config['heading'] ) + ); + } + + /** + * Subheading element with the configured tag, or empty when blank. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function subheading_block( array $config ): string { + if ( '' === $config['subheading'] ) { + return ''; + } + + $tag = in_array( $config['subheading_tag'], self::SUBHEADING_TAGS, true ) ? $config['subheading_tag'] : 'p'; + return sprintf( + '<%1$s class="memberful-paywall__subheading">%2$s', + tag_escape( $tag ), + esc_html( $config['subheading'] ) + ); + } + + /** + * Feature list with inline check icons, or empty when no features. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function features_block( array $config ): string { + if ( empty( $config['features'] ) ) { + return ''; + } + + $items = ''; + foreach ( $config['features'] as $feature ) { + $items .= '
  • ' . self::check_icon() . '' . esc_html( $feature ) . '
  • '; + } + + return ''; + } + + /** + * Primary subscribe CTA anchor. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function primary_cta( array $config ): string { + return sprintf( + '%s', + esc_url( self::subscribe_url( $config ) ), + esc_html( $config['button_label'] ) + ); + } + + /** + * Secondary sign-in CTA anchor (card layout only). + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function secondary_cta( array $config ): string { + return sprintf( + '%s', + esc_url( self::sign_in_url( $config ) ), + esc_html__( 'Sign in', 'memberful' ) + ); + } + + /** + * Wrapper inline style carrying the brand colour and button radius custom properties. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function wrapper_style( array $config ): string { + return sprintf( + '--mf-brand:%s;--mf-radius:%s;', + $config['brand_color'], + self::button_radius( $config['button_shape'] ) + ); + } + + /** + * Map the button-shape enum to a CSS radius. + * + * @param string $shape One of the `button_shape` enum values. + * + * @return string + */ + private static function button_radius( string $shape ): string { + switch ( $shape ) { + case 'pill': + return '999px'; + case 'square': + return '0'; + case 'rounded': + default: + return '8px'; + } + } + + /** + * Custom CSS '; + } + + /** + * Resolve the subscribe URL, falling back to the Memberful registration page. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function subscribe_url( array $config ): string { + return ! empty( $config['subscribe_url'] ) ? $config['subscribe_url'] : memberful_registration_page_url(); + } + + /** + * Resolve the sign-in URL, falling back to the Memberful sign-in endpoint. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function sign_in_url( array $config ): string { + return ! empty( $config['sign_in_url'] ) ? $config['sign_in_url'] : memberful_sign_in_url(); + } + + /** + * Inline check-mark SVG used in the features list. + * + * @return string + */ + private static function check_icon(): string { + return ''; + } +} \ No newline at end of file From df0ee893888ac1adcba7aea5be312d5d24ec91db Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Tue, 21 Apr 2026 13:01:27 +1000 Subject: [PATCH 03/28] Prepare render for paywal config --- .../plugins/memberful-wp/src/admin.php | 60 +++++++++++-------- .../memberful-wp/src/global_marketing.php | 33 +++++++--- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/src/admin.php b/wordpress/wp-content/plugins/memberful-wp/src/admin.php index fa18a503..698a86d9 100755 --- a/wordpress/wp-content/plugins/memberful-wp/src/admin.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/admin.php @@ -687,32 +687,40 @@ function memberful_wp_add_protected_state_to_post_list($states, $post) { } function memberful_wp_global_marketing() { - if ( isset( $_POST['save_global_marketing'] ) && memberful_wp_valid_nonce( 'memberful_options' ) ) { - if ( isset( $_POST['memberful_use_global_marketing'] ) ) { - update_option( 'memberful_use_global_marketing', true ); - update_option( 'memberful_global_marketing_override', filter_input( INPUT_POST, 'memberful_global_marketing_override', FILTER_SANITIZE_NUMBER_INT ) ); - update_option( 'memberful_global_marketing_content', memberful_wp_kses_post( filter_input( INPUT_POST, 'memberful_global_marketing_content' ) ) ); - update_option( 'memberful_use_global_snippets', (int) isset($_POST['memberful_use_global_snippets'])); - } else { - update_option( 'memberful_use_global_marketing', false ); - } - } - - $use_global_marketing = get_option( 'memberful_use_global_marketing' ); - $use_global_snippets = get_option( 'memberful_use_global_snippets'); - $global_marketing_content = get_option( 'memberful_global_marketing_content' ); - $global_marketing_override = get_option( 'memberful_global_marketing_override', true ); - - memberful_wp_render( - 'global_marketing', - array( - 'use_global_marketing' => $use_global_marketing, - 'use_global_snippets' => $use_global_snippets, - 'global_marketing_content' => $global_marketing_content, - 'global_marketing_override' => $global_marketing_override, - 'form_target' => memberful_wp_plugin_global_marketing_url() - ) - ); + if ( isset( $_POST['save_global_marketing'] ) && memberful_wp_valid_nonce( 'memberful_options' ) ) { + if ( isset( $_POST['memberful_use_global_marketing'] ) ) { + update_option( 'memberful_use_global_marketing', true ); + update_option( 'memberful_global_marketing_override', filter_input( INPUT_POST, 'memberful_global_marketing_override', FILTER_SANITIZE_NUMBER_INT ) ); + update_option( 'memberful_use_global_snippets', (int) isset( $_POST['memberful_use_global_snippets'] ) ); + + $paywall_input = filter_input( INPUT_POST, 'memberful_paywall', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); + if ( is_array( $paywall_input ) && ! empty( $paywall_input ) ) { + Memberful_Paywall_Config::save( $paywall_input ); + } + + if ( 'custom_html' === Memberful_Paywall_Config::get()['mode'] ) { + update_option( 'memberful_global_marketing_content', memberful_wp_kses_post( filter_input( INPUT_POST, 'memberful_global_marketing_content' ) ) ); + } + } else { + update_option( 'memberful_use_global_marketing', false ); + } + } + + $use_global_marketing = get_option( 'memberful_use_global_marketing' ); + $use_global_snippets = get_option( 'memberful_use_global_snippets'); + $global_marketing_content = get_option( 'memberful_global_marketing_content' ); + $global_marketing_override = get_option( 'memberful_global_marketing_override', true ); + + memberful_wp_render( + 'global_marketing', + array( + 'use_global_marketing' => $use_global_marketing, + 'use_global_snippets' => $use_global_snippets, + 'global_marketing_content' => $global_marketing_content, + 'global_marketing_override' => $global_marketing_override, + 'form_target' => memberful_wp_plugin_global_marketing_url() + ) + ); } /** diff --git a/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php b/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php index ed1598c9..99442ee4 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php @@ -17,18 +17,33 @@ * @return string */ function memberful_get_global_replacement($marketing_content){ - $override = get_option( 'memberful_global_marketing_override' ); - $global_marketing_content = get_option( 'memberful_global_marketing_content' ); + $override = get_option( 'memberful_global_marketing_override' ); + $global_marketing_content = memberful_wp_resolve_global_marketing_content(); - if($override) { - return $global_marketing_content; - } + if ( $override ) { + return $global_marketing_content; + } - if(empty(trim($marketing_content))){ - return $global_marketing_content; - } + if ( empty( trim( $marketing_content ) ) ) { + return $global_marketing_content; + } + + return $marketing_content; +} + +/** + * Resolve the global marketing HTML from whichever source the paywall config points to. + * + * @return string + */ +function memberful_wp_resolve_global_marketing_content(): string { + $config = Memberful_Paywall_Config::get(); + + if ( 'builder' === $config['mode'] ) { + return Memberful_Paywall_Renderer::render( $config ); + } - return $marketing_content; + return (string) get_option( 'memberful_global_marketing_content' ); } /** From d335a333dc64e6870b96880beb5ee2ab3eb8b5c1 Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Tue, 21 Apr 2026 16:41:01 +1000 Subject: [PATCH 04/28] Add builder UI --- .../memberful-wp/js/src/paywall-builder.js | 24 ++++ .../plugins/memberful-wp/src/admin.php | 23 +++- .../plugins/memberful-wp/src/options.php | 1 - .../plugins/memberful-wp/src/paywall.php | 5 +- .../memberful-wp/src/paywall/config.php | 29 +++- .../memberful-wp/src/paywall/renderer.php | 12 +- .../memberful-wp/src/paywall/sanitizer.php | 10 +- .../memberful-wp/stylesheets/admin.css | 35 +++++ .../memberful-wp/views/global_marketing.php | 124 +++++++++--------- .../views/paywall/builder-panel.php | 113 ++++++++++++++++ .../views/paywall/custom-html-panel.php | 13 ++ .../memberful-wp/views/paywall/mode-radio.php | 21 +++ .../plugins/memberful-wp/webpack.config.js | 1 + 13 files changed, 323 insertions(+), 88 deletions(-) create mode 100644 wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js create mode 100644 wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php create mode 100644 wordpress/wp-content/plugins/memberful-wp/views/paywall/custom-html-panel.php create mode 100644 wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php diff --git a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js new file mode 100644 index 00000000..21d34b36 --- /dev/null +++ b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js @@ -0,0 +1,24 @@ +/** + * Paywall builder admin script. + */ + +jQuery(function ($) { + $('.memberful-paywall-builder__color').wpColorPicker(); + + const $modeInputs = $('input[name="memberful_paywall[mode]"]'); + const $panels = $('.memberful-paywall-builder__panel'); + + function applyMode(mode) { + $panels.each(function () { + this.style.display = this.dataset.panel === mode ? '' : 'none'; + }); + } + + $modeInputs.on('change', function () { + if (this.checked) { + applyMode(this.value); + } + }); + + applyMode($modeInputs.filter(':checked').val() || 'builder'); +}); \ No newline at end of file diff --git a/wordpress/wp-content/plugins/memberful-wp/src/admin.php b/wordpress/wp-content/plugins/memberful-wp/src/admin.php index 698a86d9..04cf6ff8 100755 --- a/wordpress/wp-content/plugins/memberful-wp/src/admin.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/admin.php @@ -127,6 +127,20 @@ function memberful_wp_admin_enqueue_scripts() { ); } + if ( + 'memberful_options' === filter_input( INPUT_GET, 'page' ) + && 'global_marketing' === filter_input( INPUT_GET, 'subpage' ) + ) { + wp_enqueue_style( 'wp-color-picker' ); + wp_enqueue_script( + 'memberful-paywall-builder', + MEMBERFUL_URL . '/js/build/paywall-builder.js', + array( 'jquery', 'wp-color-picker' ), + MEMBERFUL_VERSION, + true + ); + } + wp_enqueue_script( 'memberful-menu', plugins_url( 'js/src/menu.js', dirname( __FILE__ ) ), @@ -714,11 +728,12 @@ function memberful_wp_global_marketing() { memberful_wp_render( 'global_marketing', array( - 'use_global_marketing' => $use_global_marketing, - 'use_global_snippets' => $use_global_snippets, - 'global_marketing_content' => $global_marketing_content, + 'use_global_marketing' => $use_global_marketing, + 'use_global_snippets' => $use_global_snippets, + 'global_marketing_content' => $global_marketing_content, 'global_marketing_override' => $global_marketing_override, - 'form_target' => memberful_wp_plugin_global_marketing_url() + 'paywall_config' => Memberful_Paywall_Config::get(), + 'form_target' => memberful_wp_plugin_global_marketing_url(), ) ); } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/options.php b/wordpress/wp-content/plugins/memberful-wp/src/options.php index 39a611c8..e6acbf47 100755 --- a/wordpress/wp-content/plugins/memberful-wp/src/options.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/options.php @@ -30,7 +30,6 @@ function memberful_wp_all_options(): array { 'memberful_global_marketing_content' => '', 'memberful_ad_provider_settings' => array(), Memberful_Paywall_Config::OPTION_KEY => array(), - Memberful_Paywall_Config::LEGACY_FLAG_KEY => false, ); } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php index b184a094..b2d46855 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php @@ -4,6 +4,7 @@ * * @package memberful-wp */ -require_once MEMBERFUL_DIR . '/src/paywall/sanitizer.php'; + require_once MEMBERFUL_DIR . '/src/paywall/config.php'; -require_once MEMBERFUL_DIR . '/src/paywall/renderer.php'; \ No newline at end of file +require_once MEMBERFUL_DIR . '/src/paywall/sanitizer.php'; +require_once MEMBERFUL_DIR . '/src/paywall/renderer.php'; diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php index d6ffa8fd..6d099ef0 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php @@ -5,8 +5,13 @@ * @package memberful-wp */ class Memberful_Paywall_Config { - const OPTION_KEY = 'memberful_paywall_config'; - const LEGACY_FLAG_KEY = 'memberful_paywall_legacy_detected'; + const OPTION_KEY = 'memberful_paywall_config'; + + const MODES = array( 'builder', 'custom_html' ); + const LAYOUTS = array( 'simple', 'card', 'banner' ); + const HEADING_TAGS = array( 'h1', 'h2', 'h3' ); + const SUBHEADING_TAGS = array( 'p', 'h3', 'h4' ); + const BUTTON_SHAPES = array( 'pill', 'rounded', 'square' ); /** * Canonical default configuration shape. @@ -34,6 +39,10 @@ public static function defaults(): array { /** * Read the stored config merged over defaults. * + * On sites with legacy custom HTML in memberful_global_marketing_content and no stored builder config yet, the + * default mode swaps to custom_html so the existing content keeps rendering untouched. Once the user saves any + * config, the stored value wins and this check short-circuits. + * * @return array */ public static function get(): array { @@ -43,7 +52,21 @@ public static function get(): array { $stored = array(); } - return wp_parse_args( $stored, self::defaults() ); + $defaults = self::defaults(); + if ( empty( $stored ) && self::has_legacy_content() ) { + $defaults['mode'] = 'custom_html'; + } + + return wp_parse_args( $stored, $defaults ); + } + + /** + * Whether the legacy marketing content option is populated. + * + * @return bool + */ + private static function has_legacy_content(): bool { + return '' !== trim( (string) get_option( 'memberful_global_marketing_content' ) ); } /** diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php index 7ad224a6..91e5c3af 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php @@ -13,10 +13,6 @@ * Renders a paywall config array to HTML. */ class Memberful_Paywall_Renderer { - const HEADING_TAGS = array( 'h1', 'h2', 'h3' ); - const SUBHEADING_TAGS = array( 'p', 'h3', 'h4' ); - const LAYOUTS = array( 'simple', 'card', 'banner' ); - /** * Render a paywall config to HTML. * @@ -27,7 +23,7 @@ class Memberful_Paywall_Renderer { public static function render( array $config ): string { $config = wp_parse_args( $config, Memberful_Paywall_Config::defaults() ); - $layout = in_array( $config['layout'], self::LAYOUTS, true ) ? $config['layout'] : 'card'; + $layout = in_array( $config['layout'], Memberful_Paywall_Config::LAYOUTS, true ) ? $config['layout'] : 'card'; $method = 'render_' . $layout; $body = self::$method( $config ); @@ -98,7 +94,7 @@ private static function render_banner( array $config ): string { * @return string */ private static function heading_block( array $config ): string { - $tag = in_array( $config['heading_tag'], self::HEADING_TAGS, true ) ? $config['heading_tag'] : 'h2'; + $tag = in_array( $config['heading_tag'], Memberful_Paywall_Config::HEADING_TAGS, true ) ? $config['heading_tag'] : 'h2'; return sprintf( '<%1$s class="memberful-paywall__heading">%2$s', tag_escape( $tag ), @@ -118,7 +114,7 @@ private static function subheading_block( array $config ): string { return ''; } - $tag = in_array( $config['subheading_tag'], self::SUBHEADING_TAGS, true ) ? $config['subheading_tag'] : 'p'; + $tag = in_array( $config['subheading_tag'], Memberful_Paywall_Config::SUBHEADING_TAGS, true ) ? $config['subheading_tag'] : 'p'; return sprintf( '<%1$s class="memberful-paywall__subheading">%2$s', tag_escape( $tag ), @@ -257,4 +253,4 @@ private static function check_icon(): string { . '' . ''; } -} \ No newline at end of file +} diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php index 82eec48e..e6e17384 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php @@ -18,11 +18,11 @@ public static function sanitize( array $input, array $defaults ): array { $clean = $defaults; $enums = array( - 'mode' => array( 'builder', 'custom_html' ), - 'layout' => array( 'simple', 'card', 'banner' ), - 'heading_tag' => array( 'h1', 'h2', 'h3' ), - 'subheading_tag' => array( 'p', 'h3', 'h4' ), - 'button_shape' => array( 'pill', 'rounded', 'square' ), + 'mode' => Memberful_Paywall_Config::MODES, + 'layout' => Memberful_Paywall_Config::LAYOUTS, + 'heading_tag' => Memberful_Paywall_Config::HEADING_TAGS, + 'subheading_tag' => Memberful_Paywall_Config::SUBHEADING_TAGS, + 'button_shape' => Memberful_Paywall_Config::BUTTON_SHAPES, ); foreach ( $enums as $key => $allowed ) { diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css index 314e67b2..45cce5d4 100755 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css @@ -270,3 +270,38 @@ Ad Provider Settings .memberful-ad-provider-settings > div { margin-left: 1rem; } + +/*--------------------------------------------------------- +Paywall Builder +------------------------------------------------------------ */ +.memberful-paywall-builder { + border-top: 1px solid #dcdcde; + margin-top: 2rem; + padding-top: 1.5rem; +} +.memberful-paywall-builder__mode { + margin-bottom: 1rem; +} +.memberful-paywall-builder__mode label { + margin-right: 1.25rem; +} +.memberful-paywall-builder__legend { + font-weight: 600; + margin-bottom: 0.35rem; +} +.memberful-paywall-builder__layout { + margin: 1rem 0; +} +.memberful-paywall-builder__layout label { + margin-right: 1.25rem; +} +.memberful-paywall-builder__fields th { + width: 160px; +} +.memberful-paywall-builder__fields .regular-text, +.memberful-paywall-builder__fields .large-text { + max-width: 480px; +} +.memberful-paywall-builder__fields select { + margin-left: 0.5rem; +} diff --git a/wordpress/wp-content/plugins/memberful-wp/views/global_marketing.php b/wordpress/wp-content/plugins/memberful-wp/views/global_marketing.php index 7b8225d3..4e647f30 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/global_marketing.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/global_marketing.php @@ -1,72 +1,66 @@ -
    - 'global_marketing' ) ); ?> - - -
    - + -

    -

    - -

    -
    - - -
    +?> +
    + 'global_marketing' ) ); ?> + -
    - -
    +
    +

    +

    + +

    -
    +
    + + +
    +
    + +
    +
    -
    - +
    + $paywall_config ) ); ?> + $paywall_config ) ); ?> + $global_marketing_content ) ); ?> +
    +
    -
    -
    -
    - -
    - -
    -
    + + + \ No newline at end of file diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php new file mode 100644 index 00000000..b74073a4 --- /dev/null +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -0,0 +1,113 @@ + __( 'Simple', 'memberful' ), + 'card' => __( 'Card', 'memberful' ), + 'banner' => __( 'Banner', 'memberful' ), +); + +$button_shape_labels = array( + 'pill' => __( 'Pill', 'memberful' ), + 'rounded' => __( 'Rounded', 'memberful' ), + 'square' => __( 'Square', 'memberful' ), +); + +$features_textarea = implode( "\n", (array) $paywall_config['features'] ); +?> +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/custom-html-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/custom-html-panel.php new file mode 100644 index 00000000..f3645318 --- /dev/null +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/custom-html-panel.php @@ -0,0 +1,13 @@ + +
    + +
    \ No newline at end of file diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php new file mode 100644 index 00000000..a1cbf461 --- /dev/null +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php @@ -0,0 +1,21 @@ + +
    + + + +
    diff --git a/wordpress/wp-content/plugins/memberful-wp/webpack.config.js b/wordpress/wp-content/plugins/memberful-wp/webpack.config.js index b32e4222..9a57145c 100644 --- a/wordpress/wp-content/plugins/memberful-wp/webpack.config.js +++ b/wordpress/wp-content/plugins/memberful-wp/webpack.config.js @@ -5,5 +5,6 @@ module.exports = { entry: { ...defaultConfig.entry, "editor-scripts": "./js/src/editor-scripts.js", + "paywall-builder": "./js/src/paywall-builder.js", }, }; From dc8d45693581982bddb3aa9d31de7efde7265c9a Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Tue, 21 Apr 2026 18:25:30 +1000 Subject: [PATCH 05/28] Fix phpcs warnings --- .../wp-content/plugins/memberful-wp/src/paywall/config.php | 4 ++++ .../plugins/memberful-wp/src/paywall/renderer.php | 2 +- .../plugins/memberful-wp/src/paywall/sanitizer.php | 6 +++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php index 6d099ef0..7a1ad9b7 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php @@ -4,6 +4,10 @@ * * @package memberful-wp */ + +/** + * Class Memberful_Paywall_Config + */ class Memberful_Paywall_Config { const OPTION_KEY = 'memberful_paywall_config'; diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php index 91e5c3af..0e82aa72 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php @@ -1,6 +1,6 @@ Date: Wed, 22 Apr 2026 11:22:16 +1000 Subject: [PATCH 06/28] Boilerplate paywal preview --- .../memberful-wp/js/src/paywall-builder.js | 107 +++++++++-- .../plugins/memberful-wp/src/admin.php | 40 +++-- .../plugins/memberful-wp/src/paywall.php | 8 +- .../memberful-wp/src/paywall/preview.php | 84 +++++++++ .../memberful-wp/stylesheets/paywall.css | 166 ++++++++++++++++++ .../views/paywall/builder-panel.php | 10 ++ 6 files changed, 390 insertions(+), 25 deletions(-) create mode 100644 wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php create mode 100644 wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css diff --git a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js index 21d34b36..99e49d68 100644 --- a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js +++ b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js @@ -1,12 +1,24 @@ -/** - * Paywall builder admin script. - */ - jQuery(function ($) { - $('.memberful-paywall-builder__color').wpColorPicker(); - + const $form = $('.memberful-paywall-builder__panel[data-panel="builder"]'); const $modeInputs = $('input[name="memberful_paywall[mode]"]'); - const $panels = $('.memberful-paywall-builder__panel'); + const $panels = $('.memberful-paywall-builder__panel'); + const $preview = $('#mf-paywall-preview'); + const $colorInput = $('.memberful-paywall-builder__color'); + + const preview = window.memberfulPaywallPreview || {}; + const DEBOUNCE_MS = 250; + + let debounceTimer = null; + let requestSeq = 0; + + $colorInput.wpColorPicker({ + change: function () { + setTimeout(scheduleRefresh, 0); + }, + clear: function () { + setTimeout(scheduleRefresh, 0); + }, + }); function applyMode(mode) { $panels.each(function () { @@ -15,10 +27,85 @@ jQuery(function ($) { } $modeInputs.on('change', function () { - if (this.checked) { - applyMode(this.value); + if (!this.checked) { + return; } + + applyMode(this.value); + refreshPreview(); }); applyMode($modeInputs.filter(':checked').val() || 'builder'); -}); \ No newline at end of file + + function collectConfig() { + return { + mode: $('input[name="memberful_paywall[mode]"]:checked').val() || 'builder', + layout: $('input[name="memberful_paywall[layout]"]:checked').val() || 'card', + heading: $('#memberful-paywall-heading').val() || '', + heading_tag: $('#memberful-paywall-heading-tag').val() || 'h2', + subheading: $('#memberful-paywall-subheading').val() || '', + subheading_tag: $('#memberful-paywall-subheading-tag').val() || 'p', + features: $('#memberful-paywall-features').val() || '', + button_label: $('#memberful-paywall-button-label').val() || '', + subscribe_url: $('#memberful-paywall-subscribe-url').val() || '', + sign_in_url: $('#memberful-paywall-signin-url').val() || '', + brand_color: $colorInput.val() || '', + button_shape: $('#memberful-paywall-button-shape').val() || 'rounded', + custom_css: $('#memberful-paywall-custom-css').val() || '', + }; + } + + function refreshPreview() { + if (!preview.ajaxUrl || !preview.action || !preview.nonce || !$preview.length) { + return; + } + + clearTimeout(debounceTimer); + + const seq = ++requestSeq; + const body = new URLSearchParams(); + body.set('action', preview.action); + body.set('nonce', preview.nonce); + + const config = collectConfig(); + Object.keys(config).forEach(function (key) { + body.append('config[' + key + ']', config[key]); + }); + + fetch(preview.ajaxUrl, { + method: 'POST', + credentials: 'same-origin', + headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, + body: body.toString(), + }) + .then(function (res) { + if (!res.ok) { + throw new Error('Request failed: ' + res.status); + } + + return res.json(); + }) + .then(function (json) { + if (seq !== requestSeq) { + return; + } + + if (json && json.success && json.data && json.data.html) { + $preview.attr('srcdoc', json.data.html); + } + }) + .catch(function (err) { + console.error('Paywall preview failed', err); + }); + } + + function scheduleRefresh() { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(refreshPreview, DEBOUNCE_MS); + } + + $form.on('input', 'input[type="text"], input[type="url"], textarea', scheduleRefresh); + $form.on('change', 'input[type="radio"], select', refreshPreview); + + refreshPreview(); +}); diff --git a/wordpress/wp-content/plugins/memberful-wp/src/admin.php b/wordpress/wp-content/plugins/memberful-wp/src/admin.php index 04cf6ff8..219462a9 100755 --- a/wordpress/wp-content/plugins/memberful-wp/src/admin.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/admin.php @@ -127,19 +127,33 @@ function memberful_wp_admin_enqueue_scripts() { ); } - if ( - 'memberful_options' === filter_input( INPUT_GET, 'page' ) - && 'global_marketing' === filter_input( INPUT_GET, 'subpage' ) - ) { - wp_enqueue_style( 'wp-color-picker' ); - wp_enqueue_script( - 'memberful-paywall-builder', - MEMBERFUL_URL . '/js/build/paywall-builder.js', - array( 'jquery', 'wp-color-picker' ), - MEMBERFUL_VERSION, - true - ); - } + if ( + 'memberful_options' === filter_input( INPUT_GET, 'page' ) + && 'global_marketing' === filter_input( INPUT_GET, 'subpage' ) + ) { + wp_enqueue_style( 'wp-color-picker' ); + + wp_enqueue_style( + 'memberful-paywall', + MEMBERFUL_URL . '/stylesheets/paywall.css', + array(), + MEMBERFUL_VERSION + ); + + wp_enqueue_script( + 'memberful-paywall-builder', + MEMBERFUL_URL . '/js/build/paywall-builder.js', + array( 'jquery', 'wp-color-picker' ), + MEMBERFUL_VERSION, + true + ); + + wp_localize_script( + 'memberful-paywall-builder', + 'memberfulPaywallPreview', + Memberful_Paywall_Preview::script_args() + ); + } wp_enqueue_script( 'memberful-menu', diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php index b2d46855..366ac454 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php @@ -1,10 +1,14 @@ 'forbidden' ), 403 ); + } + + $raw = filter_input( INPUT_POST, 'config', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); + $raw = is_array( $raw ) ? $raw : array(); + + $config = Memberful_Paywall_Sanitizer::sanitize( $raw, Memberful_Paywall_Config::defaults() ); + + wp_send_json_success( array( 'html' => self::document( $config ) ) ); + } + + /** + * Wrap the rendered paywall HTML in a minimal document suitable for an iframe. + * + * @param array $config Sanitized config. + * + * @return string + */ + public static function document( array $config ): string { + $body = Memberful_Paywall_Renderer::render( $config ); + + $paywall_css = plugins_url( 'stylesheets/paywall.css', MEMBERFUL_PLUGIN_FILE ); + $theme_css = get_stylesheet_uri(); + + $links = sprintf( '', esc_url( $paywall_css ) ); + if ( ! empty( $theme_css ) ) { + $links .= sprintf( '', esc_url( $theme_css ) ); + } + + return '' + . '' + . '' + . '' + . '' + . $links + . '' + . '' + . '' . $body . '' + . ''; + } + + /** + * AJAX args passed to the builder JS via wp_localize_script. + * + * @return array + */ + public static function script_args(): array { + return array( + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'action' => self::ACTION, + 'nonce' => wp_create_nonce( self::NONCE_KEY ), + ); + } +} + +Memberful_Paywall_Preview::register(); diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css new file mode 100644 index 00000000..0f7ed2ed --- /dev/null +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css @@ -0,0 +1,166 @@ +/** + * Memberful paywall — frontend + admin preview styles. + */ + +.memberful-paywall { + --mf-brand: #2f80ed; + --mf-radius: 8px; + --mf-text: #1b1b1b; + --mf-muted: #555; + --mf-surface: #fff; + --mf-border: rgba(0, 0, 0, 0.08); + + box-sizing: border-box; + color: var(--mf-text); + font-family: inherit; + line-height: 1.5; + margin: 1.5em auto; + max-width: 640px; + padding: 2rem; +} + +.memberful-paywall *, +.memberful-paywall *::before, +.memberful-paywall *::after { + box-sizing: border-box; +} + +.memberful-paywall__heading { + font-weight: 700; + line-height: 1.2; + margin: 0 0 0.5rem; +} + +.memberful-paywall__subheading { + color: var(--mf-muted); + margin: 0 0 1.25rem; +} + +.memberful-paywall__features { + list-style: none; + margin: 0 0 1.5rem; + padding: 0; +} + +.memberful-paywall__features li { + align-items: flex-start; + display: flex; + gap: 0.5rem; + padding: 0.25rem 0; +} + +.memberful-paywall__check { + color: var(--mf-brand); + flex: 0 0 auto; + margin-top: 0.2rem; +} + +.memberful-paywall__actions { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} + +.memberful-paywall__button { + border-radius: var(--mf-radius); + display: inline-block; + font-weight: 600; + line-height: 1.2; + padding: 0.6rem 1.25rem; + text-decoration: none; + transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease; +} + +.memberful-paywall__button--primary { + background: var(--mf-brand); + color: #fff; + border: 1px solid var(--mf-brand); +} + +.memberful-paywall__button--primary:hover, +.memberful-paywall__button--primary:focus { + filter: brightness(0.92); +} + +.memberful-paywall__button--secondary { + background: transparent; + border: 1px solid var(--mf-border); + color: var(--mf-brand); +} + +.memberful-paywall__button--secondary:hover, +.memberful-paywall__button--secondary:focus { + border-color: var(--mf-brand); +} + +/* Simple layout - left aligned stack, no surface. */ +.memberful-paywall--simple { + text-align: left; +} + +/* Card layout - centred card with border and shadow. */ +.memberful-paywall--card { + text-align: center; +} + +.memberful-paywall--card .memberful-paywall__card { + background: var(--mf-surface); + border: 1px solid var(--mf-border); + border-radius: calc(var(--mf-radius) + 4px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); + padding: 2rem; +} + +.memberful-paywall--card .memberful-paywall__features { + display: inline-block; + text-align: left; +} + +.memberful-paywall--card .memberful-paywall__actions { + justify-content: center; +} + +/* Banner layout - heading/features on the left, CTA on the right. */ +.memberful-paywall--banner { + align-items: center; + background: var(--mf-surface); + border: 1px solid var(--mf-border); + border-radius: var(--mf-radius); + display: flex; + gap: 1.5rem; + max-width: none; + padding: 1.5rem 2rem; +} + +.memberful-paywall--banner .memberful-paywall__banner-text { + flex: 1 1 auto; + min-width: 0; +} + +.memberful-paywall--banner .memberful-paywall__heading { + font-size: 1.15rem; +} + +.memberful-paywall--banner .memberful-paywall__subheading { + margin-bottom: 0; +} + +.memberful-paywall--banner .memberful-paywall__actions { + flex: 0 0 auto; +} + +@media (max-width: 768px) { + .memberful-paywall--banner { + align-items: stretch; + flex-direction: column; + } + + .memberful-paywall--banner .memberful-paywall__features { + display: none; + } + + .memberful-paywall--banner .memberful-paywall__actions { + justify-content: center; + } +} \ No newline at end of file diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php index b74073a4..2244d86e 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -110,4 +110,14 @@ + +
    +

    + +
    From 28a92308e111e6e31a8e9fe7b8ed157eed35eac0 Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Wed, 22 Apr 2026 12:20:57 +1000 Subject: [PATCH 07/28] Drop custom CSS field from the builder --- .../memberful-wp/js/src/paywall-builder.js | 1 - .../memberful-wp/src/paywall/config.php | 1 - .../memberful-wp/src/paywall/renderer.php | 20 ++----------------- .../memberful-wp/src/paywall/sanitizer.php | 4 ---- .../views/paywall/builder-panel.php | 7 ------- 5 files changed, 2 insertions(+), 31 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js index 99e49d68..e1aa211a 100644 --- a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js +++ b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js @@ -51,7 +51,6 @@ jQuery(function ($) { sign_in_url: $('#memberful-paywall-signin-url').val() || '', brand_color: $colorInput.val() || '', button_shape: $('#memberful-paywall-button-shape').val() || 'rounded', - custom_css: $('#memberful-paywall-custom-css').val() || '', }; } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php index 7a1ad9b7..243f31ef 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php @@ -36,7 +36,6 @@ public static function defaults(): array { 'sign_in_url' => '', 'brand_color' => '#2f80ed', 'button_shape' => 'rounded', - 'custom_css' => '', ); } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php index 0e82aa72..e5c31bf1 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php @@ -29,11 +29,10 @@ public static function render( array $config ): string { $body = self::$method( $config ); return sprintf( - '
    %s
    %s', + '
    %s
    ', esc_attr( $layout ), esc_attr( self::wrapper_style( $config ) ), - $body, - self::custom_css_block( $config ) + $body ); } @@ -206,21 +205,6 @@ private static function button_radius( string $shape ): string { } } - /** - * Custom CSS '; - } - /** * Resolve the subscribe URL, falling back to the Memberful registration page. * diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php index 1304890b..9332dfa9 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php @@ -66,10 +66,6 @@ public static function sanitize( array $input, array $defaults ): array { } } - if ( isset( $input['custom_css'] ) ) { - $clean['custom_css'] = wp_strip_all_tags( (string) $input['custom_css'] ); - } - return $clean; } } diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php index 2244d86e..7ffb07a3 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -101,13 +101,6 @@ - - - - -

    - - From e8a69d869c1cdb594ad6320c6f362edf1c240e9a Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Wed, 22 Apr 2026 13:20:20 +1000 Subject: [PATCH 08/28] Adjust UI/UX --- .../memberful-wp/js/src/paywall-builder.js | 1 - .../memberful-wp/src/paywall/config.php | 32 ++- .../memberful-wp/src/paywall/renderer.php | 6 +- .../memberful-wp/src/paywall/sanitizer.php | 9 +- .../memberful-wp/stylesheets/admin.css | 232 ++++++++++++++++-- .../memberful-wp/views/global_marketing.php | 2 +- .../views/paywall/builder-panel.php | 202 ++++++++------- .../memberful-wp/views/paywall/mode-radio.php | 22 +- 8 files changed, 358 insertions(+), 148 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js index e1aa211a..75d2d89a 100644 --- a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js +++ b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js @@ -44,7 +44,6 @@ jQuery(function ($) { heading: $('#memberful-paywall-heading').val() || '', heading_tag: $('#memberful-paywall-heading-tag').val() || 'h2', subheading: $('#memberful-paywall-subheading').val() || '', - subheading_tag: $('#memberful-paywall-subheading-tag').val() || 'p', features: $('#memberful-paywall-features').val() || '', button_label: $('#memberful-paywall-button-label').val() || '', subscribe_url: $('#memberful-paywall-subscribe-url').val() || '', diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php index 243f31ef..8a38b2df 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php @@ -11,11 +11,10 @@ class Memberful_Paywall_Config { const OPTION_KEY = 'memberful_paywall_config'; - const MODES = array( 'builder', 'custom_html' ); - const LAYOUTS = array( 'simple', 'card', 'banner' ); - const HEADING_TAGS = array( 'h1', 'h2', 'h3' ); - const SUBHEADING_TAGS = array( 'p', 'h3', 'h4' ); - const BUTTON_SHAPES = array( 'pill', 'rounded', 'square' ); + const MODES = array( 'builder', 'custom_html' ); + const LAYOUTS = array( 'simple', 'card', 'banner' ); + const HEADING_TAGS = array( 'h1', 'h2', 'h3' ); + const BUTTON_SHAPES = array( 'pill', 'rounded', 'square' ); /** * Canonical default configuration shape. @@ -24,18 +23,17 @@ class Memberful_Paywall_Config { */ public static function defaults(): array { return array( - 'mode' => 'builder', - 'layout' => 'card', - 'heading' => esc_html__( 'Subscribe to keep reading', 'memberful' ), - 'heading_tag' => 'h2', - 'subheading' => esc_html__( 'This post is for paying subscribers.', 'memberful' ), - 'subheading_tag' => 'p', - 'features' => array(), - 'button_label' => esc_html__( 'Subscribe', 'memberful' ), - 'subscribe_url' => '', - 'sign_in_url' => '', - 'brand_color' => '#2f80ed', - 'button_shape' => 'rounded', + 'mode' => 'builder', + 'layout' => 'card', + 'heading' => esc_html__( 'Subscribe to keep reading', 'memberful' ), + 'heading_tag' => 'h2', + 'subheading' => esc_html__( 'This post is for paying subscribers.', 'memberful' ), + 'features' => array(), + 'button_label' => esc_html__( 'Subscribe', 'memberful' ), + 'subscribe_url' => '', + 'sign_in_url' => '', + 'brand_color' => '#2f80ed', + 'button_shape' => 'rounded', ); } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php index e5c31bf1..63fbcae3 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php @@ -102,7 +102,7 @@ private static function heading_block( array $config ): string { } /** - * Subheading element with the configured tag, or empty when blank. + * Subheading paragraph, or empty when blank. * * @param array $config Sanitized config. * @@ -113,10 +113,8 @@ private static function subheading_block( array $config ): string { return ''; } - $tag = in_array( $config['subheading_tag'], Memberful_Paywall_Config::SUBHEADING_TAGS, true ) ? $config['subheading_tag'] : 'p'; return sprintf( - '<%1$s class="memberful-paywall__subheading">%2$s', - tag_escape( $tag ), + '

    %s

    ', esc_html( $config['subheading'] ) ); } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php index 9332dfa9..32a7eb2b 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php @@ -22,11 +22,10 @@ public static function sanitize( array $input, array $defaults ): array { $clean = $defaults; $enums = array( - 'mode' => Memberful_Paywall_Config::MODES, - 'layout' => Memberful_Paywall_Config::LAYOUTS, - 'heading_tag' => Memberful_Paywall_Config::HEADING_TAGS, - 'subheading_tag' => Memberful_Paywall_Config::SUBHEADING_TAGS, - 'button_shape' => Memberful_Paywall_Config::BUTTON_SHAPES, + 'mode' => Memberful_Paywall_Config::MODES, + 'layout' => Memberful_Paywall_Config::LAYOUTS, + 'heading_tag' => Memberful_Paywall_Config::HEADING_TAGS, + 'button_shape' => Memberful_Paywall_Config::BUTTON_SHAPES, ); foreach ( $enums as $key => $allowed ) { diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css index 45cce5d4..4e816a79 100755 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css @@ -274,34 +274,234 @@ Ad Provider Settings /*--------------------------------------------------------- Paywall Builder ------------------------------------------------------------ */ +.memberful-bulk-apply-box--wide { + max-width: none; +} .memberful-paywall-builder { border-top: 1px solid #dcdcde; margin-top: 2rem; padding-top: 1.5rem; } +.memberful-paywall-builder__section-heading { + display: block; + font-size: 13px; + font-weight: 600; + margin: 0 0 0.75rem; + padding: 0; + text-transform: none; +} + +/* Content source: tab-style segmented control */ .memberful-paywall-builder__mode { - margin-bottom: 1rem; + border: 0; + margin: 0 0 1.5rem; + padding: 0; } -.memberful-paywall-builder__mode label { - margin-right: 1.25rem; +.memberful-paywall-builder__mode-tabs { + background: #fff; + border: 1px solid #c3c4c7; + border-radius: 4px; + display: inline-flex; + overflow: hidden; } -.memberful-paywall-builder__legend { - font-weight: 600; - margin-bottom: 0.35rem; +.memberful-paywall-builder__mode-tab { + cursor: pointer; + margin: 0; + position: relative; +} +.memberful-paywall-builder__mode-tab input[type="radio"] { + opacity: 0; + pointer-events: none; + position: absolute; +} +.memberful-paywall-builder__mode-tab span { + background: #fff; + border-right: 1px solid #c3c4c7; + color: #2c3338; + display: inline-block; + font-size: 13px; + font-weight: 500; + line-height: 1.4; + padding: 8px 16px; +} +.memberful-paywall-builder__mode-tab:last-child span { + border-right: 0; +} +.memberful-paywall-builder__mode-tab input[type="radio"]:checked + span { + background: #2271b1; + color: #fff; } +.memberful-paywall-builder__mode-tab input[type="radio"]:focus-visible + span { + box-shadow: inset 0 0 0 2px #2271b1; +} + +/* Template picker cards */ .memberful-paywall-builder__layout { - margin: 1rem 0; + border: 0; + margin: 0 0 1.75rem; + padding: 0; +} +.memberful-paywall-builder__template-grid { + display: grid; + gap: 16px; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} +.memberful-paywall-builder__template-card { + cursor: pointer; + display: block; + margin: 0; + position: relative; +} +.memberful-paywall-builder__template-card input[type="radio"] { + opacity: 0; + pointer-events: none; + position: absolute; +} +.memberful-paywall-builder__template-card-inner { + background: #fff; + border: 2px solid #dcdcde; + border-radius: 6px; + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + transition: border-color 120ms ease, box-shadow 120ms ease; +} +.memberful-paywall-builder__template-card:hover .memberful-paywall-builder__template-card-inner { + border-color: #8c8f94; +} +.memberful-paywall-builder__template-card input[type="radio"]:checked + .memberful-paywall-builder__template-card-inner { + border-color: #2271b1; + box-shadow: 0 0 0 1px #2271b1; +} +.memberful-paywall-builder__template-card input[type="radio"]:checked + .memberful-paywall-builder__template-card-inner::after { + background: #2271b1 url("data:image/svg+xml;utf8,") center/12px no-repeat; + border-radius: 50%; + content: ""; + height: 20px; + position: absolute; + right: 10px; + top: 10px; + width: 20px; +} +.memberful-paywall-builder__template-card input[type="radio"]:focus-visible + .memberful-paywall-builder__template-card-inner { + box-shadow: 0 0 0 2px #2271b1; } -.memberful-paywall-builder__layout label { - margin-right: 1.25rem; + +.memberful-paywall-builder__template-thumb { + align-items: center; + background: #f0f0f1; + border-bottom: 1px solid #dcdcde; + display: flex; + flex-direction: column; + gap: 6px; + height: 96px; + justify-content: center; + position: relative; +} +.memberful-paywall-builder__template-thumb--simple { + background: #f6f7f7; +} +.memberful-paywall-builder__template-thumb--card { + background: #f0f0f1; +} +.memberful-paywall-builder__template-thumb--banner { + background: #1d2327; +} +.memberful-paywall-builder__template-thumb--banner .memberful-paywall-builder__thumb-line { + background: #50575e; +} +.memberful-paywall-builder__thumb-line { + background: #c3c4c7; + border-radius: 2px; + height: 3px; + width: 100px; +} +.memberful-paywall-builder__thumb-button { + background: #2271b1; + border-radius: 2px; + height: 10px; + width: 48px; +} +.memberful-paywall-builder__thumb-lock { + background: #2271b1 url("data:image/svg+xml;utf8,") center/18px no-repeat; + border-radius: 50%; + height: 26px; + width: 26px; +} + +.memberful-paywall-builder__template-meta { + display: block; + padding: 10px 12px 12px; +} +.memberful-paywall-builder__template-meta strong { + color: #1d2327; + display: block; + font-size: 13px; + margin-bottom: 2px; +} +.memberful-paywall-builder__template-meta small { + color: #646970; + display: block; + font-size: 12px; + line-height: 1.35; +} + +/* Two-column split: customize | preview */ +.memberful-paywall-builder__split { + align-items: start; + display: grid; + gap: 32px; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); +} +@media (max-width: 900px) { + .memberful-paywall-builder__split { + grid-template-columns: minmax(0, 1fr); + } +} + +.memberful-paywall-builder__customize .memberful-paywall-builder__field { + display: block; + margin: 0 0 1rem; +} +.memberful-paywall-builder__customize label { + display: block; + font-size: 13px; + font-weight: 600; + margin-bottom: 4px; +} +.memberful-paywall-builder__customize input[type="text"], +.memberful-paywall-builder__customize input[type="url"], +.memberful-paywall-builder__customize textarea { + box-sizing: border-box; + max-width: 100%; + width: 100%; +} +.memberful-paywall-builder__customize .description { + color: #646970; + display: block; + font-size: 12px; + margin-top: 4px; } -.memberful-paywall-builder__fields th { - width: 160px; +.memberful-paywall-builder__customize .memberful-paywall-builder__field--paired { + align-items: start; + display: grid; + gap: 12px; + grid-template-columns: 1fr auto; + margin: 0 0 1rem; +} +.memberful-paywall-builder__field--paired .memberful-paywall-builder__field-main, +.memberful-paywall-builder__field--paired .memberful-paywall-builder__field-aside { + margin: 0; } -.memberful-paywall-builder__fields .regular-text, -.memberful-paywall-builder__fields .large-text { - max-width: 480px; +.memberful-paywall-builder__field--paired .memberful-paywall-builder__field-aside select { + min-width: 80px; } -.memberful-paywall-builder__fields select { - margin-left: 0.5rem; + +.memberful-paywall-builder__preview-frame { + background: #fff; + border: 1px solid #dcdcde; + border-radius: 4px; + min-height: 480px; + width: 100%; } diff --git a/wordpress/wp-content/plugins/memberful-wp/views/global_marketing.php b/wordpress/wp-content/plugins/memberful-wp/views/global_marketing.php index 4e647f30..abb195f4 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/global_marketing.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/global_marketing.php @@ -20,7 +20,7 @@
    -
    +

    - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

    +
    + +

    + + + +

    + +

    + + +

    + +

    + + + +

    + +

    + + + +

    +
    -
    -

    - +
    +

    + +
    -
    + \ No newline at end of file diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php index a1cbf461..68f26ee9 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php @@ -9,13 +9,15 @@ ?>
    - - - -
    + +
    + + +
    + \ No newline at end of file From 03db76cf61c12975e9dc19de9bccafccd2bef10c Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Wed, 22 Apr 2026 16:22:26 +1000 Subject: [PATCH 09/28] Save work in progress --- .../memberful-wp/src/paywall/config.php | 4 +- .../memberful-wp/src/paywall/preview.php | 10 +- .../memberful-wp/src/paywall/renderer.php | 117 ++++++++++--- .../memberful-wp/stylesheets/admin.css | 2 +- .../memberful-wp/stylesheets/paywall.css | 158 ++++++++++-------- .../views/paywall/builder-panel.php | 2 +- 6 files changed, 196 insertions(+), 97 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php index 8a38b2df..62ad4889 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php @@ -32,8 +32,8 @@ public static function defaults(): array { 'button_label' => esc_html__( 'Subscribe', 'memberful' ), 'subscribe_url' => '', 'sign_in_url' => '', - 'brand_color' => '#2f80ed', - 'button_shape' => 'rounded', + 'brand_color' => '', + 'button_shape' => 'square', ); } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php index 49f54334..1ea186da 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php @@ -55,15 +55,21 @@ public static function document( array $config ): string { $links .= sprintf( '', esc_url( $theme_css ) ); } + $teaser = ''; + + $styles = 'html,body{margin:0;background:#fff;color:#1b1b1b;}' + . '.mf-preview-teaser{font-size:16px;line-height:1.6;padding:24px 24px 0;}' + . '.mf-preview-teaser p{margin:0 0 1em;}'; + return '' . '' . '' . '' . '' . $links - . '' + . '' . '' - . '' . $body . '' + . '' . $teaser . $body . '' . ''; } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php index 63fbcae3..e708c8c7 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php @@ -13,6 +13,54 @@ * Renders a paywall config array to HTML. */ class Memberful_Paywall_Renderer { + /** + * Flag to determine if we should load paywall styles. + * + * @var bool + */ + private static $should_print_styles = false; + + /** + * Register the actions on plugin load. + */ + public static function register(): void { + add_filter( 'memberful_wp_protect_content', array( __CLASS__, 'protect_content' ) ); + add_action( 'wp_footer', array( __CLASS__, 'maybe_print_styles' ) ); + } + + /** + * Conditionally flag paywall loading. + * + * @param string $content Content. + * + * @return string + */ + public static function protect_content( string $content ): string { + $config = Memberful_Paywall_Config::get(); + + if ( 'builder' === $config['mode'] ) { + self::$should_print_styles = true; + } + + return $content; + } + + /** + * Render paywall styles. + */ + public static function maybe_print_styles(): void { + if ( ! self::$should_print_styles ) { + return; + } + + wp_enqueue_style( + 'memberful-paywall', + MEMBERFUL_URL . '/stylesheets/paywall.css', + array(), + MEMBERFUL_VERSION + ); + } + /** * Render a paywall config to HTML. * @@ -26,63 +74,66 @@ public static function render( array $config ): string { $layout = in_array( $config['layout'], Memberful_Paywall_Config::LAYOUTS, true ) ? $config['layout'] : 'card'; $method = 'render_' . $layout; - $body = self::$method( $config ); - return sprintf( - '
    %s
    ', + '
    %3$s
    ', esc_attr( $layout ), esc_attr( self::wrapper_style( $config ) ), - $body + self::$method( $config ) ); } /** - * Render the "simple" layout. + * Render the "simple" layout — minimal text + CTA on a transparent band. * * @param array $config Sanitized config. * * @return string */ private static function render_simple( array $config ): string { - return self::heading_block( $config ) + return '
    ' + . self::heading_block( $config ) . self::subheading_block( $config ) . self::features_block( $config ) - . '
    ' . self::primary_cta( $config ) . '
    '; + . '
    ' . self::primary_cta( $config ) . '
    ' + . self::sign_in_prompt( $config ) + . '
    '; } /** - * Render the "card" layout. + * Render the "card" layout — centred white card with lock badge. * * @param array $config Sanitized config. * * @return string */ private static function render_card( array $config ): string { - return '
    ' + return '
    ' + . '
    ' + . self::lock_badge() . self::heading_block( $config ) . self::subheading_block( $config ) . self::features_block( $config ) - . '
    ' - . self::primary_cta( $config ) - . self::secondary_cta( $config ) + . '
    ' . self::primary_cta( $config ) . '
    ' + . self::sign_in_prompt( $config ) . '
    ' . '
    '; } /** - * Render the "banner" layout. + * Render the "banner" layout — full-width dark band. * * @param array $config Sanitized config. * * @return string */ private static function render_banner( array $config ): string { - return '
    ' + return '
    ' . self::heading_block( $config ) . self::subheading_block( $config ) . self::features_block( $config ) - . '
    ' - . '
    ' . self::primary_cta( $config ) . '
    '; + . '
    ' . self::primary_cta( $config ) . '
    ' + . self::sign_in_prompt( $config ) + . '
    '; } /** @@ -155,20 +206,34 @@ private static function primary_cta( array $config ): string { } /** - * Secondary sign-in CTA anchor (card layout only). + * "Already a subscriber? Sign in" text prompt shown under every layout's CTA. * * @param array $config Sanitized config. * * @return string */ - private static function secondary_cta( array $config ): string { + private static function sign_in_prompt( array $config ): string { return sprintf( - '%s', + '', + esc_html__( 'Already a subscriber?', 'memberful' ), esc_url( self::sign_in_url( $config ) ), esc_html__( 'Sign in', 'memberful' ) ); } + /** + * Circular lock badge shown at the top of the card layout. + * + * @return string + */ + private static function lock_badge(): string { + return ''; + } + /** * Wrapper inline style carrying the brand colour and button radius custom properties. * @@ -177,11 +242,13 @@ private static function secondary_cta( array $config ): string { * @return string */ private static function wrapper_style( array $config ): string { - return sprintf( - '--mf-brand:%s;--mf-radius:%s;', - $config['brand_color'], - self::button_radius( $config['button_shape'] ) - ); + $parts = array( '--mf-radius:' . self::button_radius( $config['button_shape'] ) ); + + if ( ! empty( $config['brand_color'] ) ) { + $parts[] = '--mf-brand:' . $config['brand_color']; + } + + return implode( ';', $parts ) . ';'; } /** @@ -236,3 +303,5 @@ private static function check_icon(): string { . ''; } } + +Memberful_Paywall_Renderer::register(); diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css index 4e816a79..4ead8d7d 100755 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css @@ -502,6 +502,6 @@ Paywall Builder background: #fff; border: 1px solid #dcdcde; border-radius: 4px; - min-height: 480px; + min-height: 520px; width: 100%; } diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css index 0f7ed2ed..3b2a02b6 100644 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css @@ -3,8 +3,8 @@ */ .memberful-paywall { - --mf-brand: #2f80ed; - --mf-radius: 8px; + --mf-brand: var(--wp--preset--color--primary, var(--wp--preset--color--accent, var(--wp-admin-theme-color, #1f2933))); + --mf-radius: 0; --mf-text: #1b1b1b; --mf-muted: #555; --mf-surface: #fff; @@ -14,9 +14,10 @@ color: var(--mf-text); font-family: inherit; line-height: 1.5; - margin: 1.5em auto; - max-width: 640px; - padding: 2rem; + margin: 0; + padding: 0; + position: relative; + width: 100%; } .memberful-paywall *, @@ -25,28 +26,55 @@ box-sizing: border-box; } +/* Shared inner column that constrains content width inside full-bleed layouts. */ +.memberful-paywall__inner { + margin: 0 auto; + max-width: 600px; + padding: 2rem 1.5rem 2.5rem; + text-align: center; +} + .memberful-paywall__heading { font-weight: 700; line-height: 1.2; - margin: 0 0 0.5rem; + margin: 0 0 0.75rem; +} + +h2.memberful-paywall__heading { + font-size: 2rem; +} + +h2.memberful-paywall__heading { + font-size: 1.5rem; +} + +h3.memberful-paywall__heading { + font-size: 1.175rem; } .memberful-paywall__subheading { color: var(--mf-muted); - margin: 0 0 1.25rem; + font-size: 1rem; + margin: 0 0 1.5rem; } .memberful-paywall__features { + color: var(--mf-muted); + column-gap: 1.5rem; + display: inline-grid; + font-size: 1rem; + grid-template-columns: repeat(2, auto); list-style: none; margin: 0 0 1.5rem; padding: 0; + row-gap: 0.25rem; + text-align: left; } .memberful-paywall__features li { align-items: flex-start; display: flex; gap: 0.5rem; - padding: 0.25rem 0; } .memberful-paywall__check { @@ -60,22 +88,26 @@ display: flex; flex-wrap: wrap; gap: 0.75rem; + justify-content: center; + margin-bottom: 1rem; } .memberful-paywall__button { + border: 1px solid transparent; border-radius: var(--mf-radius); display: inline-block; + font-size: 1rem; font-weight: 600; line-height: 1.2; - padding: 0.6rem 1.25rem; + padding: 0.85rem 1.75rem; text-decoration: none; - transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease; + transition: background-color 150ms ease, color 150ms ease, filter 150ms ease; } .memberful-paywall__button--primary { background: var(--mf-brand); + border-color: var(--mf-brand); color: #fff; - border: 1px solid var(--mf-brand); } .memberful-paywall__button--primary:hover, @@ -83,84 +115,76 @@ filter: brightness(0.92); } -.memberful-paywall__button--secondary { - background: transparent; - border: 1px solid var(--mf-border); - color: var(--mf-brand); +.memberful-paywall__signin { + color: var(--mf-muted); + font-size: 0.9rem; + margin: 0; } -.memberful-paywall__button--secondary:hover, -.memberful-paywall__button--secondary:focus { - border-color: var(--mf-brand); +.memberful-paywall__signin-link { + color: inherit; + text-decoration: underline; } -/* Simple layout - left aligned stack, no surface. */ -.memberful-paywall--simple { - text-align: left; +.memberful-paywall__lock { + align-items: center; + background: var(--mf-brand); + border-radius: 50%; + color: #fff; + display: inline-flex; + height: 44px; + justify-content: center; + margin: 0 auto 1rem; + width: 44px; } -/* Card layout - centred card with border and shadow. */ -.memberful-paywall--card { - text-align: center; +/* Simple — transparent band, minimal spacing. */ +.memberful-paywall--simple { + background: var(--mf-surface); } -.memberful-paywall--card .memberful-paywall__card { - background: var(--mf-surface); - border: 1px solid var(--mf-border); - border-radius: calc(var(--mf-radius) + 4px); - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); - padding: 2rem; +.memberful-paywall--simple .memberful-paywall__button { + padding: 0.6rem 1.4rem; } -.memberful-paywall--card .memberful-paywall__features { - display: inline-block; - text-align: left; +/* Card — centred white surface floating on a muted page backdrop. */ +.memberful-paywall--card { + background: #f2f4f7; } -.memberful-paywall--card .memberful-paywall__actions { - justify-content: center; +.memberful-paywall--card .memberful-paywall__inner { + padding-top: 1.5rem; } -/* Banner layout - heading/features on the left, CTA on the right. */ -.memberful-paywall--banner { - align-items: center; +.memberful-paywall--card .memberful-paywall__card { background: var(--mf-surface); border: 1px solid var(--mf-border); - border-radius: var(--mf-radius); - display: flex; - gap: 1.5rem; - max-width: none; - padding: 1.5rem 2rem; + border-radius: calc(var(--mf-radius) + 4px); + box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08); + margin: 0 auto; + max-width: 420px; + padding: 2.25rem 2rem; } -.memberful-paywall--banner .memberful-paywall__banner-text { - flex: 1 1 auto; - min-width: 0; +.memberful-paywall--card .memberful-paywall__button--primary { + display: block; + padding: 0.95rem 1.5rem; + width: 100%; } -.memberful-paywall--banner .memberful-paywall__heading { - font-size: 1.15rem; -} +/* Banner — full-bleed dark band with white copy. */ +.memberful-paywall--banner { + --mf-text: #fff; + --mf-muted: rgba(255, 255, 255, 0.75); -.memberful-paywall--banner .memberful-paywall__subheading { - margin-bottom: 0; + background: #1f2933; + color: var(--mf-text); } -.memberful-paywall--banner .memberful-paywall__actions { - flex: 0 0 auto; +.memberful-paywall--banner .memberful-paywall__signin-link { + color: #fff; } -@media (max-width: 768px) { - .memberful-paywall--banner { - align-items: stretch; - flex-direction: column; - } - - .memberful-paywall--banner .memberful-paywall__features { - display: none; - } - - .memberful-paywall--banner .memberful-paywall__actions { - justify-content: center; - } -} \ No newline at end of file +.memberful-paywall--banner .memberful-paywall__button--primary { + padding: 1rem 2rem; +} diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php index 7c223827..b5af465a 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -101,7 +101,7 @@

    - +

    From e978111fac2a39262d6c968249204b4a78e50cdc Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Thu, 23 Apr 2026 19:46:20 +1000 Subject: [PATCH 10/28] Adjust styles --- .../memberful-wp/src/global_marketing.php | 3 +- .../memberful-wp/src/paywall/preview.php | 12 +- .../memberful-wp/src/paywall/renderer.php | 53 ++++++- .../memberful-wp/stylesheets/admin.css | 2 +- .../memberful-wp/stylesheets/paywall.css | 136 +++++++++++------- 5 files changed, 145 insertions(+), 61 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php b/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php index 99442ee4..f14db2d8 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php @@ -98,7 +98,8 @@ function memberful_apply_global_snippets_content_filter( $memberful_marketing_co } } - $wrapped_teaser = "

    $teaser
    "; + $teaser_class = apply_filters( 'memberful_global_teaser_class', 'memberful-global-teaser-content' ); + $wrapped_teaser = "
    $teaser
    "; if ( $has_teaser && ! did_filter( 'memberful_teaser_css' ) ) { $wrapped_teaser .= apply_filters( 'memberful_teaser_css', memberful_get_teaser_css() ); diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php index 1ea186da..9b2636c0 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php @@ -55,11 +55,15 @@ public static function document( array $config ): string { $links .= sprintf( '', esc_url( $theme_css ) ); } - $teaser = ''; + $teaser_class = 'memberful-global-teaser-content memberful-global-teaser-content--mf-' . $config['layout']; + $teaser = sprintf( + '', + esc_attr( $teaser_class ) + ); - $styles = 'html,body{margin:0;background:#fff;color:#1b1b1b;}' - . '.mf-preview-teaser{font-size:16px;line-height:1.6;padding:24px 24px 0;}' - . '.mf-preview-teaser p{margin:0 0 1em;}'; + $styles = 'html,body{background:#fff;color:#1b1b1b;font-size:16px;line-height:1.6;margin:0;}' + . '.memberful-global-teaser-content{padding:24px 24px 0;}' + . '.memberful-global-teaser-content p{margin:0; padding-bottom: 1rem;}'; return '' . '' diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php index e708c8c7..ae234cf6 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php @@ -26,6 +26,36 @@ class Memberful_Paywall_Renderer { public static function register(): void { add_filter( 'memberful_wp_protect_content', array( __CLASS__, 'protect_content' ) ); add_action( 'wp_footer', array( __CLASS__, 'maybe_print_styles' ) ); + add_filter( 'memberful_global_teaser_class', array( __CLASS__, 'filter_teaser_class' ) ); + add_filter( 'memberful_teaser_css', array( __CLASS__, 'filter_teaser_css' ) ); + } + + /** + * Append a layout modifier to the teaser wrapper class when the builder paywall is active. + * + * @param string $classes Default teaser wrapper class list. + * + * @return string + */ + public static function filter_teaser_class( string $classes ): string { + if ( ! self::is_builder_mode() ) { + return $classes; + } + + $config = Memberful_Paywall_Config::get(); + + return $classes . ' memberful-global-teaser-content--mf-' . $config['layout']; + } + + /** + * Suppress the legacy inline teaser fade when the builder paywall owns the fade via paywall.css. + * + * @param string $css Legacy inline teaser CSS block. + * + * @return string + */ + public static function filter_teaser_css( string $css ): string { + return self::is_builder_mode() ? '' : $css; } /** @@ -36,9 +66,7 @@ public static function register(): void { * @return string */ public static function protect_content( string $content ): string { - $config = Memberful_Paywall_Config::get(); - - if ( 'builder' === $config['mode'] ) { + if ( self::is_builder_mode() ) { self::$should_print_styles = true; } @@ -227,11 +255,11 @@ private static function sign_in_prompt( array $config ): string { * @return string */ private static function lock_badge(): string { - return '
    diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php index 68f26ee9..2f0914de 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/mode-radio.php @@ -9,15 +9,15 @@ ?>
    - -
    - - -
    -
    \ No newline at end of file + +
    + + +
    + From b8e2df8d6fd35fd701945a7c4f49938b09984a02 Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Mon, 27 Apr 2026 09:51:13 +1000 Subject: [PATCH 12/28] Fix issues --- .../plugins/memberful-wp/src/admin.php | 3 +- .../memberful-wp/src/global_marketing.php | 26 +++++----- .../memberful-wp/stylesheets/paywall.css | 2 +- .../views/paywall/builder-panel.php | 52 +++++++++---------- 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/src/admin.php b/wordpress/wp-content/plugins/memberful-wp/src/admin.php index df8afe52..d6fd5669 100755 --- a/wordpress/wp-content/plugins/memberful-wp/src/admin.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/admin.php @@ -726,7 +726,8 @@ function memberful_wp_global_marketing() { Memberful_Paywall_Config::save( $paywall_input ); } - if ( 'custom_html' === Memberful_Paywall_Config::get()['mode'] ) { + $config_mode = $paywall_input['mode'] ?? 'builder'; + if ( 'custom_html' === $config_mode ) { update_option( 'memberful_global_marketing_content', memberful_wp_kses_post( filter_input( INPUT_POST, 'memberful_global_marketing_content' ) ) ); } } else { diff --git a/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php b/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php index ceea1f42..c3a7eb6c 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php @@ -17,17 +17,17 @@ * @return string */ function memberful_get_global_replacement($marketing_content){ - $override = get_option( 'memberful_global_marketing_override' ); + $override = get_option( 'memberful_global_marketing_override' ); - if ( $override ) { - return memberful_wp_resolve_global_marketing_content(); - } + if ( $override ) { + return memberful_wp_resolve_global_marketing_content(); + } - if ( empty( trim( $marketing_content ) ) ) { - return memberful_wp_resolve_global_marketing_content(); - } + if ( empty( trim( $marketing_content ) ) ) { + return memberful_wp_resolve_global_marketing_content(); + } - return $marketing_content; + return $marketing_content; } /** @@ -36,13 +36,13 @@ function memberful_get_global_replacement($marketing_content){ * @return string */ function memberful_wp_resolve_global_marketing_content(): string { - $config = Memberful_Paywall_Config::get(); + $config = Memberful_Paywall_Config::get(); - if ( 'builder' === $config['mode'] ) { - return Memberful_Paywall_Renderer::render( $config ); - } + if ( 'builder' === $config['mode'] ) { + return Memberful_Paywall_Renderer::render( $config ); + } - return (string) get_option( 'memberful_global_marketing_content' ); + return (string) get_option( 'memberful_global_marketing_content' ); } /** diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css index de8ba768..0ddaa2de 100644 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css @@ -219,7 +219,7 @@ h3.memberful-paywall__heading { } .memberful-global-teaser-content[class*="--mf-"]::after { - background: linear-gradient(transparent, #fff); + background: linear-gradient(transparent, var(--wp--preset--color--background, #fff)); bottom: 0; content: ""; height: 4rem; diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php index 9c8ac17d..80e0aaf5 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -16,40 +16,40 @@
    From 70af3b511e6f89eabb2756e43ab4b676eb2d8ad6 Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Wed, 29 Apr 2026 08:38:19 +1000 Subject: [PATCH 13/28] Improve post edit screen --- .../plugins/memberful-wp/src/metabox.php | 8 ++++ .../plugins/memberful-wp/views/metabox.php | 41 +++++++++++++------ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/src/metabox.php b/wordpress/wp-content/plugins/memberful-wp/src/metabox.php index d82b7ac3..59fd9765 100755 --- a/wordpress/wp-content/plugins/memberful-wp/src/metabox.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/metabox.php @@ -55,6 +55,10 @@ function memberful_wp_metabox( $post ) { $view_vars['viewable_by_any_registered_users'] = memberful_wp_get_post_available_to_any_registered_users( $post->ID ); $view_vars['viewable_by_anybody_subscribed_to_a_plan'] = memberful_wp_get_post_available_to_anybody_subscribed_to_a_plan( $post->ID ); + $paywall_config = Memberful_Paywall_Config::get(); + $view_vars['global_marketing_overrides_post_content'] = (bool) get_option( 'memberful_global_marketing_override' ) + || 'builder' === $paywall_config['mode']; + memberful_wp_render( 'metabox', $view_vars ); } @@ -155,6 +159,10 @@ function memberful_wp_add_term_metabox( $term ) { $view_vars['viewable_by_any_registered_users'] = memberful_wp_is_term_available_to_any_registered_users( $term->term_id ); $view_vars['viewable_by_anybody_subscribed_to_a_plan'] = memberful_wp_is_term_available_to_anybody_subscribed_to_a_plan( $term->term_id ); + $paywall_config = Memberful_Paywall_Config::get(); + $view_vars['global_marketing_overrides_post_content'] = (bool) get_option( 'memberful_global_marketing_override' ) + || 'builder' === $paywall_config['mode']; + memberful_wp_render( 'metabox', $view_vars ); } diff --git a/wordpress/wp-content/plugins/memberful-wp/views/metabox.php b/wordpress/wp-content/plugins/memberful-wp/views/metabox.php index 46d6fff3..9d7343fa 100755 --- a/wordpress/wp-content/plugins/memberful-wp/views/metabox.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/metabox.php @@ -5,19 +5,34 @@
    - -
    - - Click Here - - to manage global marketing content. -
    + +
    +

    + global marketing settings. Anything entered here is ignored until those settings change.', 'memberful' ), + array( 'a' => array( 'href' => array() ) ) + ), + esc_url( memberful_wp_plugin_global_marketing_url() ) + ); + ?> +

    +
    + + +
    + + + + +
    +
    From 3b40da0503fbfc59043c82e9e623a080056d78c6 Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Tue, 5 May 2026 08:05:35 +1000 Subject: [PATCH 14/28] Fix code audit issues --- .../memberful-wp/js/src/paywall-builder.js | 2 +- .../plugins/memberful-wp/src/options.php | 32 +- .../memberful-wp/src/paywall/config.php | 6 +- .../memberful-wp/src/paywall/preview.php | 10 +- .../memberful-wp/src/paywall/renderer.php | 666 +++++++++--------- .../memberful-wp/stylesheets/paywall.css | 80 +-- .../plugins/memberful-wp/views/metabox.php | 2 +- .../views/paywall/builder-panel.php | 2 +- 8 files changed, 406 insertions(+), 394 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js index a00985e7..72ec8641 100644 --- a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js +++ b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js @@ -2,7 +2,7 @@ jQuery(function ($) { const $form = $('.memberful-paywall-builder__panel[data-panel="builder"]'); const $modeInputs = $('input[name="memberful_paywall[mode]"]'); const $panels = $('.memberful-paywall-builder__panel'); - const $preview = $('#mf-paywall-preview'); + const $preview = $('#memberful-paywall-preview'); const $colorInput = $('.memberful-paywall-builder__color'); const preview = window.memberfulPaywallPreview || {}; diff --git a/wordpress/wp-content/plugins/memberful-wp/src/options.php b/wordpress/wp-content/plugins/memberful-wp/src/options.php index aa6a5a36..fd0351be 100755 --- a/wordpress/wp-content/plugins/memberful-wp/src/options.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/options.php @@ -3,30 +3,30 @@ function memberful_wp_all_options(): array { return array( - 'memberful_client_id' => null, - 'memberful_client_secret' => null, - 'memberful_site' => null, - 'memberful_custom_domain' => null, - 'memberful_api_key' => null, - 'memberful_webhook_secret' => null, + 'memberful_client_id' => NULL, + 'memberful_client_secret' => NULL, + 'memberful_site' => NULL, + 'memberful_custom_domain' => NULL, + 'memberful_api_key' => NULL, + 'memberful_webhook_secret' => NULL, 'memberful_products' => array(), 'memberful_subscriptions' => array(), 'memberful_acl' => array(), - 'memberful_embed_enabled' => false, + 'memberful_embed_enabled' => FALSE, 'memberful_error_log' => array(), 'memberful_role_active_customer' => 'subscriber', 'memberful_role_inactive_customer' => 'subscriber', 'memberful_plan_role_mappings' => array(), - 'memberful_use_per_plan_roles' => false, + 'memberful_use_per_plan_roles' => FALSE, 'memberful_posts_available_to_any_registered_user' => array(), - 'memberful_hide_admin_toolbar' => true, - 'memberful_block_dashboard_access' => true, - 'memberful_filter_account_menu_items' => true, - 'memberful_auto_sync_display_names' => false, - 'memberful_show_protected_content_in_search' => false, - 'memberful_use_global_marketing' => false, - 'memberful_use_global_snippets' => true, - 'memberful_global_marketing_override' => true, + 'memberful_hide_admin_toolbar' => TRUE, + 'memberful_block_dashboard_access' => TRUE, + 'memberful_filter_account_menu_items' => TRUE, + 'memberful_auto_sync_display_names' => FALSE, + 'memberful_show_protected_content_in_search' => FALSE, + 'memberful_use_global_marketing' => FALSE, + 'memberful_use_global_snippets' => TRUE, + 'memberful_global_marketing_override' => TRUE, 'memberful_global_marketing_content' => '', 'memberful_ad_provider_settings' => array(), Memberful_Paywall_Config::OPTION_KEY => array(), diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php index 62ad4889..7c5c3ac4 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php @@ -25,11 +25,11 @@ public static function defaults(): array { return array( 'mode' => 'builder', 'layout' => 'card', - 'heading' => esc_html__( 'Subscribe to keep reading', 'memberful' ), + 'heading' => __( 'Subscribe to keep reading', 'memberful' ), 'heading_tag' => 'h2', - 'subheading' => esc_html__( 'This post is for paying subscribers.', 'memberful' ), + 'subheading' => __( 'This post is for paying subscribers.', 'memberful' ), 'features' => array(), - 'button_label' => esc_html__( 'Subscribe', 'memberful' ), + 'button_label' => __( 'Subscribe', 'memberful' ), 'subscribe_url' => '', 'sign_in_url' => '', 'brand_color' => '', diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php index 9b2636c0..82c1f2b2 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/preview.php @@ -47,7 +47,7 @@ public static function handle(): void { public static function document( array $config ): string { $body = Memberful_Paywall_Renderer::render( $config ); - $paywall_css = plugins_url( 'stylesheets/paywall.css', MEMBERFUL_PLUGIN_FILE ); + $paywall_css = add_query_arg( 'ver', MEMBERFUL_VERSION, plugins_url( 'stylesheets/paywall.css', MEMBERFUL_PLUGIN_FILE ) ); $theme_css = get_stylesheet_uri(); $links = sprintf( '', esc_url( $paywall_css ) ); @@ -55,10 +55,12 @@ public static function document( array $config ): string { $links .= sprintf( '', esc_url( $theme_css ) ); } - $teaser_class = 'memberful-global-teaser-content memberful-global-teaser-content--mf-' . $config['layout']; + $layout = ( isset( $config['layout'] ) && in_array( $config['layout'], Memberful_Paywall_Config::LAYOUTS, true ) ) ? $config['layout'] : 'card'; + $teaser_class = 'memberful-global-teaser-content memberful-global-teaser-content--memberful-' . $layout; $teaser = sprintf( - '', - esc_attr( $teaser_class ) + '', + esc_attr( $teaser_class ), + esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae urna id quam faucibus gravida ac sed ipsum. Quisque eget velit dictum leo tempor bibendum nec sed odio.', 'memberful' ) ); $styles = 'html,body{background:#fff;color:#1b1b1b;font-size:16px;line-height:1.6;margin:0;}' diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php index ae234cf6..373fec0e 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php @@ -13,334 +13,344 @@ * Renders a paywall config array to HTML. */ class Memberful_Paywall_Renderer { - /** - * Flag to determine if we should load paywall styles. - * - * @var bool - */ - private static $should_print_styles = false; - - /** - * Register the actions on plugin load. - */ - public static function register(): void { - add_filter( 'memberful_wp_protect_content', array( __CLASS__, 'protect_content' ) ); - add_action( 'wp_footer', array( __CLASS__, 'maybe_print_styles' ) ); - add_filter( 'memberful_global_teaser_class', array( __CLASS__, 'filter_teaser_class' ) ); - add_filter( 'memberful_teaser_css', array( __CLASS__, 'filter_teaser_css' ) ); - } - - /** - * Append a layout modifier to the teaser wrapper class when the builder paywall is active. - * - * @param string $classes Default teaser wrapper class list. - * - * @return string - */ - public static function filter_teaser_class( string $classes ): string { - if ( ! self::is_builder_mode() ) { - return $classes; - } - - $config = Memberful_Paywall_Config::get(); - - return $classes . ' memberful-global-teaser-content--mf-' . $config['layout']; - } - - /** - * Suppress the legacy inline teaser fade when the builder paywall owns the fade via paywall.css. - * - * @param string $css Legacy inline teaser CSS block. - * - * @return string - */ - public static function filter_teaser_css( string $css ): string { - return self::is_builder_mode() ? '' : $css; - } - - /** - * Conditionally flag paywall loading. - * - * @param string $content Content. - * - * @return string - */ - public static function protect_content( string $content ): string { - if ( self::is_builder_mode() ) { - self::$should_print_styles = true; - } - - return $content; - } - - /** - * Render paywall styles. - */ - public static function maybe_print_styles(): void { - if ( ! self::$should_print_styles ) { - return; - } - - wp_enqueue_style( - 'memberful-paywall', - MEMBERFUL_URL . '/stylesheets/paywall.css', - array(), - MEMBERFUL_VERSION - ); - } - - /** - * Render a paywall config to HTML. - * - * @param array $config Config shape from Memberful_Paywall_Config::get(). - * - * @return string - */ - public static function render( array $config ): string { - $config = wp_parse_args( $config, Memberful_Paywall_Config::defaults() ); - - $layout = in_array( $config['layout'], Memberful_Paywall_Config::LAYOUTS, true ) ? $config['layout'] : 'card'; - $method = 'render_' . $layout; - - return sprintf( - '
    %3$s
    ', - esc_attr( $layout ), - esc_attr( self::wrapper_style( $config ) ), - self::$method( $config ) - ); - } - - /** - * Render the "simple" layout — minimal text + CTA on a transparent band. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function render_simple( array $config ): string { - return '
    ' - . self::heading_block( $config ) - . self::subheading_block( $config ) - . self::features_block( $config ) - . '
    ' . self::primary_cta( $config ) . '
    ' - . self::sign_in_prompt( $config ) - . '
    '; - } - - /** - * Render the "card" layout — centred white card with lock badge. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function render_card( array $config ): string { - return '
    ' - . '
    ' - . self::lock_badge() - . self::heading_block( $config ) - . self::subheading_block( $config ) - . self::features_block( $config ) - . '
    ' . self::primary_cta( $config ) . '
    ' - . self::sign_in_prompt( $config ) - . '
    ' - . '
    '; - } - - /** - * Render the "banner" layout — full-width dark band. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function render_banner( array $config ): string { - return '
    ' - . self::heading_block( $config ) - . self::subheading_block( $config ) - . self::features_block( $config ) - . '
    ' . self::primary_cta( $config ) . '
    ' - . self::sign_in_prompt( $config ) - . '
    '; - } - - /** - * Heading element with the configured tag. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function heading_block( array $config ): string { - $tag = in_array( $config['heading_tag'], Memberful_Paywall_Config::HEADING_TAGS, true ) ? $config['heading_tag'] : 'h2'; - return sprintf( - '<%1$s class="memberful-paywall__heading">%2$s', - tag_escape( $tag ), - esc_html( $config['heading'] ) - ); - } - - /** - * Subheading paragraph, or empty when blank. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function subheading_block( array $config ): string { - if ( '' === $config['subheading'] ) { - return ''; - } - - return sprintf( - '

    %s

    ', - esc_html( $config['subheading'] ) - ); - } - - /** - * Feature list with inline check icons, or empty when no features. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function features_block( array $config ): string { - if ( empty( $config['features'] ) ) { - return ''; - } - - $items = ''; - foreach ( $config['features'] as $feature ) { - $items .= '
  • ' . self::check_icon() . '' . esc_html( $feature ) . '
  • '; - } - - return '
      ' . $items . '
    '; - } - - /** - * Primary subscribe CTA anchor. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function primary_cta( array $config ): string { - return sprintf( - '%s', - esc_url( self::subscribe_url( $config ) ), - esc_html( $config['button_label'] ) - ); - } - - /** - * "Already a subscriber? Sign in" text prompt shown under every layout's CTA. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function sign_in_prompt( array $config ): string { - return sprintf( - '', - esc_html__( 'Already a subscriber?', 'memberful' ), - esc_url( self::sign_in_url( $config ) ), - esc_html__( 'Sign in', 'memberful' ) - ); - } - - /** - * Circular lock badge shown at the top of the card layout. - * - * @return string - */ - private static function lock_badge(): string { - return ''; - } - - /** - * Wrapper inline style carrying the brand colour and button radius custom properties. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function wrapper_style( array $config ): string { - $parts = array( '--mf-radius:' . self::button_radius( $config['button_shape'] ) ); - - if ( ! empty( $config['brand_color'] ) ) { - $parts[] = '--mf-brand:' . $config['brand_color']; - } - - return implode( ';', $parts ) . ';'; - } - - /** - * Map the button-shape enum to a CSS radius. - * - * @param string $shape One of the `button_shape` enum values. - * - * @return string - */ - private static function button_radius( string $shape ): string { - switch ( $shape ) { - case 'pill': - return '999px'; - case 'square': - return '0'; - case 'rounded': - default: - return '8px'; - } - } - - /** - * Resolve the subscribe URL, falling back to the Memberful registration page. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function subscribe_url( array $config ): string { - return ! empty( $config['subscribe_url'] ) ? $config['subscribe_url'] : memberful_registration_page_url(); - } - - /** - * Resolve the sign-in URL, falling back to the Memberful sign-in endpoint. - * - * @param array $config Sanitized config. - * - * @return string - */ - private static function sign_in_url( array $config ): string { - return ! empty( $config['sign_in_url'] ) ? $config['sign_in_url'] : memberful_sign_in_url(); - } - - /** - * Inline check-mark SVG used in the features list. - * - * @return string - */ - private static function check_icon(): string { - return ''; - } - - /** - * Whether the builder paywall is the active rendering mode. - * - * @return bool - */ - private static function is_builder_mode(): bool { - $config = Memberful_Paywall_Config::get(); - - return 'builder' === $config['mode']; - } + /** + * Flag to determine if we should load paywall styles. + * + * @var bool + */ + private static $should_print_styles = false; + + /** + * Register the actions on plugin load. + */ + public static function register(): void { + add_filter( 'memberful_wp_protect_content', array( __CLASS__, 'protect_content' ) ); + add_action( 'wp_footer', array( __CLASS__, 'maybe_print_styles' ) ); + add_filter( 'memberful_global_teaser_class', array( __CLASS__, 'filter_teaser_class' ) ); + add_filter( 'memberful_teaser_css', array( __CLASS__, 'filter_teaser_css' ) ); + } + + /** + * Append a layout modifier to the teaser wrapper class when the builder paywall is active. + * + * @param string $classes Default teaser wrapper class list. + * + * @return string + */ + public static function filter_teaser_class( string $classes ): string { + if ( ! self::is_builder_mode() ) { + return $classes; + } + + $config = Memberful_Paywall_Config::get(); + + return $classes . ' memberful-global-teaser-content--memberful-' . $config['layout']; + } + + /** + * Suppress the legacy inline teaser fade when the builder paywall owns the fade via paywall.css. + * + * @param string $css Legacy inline teaser CSS block. + * + * @return string + */ + public static function filter_teaser_css( string $css ): string { + return self::is_builder_mode() ? '' : $css; + } + + /** + * Conditionally flag paywall loading. + * + * @param string $content Content. + * + * @return string + */ + public static function protect_content( string $content ): string { + if ( self::is_builder_mode() ) { + self::$should_print_styles = true; + } + + /** + * Filter the protected paywall content before it is returned. + * + * @param string $content Rendered paywall content. + */ + return apply_filters( 'memberful_paywall_protected_content', $content ); + } + + /** + * Render paywall styles. + */ + public static function maybe_print_styles(): void { + /** + * Filter whether the bundled paywall stylesheet should be enqueued. + * + * @param bool $should_print_styles Whether the stylesheet will be enqueued. + */ + if ( ! apply_filters( 'memberful_paywall_print_styles', self::$should_print_styles ) ) { + return; + } + + wp_enqueue_style( + 'memberful-paywall', + MEMBERFUL_URL . '/stylesheets/paywall.css', + array(), + MEMBERFUL_VERSION + ); + } + + /** + * Render a paywall config to HTML. + * + * @param array $config Config shape from Memberful_Paywall_Config::get(). + * + * @return string + */ + public static function render( array $config ): string { + $config = wp_parse_args( $config, Memberful_Paywall_Config::defaults() ); + + $layout = in_array( $config['layout'], Memberful_Paywall_Config::LAYOUTS, true ) ? $config['layout'] : 'card'; + $method = 'render_' . $layout; + + return sprintf( + '
    %3$s
    ', + esc_attr( $layout ), + esc_attr( self::wrapper_style( $config ) ), + self::$method( $config ) + ); + } + + /** + * Render the "simple" layout — minimal text + CTA on a transparent band. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function render_simple( array $config ): string { + return '
    ' . self::render_inner( $config ) . '
    '; + } + + /** + * Render the "card" layout — centred white card with lock badge. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function render_card( array $config ): string { + return '
    ' + . '
    ' + . self::lock_badge() + . self::render_inner( $config ) + . '
    ' + . '
    '; + } + + /** + * Render the "banner" layout — full-width dark band. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function render_banner( array $config ): string { + return '
    ' . self::render_inner( $config ) . '
    '; + } + + /** + * Shared inner content shared by every layout: heading, subheading, features, CTA, sign-in. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function render_inner( array $config ): string { + return self::heading_block( $config ) + . self::subheading_block( $config ) + . self::features_block( $config ) + . '
    ' . self::primary_cta( $config ) . '
    ' + . self::sign_in_prompt( $config ); + } + + /** + * Heading element with the configured tag. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function heading_block( array $config ): string { + $tag = in_array( $config['heading_tag'], Memberful_Paywall_Config::HEADING_TAGS, true ) ? $config['heading_tag'] : 'h2'; + return sprintf( + '<%1$s class="memberful-paywall__heading">%2$s', + tag_escape( $tag ), + esc_html( $config['heading'] ) + ); + } + + /** + * Subheading paragraph, or empty when blank. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function subheading_block( array $config ): string { + if ( '' === $config['subheading'] ) { + return ''; + } + + return sprintf( + '

    %s

    ', + esc_html( $config['subheading'] ) + ); + } + + /** + * Feature list with inline check icons, or empty when no features. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function features_block( array $config ): string { + if ( empty( $config['features'] ) ) { + return ''; + } + + $items = ''; + foreach ( $config['features'] as $feature ) { + $items .= '
  • ' . self::check_icon() . '' . esc_html( $feature ) . '
  • '; + } + + return '
      ' . $items . '
    '; + } + + /** + * Primary subscribe CTA anchor. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function primary_cta( array $config ): string { + return sprintf( + '%s', + esc_url( self::subscribe_url( $config ) ), + esc_html( $config['button_label'] ) + ); + } + + /** + * "Already a subscriber? Sign in" text prompt shown under every layout's CTA. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function sign_in_prompt( array $config ): string { + return sprintf( + '', + esc_html__( 'Already a subscriber?', 'memberful' ), + esc_url( self::sign_in_url( $config ) ), + esc_html__( 'Sign in', 'memberful' ) + ); + } + + /** + * Circular lock badge shown at the top of the card layout. + * + * @return string + */ + private static function lock_badge(): string { + return ''; + } + + /** + * Wrapper inline style carrying the brand colour and button radius custom properties. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function wrapper_style( array $config ): string { + $parts = array( '--memberful-radius:' . self::button_radius( $config['button_shape'] ) ); + + $brand_color = isset( $config['brand_color'] ) ? sanitize_hex_color( (string) $config['brand_color'] ) : ''; + if ( ! empty( $brand_color ) ) { + $parts[] = '--memberful-brand:' . $brand_color; + } + + return implode( ';', $parts ) . ';'; + } + + /** + * Map the button-shape enum to a CSS radius. + * + * @param string $shape One of the `button_shape` enum values. + * + * @return string + */ + private static function button_radius( string $shape ): string { + switch ( $shape ) { + case 'pill': + return '999px'; + case 'square': + return '0'; + case 'rounded': + default: + return '8px'; + } + } + + /** + * Resolve the subscribe URL, falling back to the Memberful registration page. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function subscribe_url( array $config ): string { + return ! empty( $config['subscribe_url'] ) ? $config['subscribe_url'] : memberful_registration_page_url(); + } + + /** + * Resolve the sign-in URL, falling back to the Memberful sign-in endpoint. + * + * @param array $config Sanitized config. + * + * @return string + */ + private static function sign_in_url( array $config ): string { + return ! empty( $config['sign_in_url'] ) ? $config['sign_in_url'] : memberful_sign_in_url(); + } + + /** + * Inline check-mark SVG used in the features list. + * + * @return string + */ + private static function check_icon(): string { + return ''; + } + + /** + * Whether the builder paywall is the active rendering mode. + * + * @return bool + */ + private static function is_builder_mode(): bool { + $config = Memberful_Paywall_Config::get(); + + return 'builder' === $config['mode']; + } } Memberful_Paywall_Renderer::register(); diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css index 0ddaa2de..45f692a7 100644 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css @@ -3,16 +3,16 @@ */ .memberful-paywall { - --mf-border: #e5e5e5; - --mf-brand: var(--wp--preset--color--primary, var(--wp--preset--color--accent, var(--wp-admin-theme-color, #2271b1))); - --mf-muted: #666; - --mf-muted-soft: #999; - --mf-radius: 0; - --mf-surface: #fff; - --mf-text: #1a1a1a; + --memberful-border: #e5e5e5; + --memberful-brand: var(--wp--preset--color--primary, var(--wp--preset--color--accent, var(--wp-admin-theme-color, #2271b1))); + --memberful-muted: #666; + --memberful-muted-soft: #999; + --memberful-radius: 0; + --memberful-surface: #fff; + --memberful-text: #1a1a1a; box-sizing: border-box; - color: var(--mf-text); + color: var(--memberful-text); font-family: inherit; line-height: 1.5; margin: 0; @@ -30,39 +30,39 @@ .memberful-paywall__inner { margin: 0 auto; max-width: 600px; - padding: 2rem 1.5rem 1.5rem; + padding: var(--wp--preset--spacing--50, 2rem) var(--wp--preset--spacing--40, 1.5rem) var(--wp--preset--spacing--40, 1.5rem); text-align: center; } .memberful-paywall__heading { - color: var(--mf-text); + color: var(--memberful-text); font-weight: 600; line-height: 1.3; margin: 0 0 0.5rem; } h1.memberful-paywall__heading { - font-size: 1.5rem; + font-size: var(--wp--preset--font-size--large, 1.5rem); } h2.memberful-paywall__heading { - font-size: 1.125rem; + font-size: var(--wp--preset--font-size--medium, 1.125rem); } h3.memberful-paywall__heading { - font-size: 1rem; + font-size: var(--wp--preset--font-size--medium, 1rem); } .memberful-paywall__subheading { - color: var(--mf-muted); - font-size: 0.875rem; + color: var(--memberful-muted); + font-size: var(--wp--preset--font-size--small, 0.875rem); margin: 0 auto 1.25rem; max-width: 28rem; } .memberful-paywall__features { - color: var(--mf-muted); - column-gap: 1.5rem; + color: var(--memberful-muted); + column-gap: var(--wp--preset--spacing--40, 1.5rem); display: inline-grid; font-size: 0.9375rem; grid-template-columns: repeat(2, auto); @@ -80,7 +80,7 @@ h3.memberful-paywall__heading { } .memberful-paywall__check { - color: var(--mf-brand); + color: var(--memberful-brand); flex: 0 0 auto; margin-top: 0.2rem; } @@ -96,9 +96,9 @@ h3.memberful-paywall__heading { .memberful-paywall__button { border: 1px solid transparent; - border-radius: var(--mf-radius); + border-radius: var(--memberful-radius); display: inline-block; - font-size: 0.875rem; + font-size: var(--wp--preset--font-size--small, 0.875rem); font-weight: 600; line-height: 1.2; padding: 0.5rem 1.5rem; @@ -107,8 +107,8 @@ h3.memberful-paywall__heading { } .memberful-paywall__button--primary { - background: var(--mf-brand); - border-color: var(--mf-brand); + background: var(--memberful-brand); + border-color: var(--memberful-brand); color: #fff; } @@ -118,19 +118,19 @@ h3.memberful-paywall__heading { } .memberful-paywall__signin { - color: var(--mf-muted-soft); + color: var(--memberful-muted-soft); font-size: 0.75rem; - margin: 1rem 0 0; + margin: var(--wp--preset--spacing--30, 1rem) 0 0; } .memberful-paywall__signin-link { - color: var(--mf-brand); + color: var(--memberful-brand); text-decoration: underline; } .memberful-paywall__lock { align-items: center; - background: var(--mf-brand); + background: var(--memberful-brand); border-radius: 50%; color: #fff; display: inline-flex; @@ -142,19 +142,19 @@ h3.memberful-paywall__heading { /* Simple — top-divider band on white. */ .memberful-paywall--simple { - background: var(--mf-surface); - border-top: 1px solid var(--mf-border); + background: var(--memberful-surface); + border-top: 1px solid var(--memberful-border); } /* Card — centred white card on muted page. */ .memberful-paywall--card { - --mf-surface: #f8f8f8; + --memberful-surface: #f8f8f8; - background: var(--mf-surface); + background: var(--memberful-surface); } .memberful-paywall--card .memberful-paywall__inner { - padding: 1.5rem; + padding: var(--wp--preset--spacing--40, 1.5rem); } .memberful-paywall--card .memberful-paywall__card { @@ -164,7 +164,7 @@ h3.memberful-paywall__heading { box-shadow: 0 10px 25px rgba(15, 23, 42, 0.1); margin: 0 auto; max-width: 24rem; - padding: 2rem; + padding: var(--wp--preset--spacing--50, 2rem); } .memberful-paywall--card .memberful-paywall__heading { @@ -179,12 +179,12 @@ h3.memberful-paywall__heading { /* Banner — full-bleed dark band, always viewport width. */ .memberful-paywall--banner { - --mf-muted: #a0a5aa; - --mf-muted-soft: #72777c; - --mf-surface: #1d2327; - --mf-text: #fff; + --memberful-muted: #a0a5aa; + --memberful-muted-soft: #72777c; + --memberful-surface: #1d2327; + --memberful-text: #fff; - background: var(--mf-surface); + background: var(--memberful-surface); margin-inline: calc(50% - 50vw); width: 100vw; } @@ -198,7 +198,7 @@ h3.memberful-paywall__heading { } .memberful-paywall--banner h2.memberful-paywall__heading { - font-size: 1.25rem; + font-size: var(--wp--preset--font-size--medium, 1.25rem); } .memberful-paywall--banner .memberful-paywall__subheading { @@ -214,11 +214,11 @@ h3.memberful-paywall__heading { } /* Teaser fade. */ -.memberful-global-teaser-content[class*="--mf-"] { +.memberful-global-teaser-content[class*="--memberful-"] { position: relative; } -.memberful-global-teaser-content[class*="--mf-"]::after { +.memberful-global-teaser-content[class*="--memberful-"]::after { background: linear-gradient(transparent, var(--wp--preset--color--background, #fff)); bottom: 0; content: ""; diff --git a/wordpress/wp-content/plugins/memberful-wp/views/metabox.php b/wordpress/wp-content/plugins/memberful-wp/views/metabox.php index 9d7343fa..961c663b 100755 --- a/wordpress/wp-content/plugins/memberful-wp/views/metabox.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/metabox.php @@ -24,7 +24,7 @@
    diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php index 80e0aaf5..355a3fa2 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -120,7 +120,7 @@

    -
    +
    +

    +
    -
    + \ No newline at end of file From ad9e596b023f4c00d81e40d971d75e28775a2c8d Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Wed, 13 May 2026 19:48:32 +1000 Subject: [PATCH 23/28] Itemised benefits functionality --- .../memberful-wp/js/src/paywall-builder.js | 33 ++++++++++- .../memberful-wp/stylesheets/admin.css | 56 ++++++++++++++++++- .../memberful-wp/stylesheets/paywall.css | 7 +-- .../views/paywall/builder-panel.php | 36 ++++++++++-- 4 files changed, 118 insertions(+), 14 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js index 291598dc..3cdc66f0 100644 --- a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js +++ b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js @@ -46,7 +46,9 @@ jQuery(function ($) { layout: $('input[name="memberful_paywall[layout]"]:checked').val() || 'card', heading: $('#memberful-paywall-heading').val() || '', subheading: $('#memberful-paywall-subheading').val() || '', - features: $('#memberful-paywall-features').val() || '', + features: $('#memberful-paywall-benefits .memberful-paywall-builder__benefit-input').map(function () { + return $(this).val(); + }).get(), button_label: $('#memberful-paywall-button-label').val() || '', subscribe_url: $('#memberful-paywall-subscribe-url').val() || '', sign_in_url: $('#memberful-paywall-signin-url').val() || '', @@ -69,7 +71,14 @@ jQuery(function ($) { const config = collectConfig(); Object.keys(config).forEach(function (key) { - body.append('config[' + key + ']', config[key]); + const value = config[key]; + if (Array.isArray(value)) { + value.forEach(function (item) { + body.append('config[' + key + '][]', item); + }); + } else { + body.append('config[' + key + ']', value); + } }); fetch(preview.ajaxUrl, { @@ -107,6 +116,26 @@ jQuery(function ($) { $form.on('input', 'input[type="text"], input[type="url"], textarea', scheduleRefresh); $form.on('change', 'input[type="radio"], select', refreshPreview); + $form.on('click', '.memberful-paywall-builder__benefit-add', function (e) { + e.preventDefault(); + + const template = document.getElementById('memberful-paywall-benefit-template'); + const list = document.getElementById('memberful-paywall-benefits'); + if (!template || !list) { + return; + } + + list.appendChild(template.content.cloneNode(true)); + $(list).find('.memberful-paywall-builder__benefit-input').last().trigger('focus'); + refreshPreview(); + }); + + $form.on('click', '.memberful-paywall-builder__benefit-remove', function (e) { + e.preventDefault(); + $(this).closest('.memberful-paywall-builder__benefit').remove(); + refreshPreview(); + }); + if (($modeInputs.filter(':checked').val() || 'builder') === 'builder') { refreshPreview(); } diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css index 1509996a..e2ae6c47 100755 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css @@ -463,12 +463,10 @@ Paywall Builder } .memberful-paywall-builder__customize .memberful-paywall-builder__field { - display: block; margin: 0 0 1rem; } .memberful-paywall-builder__customize label { display: block; - font-size: 13px; font-weight: 600; margin-bottom: 4px; } @@ -500,6 +498,60 @@ Paywall Builder min-width: 80px; } +/* Benefits */ +.memberful-paywall-builder__benefits-label { + font-weight: 600; + margin-bottom: 8px; + padding: 0; +} +.memberful-paywall-builder__benefit-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 8px; +} +.memberful-paywall-builder__benefit { + align-items: center; + display: flex; + gap: 8px; +} +.memberful-paywall-builder__benefit-icon { + color: var(--wp-admin-theme-color); + flex: 0 0 auto; +} +.memberful-paywall-builder__customize .memberful-paywall-builder__benefit-label { + flex: 1; + margin: 0; +} +.memberful-paywall-builder__benefit-remove { + background: none; + border: 0; + color: #646970; + cursor: pointer; + display: inline-flex; + opacity: 0; + padding: 4px; + transition: opacity 120ms ease; +} +.memberful-paywall-builder__benefit:hover .memberful-paywall-builder__benefit-remove, +.memberful-paywall-builder__benefit-remove:focus-visible { + opacity: 1; +} +.memberful-paywall-builder__benefit-remove:hover { + color: #1d2327; +} +.memberful-paywall-builder__benefit-add { + background: none; + border: 0; + color: var(--wp-admin-theme-color); + cursor: pointer; + font-weight: 500; + padding: 4px 0; +} +.memberful-paywall-builder__benefit-add:hover { + color: var(--wp-admin-theme-color-darker-10); +} + .memberful-paywall-builder__preview-frame { background: #fff; border: 1px solid #dcdcde; diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css index 1efadb33..e75b81ed 100644 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css @@ -54,14 +54,13 @@ .memberful-paywall__features { color: var(--memberful-muted); - column-gap: var(--wp--preset--spacing--40, 1.5rem); - display: inline-grid; + display: inline-flex; + flex-direction: column; font-size: 0.9375rem; - grid-template-columns: repeat(2, auto); + gap: 0.375rem; list-style: none; margin: 0 0 1.25rem; padding: 0; - row-gap: 0.375rem; text-align: left; } diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php index 27ee75ca..1a22111e 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -8,7 +8,6 @@ * @var bool $is_active */ -$features_textarea = implode( "\n", (array) $paywall_config['features'] ); ?> - \ No newline at end of file + From 16f45d8f68207aac737bc76616987392effe11ac Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Thu, 14 May 2026 08:55:27 +1000 Subject: [PATCH 25/28] Adjust accent and background colors functionality --- .../memberful-wp/js/src/paywall-builder.js | 62 +++++++++------ .../plugins/memberful-wp/src/paywall.php | 1 + .../memberful-wp/src/paywall/color.php | 76 +++++++++++++++++++ .../memberful-wp/src/paywall/config.php | 21 ++--- .../memberful-wp/src/paywall/renderer.php | 6 ++ .../memberful-wp/src/paywall/sanitizer.php | 10 ++- .../memberful-wp/stylesheets/admin.css | 12 +++ .../memberful-wp/stylesheets/paywall.css | 12 ++- .../views/paywall/builder-panel.php | 42 +++++++++- 9 files changed, 195 insertions(+), 47 deletions(-) create mode 100644 wordpress/wp-content/plugins/memberful-wp/src/paywall/color.php diff --git a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js index 3cdc66f0..57fced03 100644 --- a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js +++ b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js @@ -1,9 +1,9 @@ jQuery(function ($) { - const $form = $('.memberful-paywall-builder__panel[data-panel="builder"]'); - const $modeInputs = $('input[name="memberful_paywall[mode]"]'); - const $panels = $('.memberful-paywall-builder__panel'); - const $preview = $('#memberful-paywall-preview'); - const $colorInput = $('.memberful-paywall-builder__color'); + const $form = $('.memberful-paywall-builder__panel[data-panel="builder"]'); + const $modeInputs = $('input[name="memberful_paywall[mode]"]'); + const $panels = $('.memberful-paywall-builder__panel'); + const $preview = $('#memberful-paywall-preview'); + const $colorInputs = $('.memberful-paywall-builder__color'); const preview = window.memberfulPaywallPreview || {}; const DEBOUNCE_MS = 250; @@ -11,13 +11,30 @@ jQuery(function ($) { let debounceTimer = null; let requestSeq = 0; - $colorInput.wpColorPicker({ - change: function () { - setTimeout(scheduleRefresh, 0); - }, - clear: function () { - setTimeout(scheduleRefresh, 0); - }, + $colorInputs.each(function () { + const $input = $(this); + let palettes = true; + const palettesAttr = $input.attr('data-palettes'); + if (palettesAttr) { + try { + const parsed = JSON.parse(palettesAttr); + if (Array.isArray(parsed) && parsed.length) { + palettes = parsed; + } + } catch (e) { + // Fall back to wpColorPicker's default palette. + } + } + + $input.wpColorPicker({ + palettes: palettes, + change: function () { + setTimeout(scheduleRefresh, 0); + }, + clear: function () { + setTimeout(scheduleRefresh, 0); + }, + }); }); function applyMode(mode) { @@ -42,18 +59,19 @@ jQuery(function ($) { function collectConfig() { return { - mode: $('input[name="memberful_paywall[mode]"]:checked').val() || 'builder', - layout: $('input[name="memberful_paywall[layout]"]:checked').val() || 'card', - heading: $('#memberful-paywall-heading').val() || '', - subheading: $('#memberful-paywall-subheading').val() || '', - features: $('#memberful-paywall-benefits .memberful-paywall-builder__benefit-input').map(function () { + mode: $('input[name="memberful_paywall[mode]"]:checked').val() || 'builder', + layout: $('input[name="memberful_paywall[layout]"]:checked').val() || 'card', + heading: $('#memberful-paywall-heading').val() || '', + subheading: $('#memberful-paywall-subheading').val() || '', + features: $('#memberful-paywall-benefits .memberful-paywall-builder__benefit-input').map(function () { return $(this).val(); }).get(), - button_label: $('#memberful-paywall-button-label').val() || '', - subscribe_url: $('#memberful-paywall-subscribe-url').val() || '', - sign_in_url: $('#memberful-paywall-signin-url').val() || '', - brand_color: $colorInput.val() || '', - button_shape: $('#memberful-paywall-button-shape').val() || 'rounded', + button_label: $('#memberful-paywall-button-label').val() || '', + subscribe_url: $('#memberful-paywall-subscribe-url').val() || '', + sign_in_url: $('#memberful-paywall-signin-url').val() || '', + brand_color: $('#memberful-paywall-brand-color').val() || '', + background_color: $('#memberful-paywall-background-color').val() || '', + button_shape: $('#memberful-paywall-button-shape').val() || 'rounded', }; } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php index 366ac454..03e81e75 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall.php @@ -8,6 +8,7 @@ /** * Load required classes. */ +require_once MEMBERFUL_DIR . '/src/paywall/color.php'; require_once MEMBERFUL_DIR . '/src/paywall/config.php'; require_once MEMBERFUL_DIR . '/src/paywall/preview.php'; require_once MEMBERFUL_DIR . '/src/paywall/renderer.php'; diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/color.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/color.php new file mode 100644 index 00000000..14f87b09 --- /dev/null +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/color.php @@ -0,0 +1,76 @@ + self::LUMINANCE_THRESHOLD ? self::TEXT_DARK : self::TEXT_LIGHT; + } + + /** + * Parse a 3- or 6-digit hex colour into [r, g, b] of 0–255 integers. + * + * @param string $hex Hex colour input. + * + * @return array{0:int,1:int,2:int}|null + */ + private static function hex_to_rgb( string $hex ): ?array { + $hex = ltrim( trim( $hex ), '#' ); + + if ( 3 === strlen( $hex ) ) { + $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; + } + + if ( 6 !== strlen( $hex ) || ! ctype_xdigit( $hex ) ) { + return null; + } + + return array( + (int) hexdec( substr( $hex, 0, 2 ) ), + (int) hexdec( substr( $hex, 2, 2 ) ), + (int) hexdec( substr( $hex, 4, 2 ) ), + ); + } +} \ No newline at end of file diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php index b779a552..884290b8 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/config.php @@ -22,16 +22,17 @@ class Memberful_Paywall_Config { */ public static function defaults(): array { return array( - 'mode' => 'builder', - 'layout' => 'card', - 'heading' => __( 'Subscribe to keep reading', 'memberful' ), - 'subheading' => __( 'This post is for paying subscribers.', 'memberful' ), - 'features' => array(), - 'button_label' => __( 'Subscribe', 'memberful' ), - 'subscribe_url' => '', - 'sign_in_url' => '', - 'brand_color' => '', - 'button_shape' => 'square', + 'mode' => 'builder', + 'layout' => 'card', + 'heading' => __( 'Subscribe to keep reading', 'memberful' ), + 'subheading' => __( 'This post is for paying subscribers.', 'memberful' ), + 'features' => array(), + 'button_label' => __( 'Subscribe', 'memberful' ), + 'subscribe_url' => '', + 'sign_in_url' => '', + 'brand_color' => '', + 'background_color' => '', + 'button_shape' => 'square', ); } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php index 8e063095..a7b12be7 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/renderer.php @@ -273,6 +273,12 @@ private static function wrapper_style( array $config ): string { $parts[] = '--memberful-brand:' . $brand_color; } + $background_color = isset( $config['background_color'] ) ? sanitize_hex_color( (string) $config['background_color'] ) : ''; + if ( ! empty( $background_color ) ) { + $parts[] = '--memberful-surface:' . $background_color; + $parts[] = '--memberful-text:' . Memberful_Paywall_Color::contrast_text_color( $background_color ); + } + return implode( ';', $parts ) . ';'; } diff --git a/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php index 967a6024..eab188dc 100644 --- a/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php +++ b/wordpress/wp-content/plugins/memberful-wp/src/paywall/sanitizer.php @@ -57,10 +57,12 @@ public static function sanitize( array $input, array $defaults ): array { } } - if ( isset( $input['brand_color'] ) ) { - $color = sanitize_hex_color( (string) $input['brand_color'] ); - if ( null !== $color && '' !== $color ) { - $clean['brand_color'] = $color; + foreach ( array( 'brand_color', 'background_color' ) as $color_key ) { + if ( isset( $input[ $color_key ] ) ) { + $color = sanitize_hex_color( (string) $input[ $color_key ] ); + if ( null !== $color && '' !== $color ) { + $clean[ $color_key ] = $color; + } } } diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css index 6f8c04ca..27786634 100755 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css @@ -502,6 +502,18 @@ Paywall Builder min-width: 80px; } +/* Accent + Background colours on one row */ +.memberful-paywall-builder__colors-row { + align-items: start; + display: flex; + flex-wrap: wrap; + gap: 32px; +} + +.memberful-paywall-builder__colors-row .wp-color-result.button { + margin: 0; +} + /* Benefits */ .memberful-paywall-builder__benefits-label { margin-bottom: 8px; diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css index e75b81ed..6643e57c 100644 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css @@ -5,11 +5,11 @@ .memberful-paywall { --memberful-border: #e5e5e5; --memberful-brand: var(--wp--preset--color--primary, var(--wp--preset--color--accent, var(--wp-admin-theme-color, #2271b1))); - --memberful-muted: #666; - --memberful-muted-soft: #999; --memberful-radius: 0; --memberful-surface: #fff; --memberful-text: #1a1a1a; + --memberful-muted: color-mix(in srgb, var(--memberful-text) 60%, transparent); + --memberful-muted-soft: color-mix(in srgb, var(--memberful-text) 40%, transparent); box-sizing: border-box; color: var(--memberful-text); @@ -137,11 +137,9 @@ border-top: 2px solid var(--memberful-brand); } -/* Card — centred white card on muted page. */ +/* Card - centred card surface on a fixed muted page. */ .memberful-paywall--card { - --memberful-surface: #f8f8f8; - - background: var(--memberful-surface); + background: #f8f8f8; } .memberful-paywall--card .memberful-paywall__inner { @@ -149,7 +147,7 @@ } .memberful-paywall--card .memberful-paywall__card { - background: #fff; + background: var(--memberful-surface); border: 1px solid #e0e0e0; border-radius: 0.5rem; box-shadow: 0 10px 25px rgba(15, 23, 42, 0.1); diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php index 9e7df61f..04e0359a 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -101,10 +101,44 @@

    -

    - - -

    + +
    +
    + + +
    +
    + + + +
    +

    From 66ede34c5d0593c0dbd4cd79b551425bfa6072c9 Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Thu, 14 May 2026 09:11:18 +1000 Subject: [PATCH 26/28] Adjust advanced settings --- .../memberful-wp/stylesheets/admin.css | 25 +++++++++++++++++ .../views/paywall/builder-panel.php | 27 ++++++++++++------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css index 27786634..14ea2d70 100755 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css @@ -567,6 +567,31 @@ Paywall Builder color: var(--wp-admin-theme-color-darker-10); } +/* Advanced settings */ +.memberful-paywall-builder__advanced { + border-top: 1px solid var(--wp-editor-canvas-background); + margin-top: 2rem; + padding-top: 1rem; +} +.memberful-paywall-builder__advanced-summary { + align-items: center; + cursor: pointer; + display: flex; + font-weight: 600; + justify-content: space-between; + margin-bottom: 1rem; +} +.memberful-paywall-builder__advanced-summary::-webkit-details-marker { + display: none; +} +.memberful-paywall-builder__advanced-chevron { + color: #646970; + transition: transform 150ms ease; +} +.memberful-paywall-builder__advanced[open] .memberful-paywall-builder__advanced-chevron { + transform: rotate(180deg); +} + .memberful-paywall-builder__preview-frame { background: #fff; border: 1px solid #dcdcde; diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php index 04e0359a..998d3b7c 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -140,17 +140,24 @@ -

    - - - -

    +
    + + + + -

    - - - -

    +

    + + + +

    + +

    + + + +

    +
    From 1c291585b09894ffe915b317dd4262b647efbc27 Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Thu, 14 May 2026 09:42:03 +1000 Subject: [PATCH 27/28] Adjust layout to be closer to the prototype --- .../memberful-wp/js/src/paywall-builder.js | 9 +- .../memberful-wp/stylesheets/admin.css | 92 ++++++++++++++----- .../views/paywall/builder-panel.php | 70 ++++++++------ 3 files changed, 122 insertions(+), 49 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js index 57fced03..03230a33 100644 --- a/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js +++ b/wordpress/wp-content/plugins/memberful-wp/js/src/paywall-builder.js @@ -71,7 +71,7 @@ jQuery(function ($) { sign_in_url: $('#memberful-paywall-signin-url').val() || '', brand_color: $('#memberful-paywall-brand-color').val() || '', background_color: $('#memberful-paywall-background-color').val() || '', - button_shape: $('#memberful-paywall-button-shape').val() || 'rounded', + button_shape: $('input[name="memberful_paywall[button_shape]"]:checked').val() || 'rounded', }; } @@ -154,6 +154,13 @@ jQuery(function ($) { refreshPreview(); }); + const $headingInput = $('#memberful-paywall-heading'); + const $headingCounter = $('[data-counter-for="memberful-paywall-heading"]'); + const headingMax = parseInt($headingCounter.attr('data-max'), 10) || 60; + $headingInput.on('input', function () { + $headingCounter.text(this.value.length + '/' + headingMax); + }); + if (($modeInputs.filter(':checked').val() || 'builder') === 'builder') { refreshPreview(); } diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css index 14ea2d70..d2b9b5ea 100755 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css @@ -299,7 +299,7 @@ Paywall Builder } .memberful-paywall-builder__mode-tabs { background: #fff; - border: 1px solid #c3c4c7; + border: 1px solid var(--wp-editor-canvas-background); border-radius: 4px; display: inline-flex; overflow: hidden; @@ -316,7 +316,7 @@ Paywall Builder } .memberful-paywall-builder__mode-tab span { background: #fff; - border-right: 1px solid #c3c4c7; + border-right: 1px solid var(--wp-editor-canvas-background); color: #2c3338; display: inline-block; font-size: 13px; @@ -328,11 +328,11 @@ Paywall Builder border-right: 0; } .memberful-paywall-builder__mode-tab input[type="radio"]:checked + span { - background: #2271b1; + background: var(--wp-admin-theme-color); color: #fff; } .memberful-paywall-builder__mode-tab input[type="radio"]:focus-visible + span { - box-shadow: inset 0 0 0 2px #2271b1; + box-shadow: inset 0 0 0 2px var(--wp-admin-theme-color); } /* Template picker cards */ @@ -371,11 +371,11 @@ Paywall Builder border-color: #8c8f94; } .memberful-paywall-builder__template-card input[type="radio"]:checked + .memberful-paywall-builder__template-card-inner { - border-color: #2271b1; - box-shadow: 0 0 0 1px #2271b1; + border-color: var(--wp-admin-theme-color); + box-shadow: 0 0 0 1px var(--wp-admin-theme-color); } .memberful-paywall-builder__template-card input[type="radio"]:checked + .memberful-paywall-builder__template-card-inner::after { - background: #2271b1 url("data:image/svg+xml;utf8,") center/12px no-repeat; + background: var(--wp-admin-theme-color) url("data:image/svg+xml;utf8,") center/12px no-repeat; border-radius: 50%; content: ""; height: 20px; @@ -385,7 +385,7 @@ Paywall Builder width: 20px; } .memberful-paywall-builder__template-card input[type="radio"]:focus-visible + .memberful-paywall-builder__template-card-inner { - box-shadow: 0 0 0 2px #2271b1; + box-shadow: 0 0 0 2px var(--wp-admin-theme-color); } .memberful-paywall-builder__template-thumb { @@ -406,19 +406,19 @@ Paywall Builder background: #f0f0f1; } .memberful-paywall-builder__thumb-line { - background: #c3c4c7; + background: var(--wp-editor-canvas-background); border-radius: 2px; height: 3px; width: 100px; } .memberful-paywall-builder__thumb-button { - background: #2271b1; + background: var(--wp-admin-theme-color); border-radius: 2px; height: 10px; width: 48px; } .memberful-paywall-builder__thumb-lock { - background: #2271b1 url("data:image/svg+xml;utf8,") center/18px no-repeat; + background: var(--wp-admin-theme-color) url("data:image/svg+xml;utf8,") center/18px no-repeat; border-radius: 50%; height: 26px; width: 26px; @@ -466,7 +466,8 @@ Paywall Builder margin: 0 0 1rem; } .memberful-paywall-builder__customize label, -.memberful-paywall-builder__benefits-label { +.memberful-paywall-builder__benefits-label, +.memberful-paywall-builder__button-shape-label { color: #6b7280; display: block; font-size: 13px; @@ -487,6 +488,19 @@ Paywall Builder font-size: 12px; margin-top: 4px; } +.memberful-paywall-builder__field-row { + align-items: baseline; + display: flex; + justify-content: space-between; + margin-bottom: 4px; +} +.memberful-paywall-builder__field-row label { + margin: 0; +} +.memberful-paywall-builder__counter { + color: #646970; + font-size: 12px; +} .memberful-paywall-builder__customize .memberful-paywall-builder__field--paired { align-items: start; display: grid; @@ -502,20 +516,54 @@ Paywall Builder min-width: 80px; } -/* Accent + Background colours on one row */ -.memberful-paywall-builder__colors-row { - align-items: start; - display: flex; - flex-wrap: wrap; - gap: 32px; +/* Accent + Background colours */ +.memberful-paywall-builder__field .wp-color-result.button { + margin: 0; } -.memberful-paywall-builder__colors-row .wp-color-result.button { - margin: 0; +/* Button shape controls */ +.memberful-paywall-builder__segmented { + border: 1px solid var(--wp-editor-canvas-background); + border-radius: 6px; + display: inline-flex; + padding: 4px; +} +.memberful-paywall-builder__segmented-option { + cursor: pointer; + margin: 0 !important; +} +.memberful-paywall-builder__segmented-option input[type="radio"] { + opacity: 0; + pointer-events: none; + position: absolute; +} +.memberful-paywall-builder__segmented-option-inner { + align-items: center; + border-radius: 4px; + color: #646970; + display: inline-flex; + font-size: 12px; + gap: 6px; + padding: 5px 14px 5px 10px; + transition: background-color 120ms ease, color 120ms ease; +} +.memberful-paywall-builder__segmented-option:hover .memberful-paywall-builder__segmented-option-inner { + color: #1d2327; +} +.memberful-paywall-builder__segmented-option input[type="radio"]:checked + .memberful-paywall-builder__segmented-option-inner { + background: var(--wp-admin-theme-color); + color: #fff; +} +.memberful-paywall-builder__segmented-option input[type="radio"]:focus-visible + .memberful-paywall-builder__segmented-option-inner { + box-shadow: 0 0 0 2px var(--wp-admin-theme-color); +} +.memberful-paywall-builder__segmented-option-inner svg { + flex: 0 0 auto; } /* Benefits */ -.memberful-paywall-builder__benefits-label { +.memberful-paywall-builder__benefits-label, +.memberful-paywall-builder__button-shape-label { margin-bottom: 8px; padding: 0; } @@ -570,7 +618,7 @@ Paywall Builder /* Advanced settings */ .memberful-paywall-builder__advanced { border-top: 1px solid var(--wp-editor-canvas-background); - margin-top: 2rem; + margin-top: 1.5rem; padding-top: 1rem; } .memberful-paywall-builder__advanced-summary { diff --git a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php index 998d3b7c..f3dc5c18 100644 --- a/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php +++ b/wordpress/wp-content/plugins/memberful-wp/views/paywall/builder-panel.php @@ -46,8 +46,11 @@

    - - + + + /60 + +

    @@ -86,20 +89,37 @@ -

    -

    - - -

    -

    - - -

    -
    +

    + + +

    + +
    + +
    + + + +
    +
    -
    -
    - - -
    -
    - - - -
    +
    + + +
    +
    + + +
    From 3ad49f9763d45bd6ceeb7e031ad978cec35f7a3b Mon Sep 17 00:00:00 2001 From: Anton Vanyukov Date: Thu, 14 May 2026 09:53:58 +1000 Subject: [PATCH 28/28] Final adjustments --- .../plugins/memberful-wp/stylesheets/admin.css | 2 +- .../plugins/memberful-wp/stylesheets/paywall.css | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css index d2b9b5ea..31e93b07 100755 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css @@ -564,7 +564,7 @@ Paywall Builder /* Benefits */ .memberful-paywall-builder__benefits-label, .memberful-paywall-builder__button-shape-label { - margin-bottom: 8px; + margin-bottom: 6px; padding: 0; } .memberful-paywall-builder__benefit-list { diff --git a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css index 6643e57c..1981f55f 100644 --- a/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css +++ b/wordpress/wp-content/plugins/memberful-wp/stylesheets/paywall.css @@ -9,7 +9,6 @@ --memberful-surface: #fff; --memberful-text: #1a1a1a; --memberful-muted: color-mix(in srgb, var(--memberful-text) 60%, transparent); - --memberful-muted-soft: color-mix(in srgb, var(--memberful-text) 40%, transparent); box-sizing: border-box; color: var(--memberful-text); @@ -42,7 +41,7 @@ } .memberful-paywall__heading { - font-size: var(--wp--preset--font-size--medium, 1.125rem); + font-size: var(--wp--preset--font-size--large, 1.375rem); } .memberful-paywall__subheading { @@ -109,7 +108,7 @@ } .memberful-paywall__signin { - color: var(--memberful-muted-soft); + color: var(--memberful-muted); font-size: 0.75rem; margin: var(--wp--preset--spacing--30, 1rem) 0 0 !important; /* fixes compatibility with some themes */ } @@ -138,10 +137,6 @@ } /* Card - centred card surface on a fixed muted page. */ -.memberful-paywall--card { - background: #f8f8f8; -} - .memberful-paywall--card .memberful-paywall__inner { padding: var(--wp--preset--spacing--40, 1.5rem); } @@ -149,10 +144,10 @@ .memberful-paywall--card .memberful-paywall__card { background: var(--memberful-surface); border: 1px solid #e0e0e0; - border-radius: 0.5rem; - box-shadow: 0 10px 25px rgba(15, 23, 42, 0.1); + border-radius: 1rem; + box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.1) 0px 4px 6px -4px; margin: 0 auto; - max-width: 24rem; + max-width: 34rem; padding: var(--wp--preset--spacing--50, 2rem); }