|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * WooCommerce Content Gate metering countdown banner. |
| 4 | + * |
| 5 | + * @package Newspack |
| 6 | + */ |
| 7 | + |
| 8 | +namespace Newspack; |
| 9 | + |
| 10 | +/** |
| 11 | + * WooCommerce Content Gate metering countdown banner class. |
| 12 | + */ |
| 13 | +class Metering_Countdown { |
| 14 | + |
| 15 | + const OPTION_PREFIX = 'np_countdown_banner_'; |
| 16 | + |
| 17 | + /** |
| 18 | + * Initialize hooks. |
| 19 | + */ |
| 20 | + public static function init() { |
| 21 | + add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue_assets' ] ); |
| 22 | + add_action( 'wp_footer', [ __CLASS__, 'print_cta' ] ); |
| 23 | + add_filter( 'body_class', [ __CLASS__, 'filter_body_class' ] ); |
| 24 | + add_filter( 'newspack_ads_placement_data', [ __CLASS__, 'filter_ads_placement_data' ], 10, 2 ); |
| 25 | + } |
| 26 | + |
| 27 | + /** |
| 28 | + * Get all settings with default values for the countdown banner. |
| 29 | + * |
| 30 | + * @return array Default countdown settings. |
| 31 | + */ |
| 32 | + public static function get_default_settings() { |
| 33 | + return [ |
| 34 | + 'enabled' => false, |
| 35 | + 'style' => 'light', |
| 36 | + 'cta_label' => __( 'Subscribe now and get unlimited access.', 'newspack-plugin' ), |
| 37 | + 'button_label' => __( 'Subscribe now', 'newspack-plugin' ), |
| 38 | + 'cta_url' => '', |
| 39 | + ]; |
| 40 | + } |
| 41 | + |
| 42 | + /** |
| 43 | + * Get all settings for the countdown banner. |
| 44 | + * |
| 45 | + * @param string $key Optional key to get a specific setting. If not provided, all settings will be returned. |
| 46 | + * |
| 47 | + * @return array|mixed Countdown banner settings, or a specific setting if a key is provided. |
| 48 | + */ |
| 49 | + public static function get_settings( $key = null ) { |
| 50 | + $settings = self::get_default_settings(); |
| 51 | + if ( $key && isset( $settings[ $key ] ) ) { |
| 52 | + return get_option( self::OPTION_PREFIX . $key, $settings[ $key ] ); |
| 53 | + } |
| 54 | + foreach ( $settings as $key => $value ) { |
| 55 | + $settings[ $key ] = get_option( self::OPTION_PREFIX . $key, $value ); |
| 56 | + } |
| 57 | + return $settings; |
| 58 | + } |
| 59 | + |
| 60 | + /** |
| 61 | + * Sanitize a setting. |
| 62 | + * |
| 63 | + * @param string $key The setting key. |
| 64 | + * @param mixed $value The setting value. |
| 65 | + * |
| 66 | + * @return mixed The sanitized setting value or WP_Error if setting key is invalid. |
| 67 | + */ |
| 68 | + public static function sanitize_setting( $key, $value ) { |
| 69 | + $default_settings = self::get_default_settings(); |
| 70 | + if ( ! isset( $default_settings[ $key ] ) ) { |
| 71 | + // translators: %s is the setting key. |
| 72 | + return new \WP_Error( 'newspack_countdown_banner_invalid_setting', sprintf( __( 'Invalid setting key: %s.', 'newspack-plugin' ), $key ) ); |
| 73 | + } |
| 74 | + if ( $key === 'style' && ! in_array( $value, [ 'light', 'dark' ], true ) ) { |
| 75 | + return $default_settings[ $key ]; |
| 76 | + } |
| 77 | + if ( $key === 'cta_url' ) { |
| 78 | + return sanitize_url( $value ); |
| 79 | + } |
| 80 | + if ( is_bool( $default_settings[ $key ] ) ) { |
| 81 | + return boolval( $value ); |
| 82 | + } |
| 83 | + return sanitize_text_field( $value ); |
| 84 | + } |
| 85 | + |
| 86 | + /** |
| 87 | + * Update settings for the countdown banner. |
| 88 | + * |
| 89 | + * @param array $settings New countdown settings. |
| 90 | + * |
| 91 | + * @return array|\WP_Error Updated countdown settings or error if update fails. |
| 92 | + */ |
| 93 | + public static function update_settings( $settings ) { |
| 94 | + $current_settings = self::get_settings(); |
| 95 | + foreach ( $settings as $key => $value ) { |
| 96 | + $sanitized = self::sanitize_setting( $key, $value ); |
| 97 | + if ( is_wp_error( $sanitized ) ) { |
| 98 | + return $sanitized; |
| 99 | + } |
| 100 | + if ( $sanitized === $current_settings[ $key ] ) { |
| 101 | + continue; |
| 102 | + } |
| 103 | + $updated = update_option( self::OPTION_PREFIX . $key, $sanitized ); |
| 104 | + if ( ! $updated ) { |
| 105 | + return new \WP_Error( 'newspack_countdown_banner_update_failed', __( 'Failed to update countdown banner settings.', 'newspack-plugin' ) ); |
| 106 | + } |
| 107 | + $current_settings[ $key ] = $sanitized; |
| 108 | + } |
| 109 | + return $current_settings; |
| 110 | + } |
| 111 | + |
| 112 | + /** |
| 113 | + * Whether the countdown banner should be displayed. |
| 114 | + * |
| 115 | + * @return bool |
| 116 | + */ |
| 117 | + public static function is_enabled() { |
| 118 | + return self::get_settings( 'enabled' ) && Metering::is_metering() && is_singular(); |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * Enqueue assets. |
| 123 | + */ |
| 124 | + public static function enqueue_assets() { |
| 125 | + // Enqueue assets only if enabled and the post is metered. |
| 126 | + if ( ! self::is_enabled() ) { |
| 127 | + return; |
| 128 | + } |
| 129 | + $asset = require_once dirname( NEWSPACK_PLUGIN_FILE ) . '/dist/content-banner.asset.php'; |
| 130 | + |
| 131 | + // Ensure the content gate metering script is enqueued first. |
| 132 | + $asset['dependencies'][] = 'newspack-content-gate-metering'; |
| 133 | + wp_enqueue_script( 'newspack-content-banner', Newspack::plugin_url() . '/dist/content-banner.js', $asset['dependencies'], NEWSPACK_PLUGIN_VERSION, true ); |
| 134 | + wp_enqueue_style( 'newspack-content-banner', Newspack::plugin_url() . '/dist/content-banner.css', [], NEWSPACK_PLUGIN_VERSION ); |
| 135 | + } |
| 136 | + |
| 137 | + /** |
| 138 | + * Print the subscribe button. |
| 139 | + */ |
| 140 | + public static function print_subscribe_button() { |
| 141 | + if ( ! class_exists( 'Newspack_Blocks' ) || ! class_exists( 'Newspack_Blocks\Modal_Checkout' ) || ! class_exists( 'Newspack_Blocks\Modal_Checkout\Checkout_Data' ) || ! function_exists( 'wc_get_product' ) ) { |
| 142 | + return; |
| 143 | + } |
| 144 | + $settings = self::get_settings(); |
| 145 | + $button_label = $settings['button_label']; |
| 146 | + $button_class = 'dark' === $settings['style'] ? 'newspack-ui__button--primary-light' : 'newspack-ui__button--accent'; |
| 147 | + |
| 148 | + $cta_url = $settings['cta_url']; |
| 149 | + if ( $cta_url ) { |
| 150 | + ?> |
| 151 | + <a href="<?php echo esc_url( $cta_url ); ?>" class="newspack-ui__button newspack-ui__button--x-small <?php echo esc_attr( $button_class ); ?>"><?php echo esc_html( $button_label ); ?></a> |
| 152 | + <?php |
| 153 | + return; |
| 154 | + } |
| 155 | + |
| 156 | + // If CTA url is not provided, try a modal checkout using the primary subscription tier product. |
| 157 | + $product = Subscriptions_Tiers::get_primary_subscription_tier_product(); |
| 158 | + if ( ! $product ) { |
| 159 | + return; |
| 160 | + } |
| 161 | + \Newspack_Blocks\Modal_Checkout::enqueue_modal( $product->get_id() ); |
| 162 | + \Newspack_Blocks::enqueue_view_assets( 'checkout-button' ); |
| 163 | + $checkout_data = \Newspack_Blocks\Modal_Checkout\Checkout_Data::get_checkout_data( $product ); |
| 164 | + ?> |
| 165 | + <div class="wp-block-newspack-blocks-checkout-button"> |
| 166 | + <form data-checkout="<?php echo esc_attr( wp_json_encode( $checkout_data ) ); ?>" target="newspack_modal_checkout_iframe"> |
| 167 | + <input type="hidden" name="newspack_checkout" value="1" /> |
| 168 | + <input type="hidden" name="modal_checkout" value="1" /> |
| 169 | + <input type="hidden" name="product_id" value="<?php echo esc_attr( $product->get_id() ); ?>" /> |
| 170 | + <button type="submit" class="newspack-ui__button newspack-ui__button--x-small <?php echo esc_attr( $button_class ); ?>"><?php echo esc_html( $button_label ); ?></button> |
| 171 | + </form> |
| 172 | + </div> |
| 173 | + <?php |
| 174 | + } |
| 175 | + |
| 176 | + /** |
| 177 | + * Print the countdown banner. |
| 178 | + * |
| 179 | + * @return void |
| 180 | + */ |
| 181 | + public static function print_cta() { |
| 182 | + if ( ! self::is_enabled() ) { |
| 183 | + return; |
| 184 | + } |
| 185 | + $settings = self::get_settings(); |
| 186 | + $style_class = sprintf( 'is-style-%s', $settings['style'] ); |
| 187 | + $classes = [ $style_class ]; |
| 188 | + $total_views = Metering::get_total_metered_views( \is_user_logged_in() ); |
| 189 | + if ( false === $total_views ) { |
| 190 | + return; |
| 191 | + } |
| 192 | + $views = Metering::get_current_user_metered_views(); |
| 193 | + if ( $views === 0 || Metering::is_frontend_metering() ) { |
| 194 | + $classes[] = 'newspack-countdown-banner__cta--hidden'; |
| 195 | + } |
| 196 | + $metering_settings = Metering::get_metering_settings( Content_Gate::get_gate_post_id() ); |
| 197 | + $registered_count = $metering_settings['registered_count']; |
| 198 | + ?> |
| 199 | + <div class="newspack-ui"> |
| 200 | + <div class="banner newspack-countdown-banner__cta <?php echo esc_attr( implode( ' ', $classes ) ); ?>"> |
| 201 | + <div class="wrapper newspack-countdown-banner__cta__content"> |
| 202 | + <div class="newspack-countdown-banner__cta__content__wrapper"> |
| 203 | + <span class="newspack-countdown-banner__cta__content__countdown newspack-ui__font--s"> |
| 204 | + <strong> |
| 205 | + <?php |
| 206 | + echo wp_kses_post( |
| 207 | + /** |
| 208 | + * Filter the countdown message that shows how many metered articles the user has viewed. |
| 209 | + * Sanitized via wp_kses_post, so basic HTML is allowed. |
| 210 | + * |
| 211 | + * @param string $message The countdown message HTML string. |
| 212 | + * @param int $views The current number of metered views. |
| 213 | + * @param int $total_views The total number of allowed views per period. |
| 214 | + * @param string $metering_period The metering period. |
| 215 | + * @return string The filtered countdown message HTML string. |
| 216 | + */ |
| 217 | + apply_filters( |
| 218 | + 'newspack_countdown_banner_countdown_message', |
| 219 | + sprintf( |
| 220 | + /* translators: 1: current number of metered views, 2: total metered views, 3: the metering period. */ |
| 221 | + __( '<span class="newspack-countdown-banner__views">%1$d</span>/<span class="newspack-countdown-banner__total_views">%2$d</span> free articles this %3$s', 'newspack-plugin' ), |
| 222 | + $views, |
| 223 | + $total_views, |
| 224 | + Metering::get_metering_period() |
| 225 | + ), |
| 226 | + $views, |
| 227 | + $total_views, |
| 228 | + Metering::get_metering_period() |
| 229 | + ) |
| 230 | + ); |
| 231 | + ?> |
| 232 | + </strong> |
| 233 | + </span> |
| 234 | + <span class="newspack-countdown-banner__cta__content__message newspack-ui__font--xs"> |
| 235 | + <?php echo esc_html( $settings['cta_label'] ); ?> |
| 236 | + <?php if ( ! \is_user_logged_in() ) : ?> |
| 237 | + <?php if ( $registered_count > 0 ) : ?> |
| 238 | + <a href="#register_modal"><?php echo esc_html( __( 'Create an account', 'newspack-plugin' ) ); ?></a>. |
| 239 | + <?php else : ?> |
| 240 | + <a href="#signin_modal"><?php echo esc_html( __( 'Sign in to an existing account', 'newspack-plugin' ) ); ?></a>. |
| 241 | + <?php endif; ?> |
| 242 | + <?php endif; ?> |
| 243 | + </span> |
| 244 | + </div> |
| 245 | + <?php self::print_subscribe_button(); ?> |
| 246 | + </div> |
| 247 | + </div> |
| 248 | + </div> |
| 249 | + <?php |
| 250 | + } |
| 251 | + |
| 252 | + /** |
| 253 | + * Filter the body class. |
| 254 | + * |
| 255 | + * @param array $classes The body classes. |
| 256 | + * |
| 257 | + * @return array The filtered body classes. |
| 258 | + */ |
| 259 | + public static function filter_body_class( $classes ) { |
| 260 | + if ( self::is_enabled() ) { |
| 261 | + $classes[] = 'newspack-has-countdown-banner'; |
| 262 | + } |
| 263 | + return $classes; |
| 264 | + } |
| 265 | + |
| 266 | + /** |
| 267 | + * Disable the sticky footer ad placement when rendering the countdown banner. |
| 268 | + * |
| 269 | + * @param array $data The ads placement data. |
| 270 | + * @param string $placement_key The placement key. |
| 271 | + * |
| 272 | + * @return array The filtered ads placement data. |
| 273 | + */ |
| 274 | + public static function filter_ads_placement_data( $data, $placement_key ) { |
| 275 | + if ( ! self::is_enabled() ) { |
| 276 | + return $data; |
| 277 | + } |
| 278 | + if ( $placement_key === 'sticky' ) { |
| 279 | + $data['enabled'] = false; |
| 280 | + } |
| 281 | + return $data; |
| 282 | + } |
| 283 | +} |
| 284 | +Metering_Countdown::init(); |
0 commit comments