Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions includes/class-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
defined( 'ABSPATH' ) || exit;

use Newspack\Optional_Modules\Collections;
use Newspack\Memberships;
use Newspack\Memberships\Metering;
Comment on lines +13 to +14
Copy link
Member

@miguelpeixe miguelpeixe Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is superfluous as we're already in the Newspack namespace.

We can keep Newspack\Memberships\Metering, but I don't think it's bad to use Memberships\Metering in the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels cleaner to use just Metering in the code imo, so I'll keep it as is. Happy to change it if you feel strongly about it though!


/**
* Newspack Blocks Class.
Expand All @@ -20,6 +22,7 @@ final class Blocks {
*/
public static function init() {
require_once NEWSPACK_ABSPATH . 'src/blocks/reader-registration/index.php';
require_once NEWSPACK_ABSPATH . 'src/blocks/content-gate-countdown/class-content-gate-countdown-block.php';

if ( wp_is_block_theme() && class_exists( 'Newspack\Corrections' ) ) {
require_once NEWSPACK_ABSPATH . 'src/blocks/correction-box/class-correction-box-block.php';
Expand Down Expand Up @@ -49,22 +52,32 @@ public static function enqueue_block_editor_assets() {
NEWSPACK_PLUGIN_VERSION,
true
);
$script_data = [
'has_newsletters' => class_exists( 'Newspack_Newsletters_Subscription' ),
'has_reader_activation' => Reader_Activation::is_enabled(),
'newsletters_url' => Wizards::get_wizard( 'newsletters' )->newsletters_settings_url(),
'has_google_oauth' => Google_OAuth::is_oauth_configured(),
'google_logo_svg' => \Newspack\Newspack_UI_Icons::get_svg( 'google' ),
'reader_activation_terms' => Reader_Activation::get_setting( 'terms_text' ),
'reader_activation_url' => Reader_Activation::get_setting( 'terms_url' ),
'has_recaptcha' => Recaptcha::can_use_captcha(),
'recaptcha_url' => admin_url( 'admin.php?page=newspack-settings' ),
'corrections_enabled' => wp_is_block_theme() && class_exists( 'Newspack\Corrections' ),
'collections_enabled' => Collections::is_module_active(),
'has_memberships' => Memberships::is_active(),
];
if ( $script_data['has_memberships'] ) {
$script_data['content_gate_data'] = [
'anonymous_metered_views' => Metering::get_total_metered_views( false ),
'loggedin_metered_views' => Metering::get_total_metered_views( true ),
'metered_views' => Metering::get_metered_views(),
'metering_period' => Metering::get_metering_period(),
];
}
\wp_localize_script(
'newspack-blocks',
'newspack_blocks',
[
'has_newsletters' => class_exists( 'Newspack_Newsletters_Subscription' ),
'has_reader_activation' => Reader_Activation::is_enabled(),
'newsletters_url' => Wizards::get_wizard( 'newsletters' )->newsletters_settings_url(),
'has_google_oauth' => Google_OAuth::is_oauth_configured(),
'google_logo_svg' => \Newspack\Newspack_UI_Icons::get_svg( 'google' ),
'reader_activation_terms' => Reader_Activation::get_setting( 'terms_text' ),
'reader_activation_url' => Reader_Activation::get_setting( 'terms_url' ),
'has_recaptcha' => Recaptcha::can_use_captcha(),
'recaptcha_url' => admin_url( 'admin.php?page=newspack-settings' ),
'corrections_enabled' => wp_is_block_theme() && class_exists( 'Newspack\Corrections' ),
'collections_enabled' => Collections::is_module_active(),
]
$script_data
);
\wp_enqueue_style(
'newspack-blocks',
Expand Down
23 changes: 23 additions & 0 deletions includes/plugins/wc-memberships/class-memberships.php
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,29 @@ public static function get_restricted_post_excerpt( $post ) {
$content = explode( '<!--more-->', $content )[0];
} else {
$count = (int) get_post_meta( $gate_post_id, 'visible_paragraphs', true );
// Remove all spaces.
$content = preg_replace( '/\s+/', ' ', $content );
/**
* Filter the list of blocks to exclude from the excerpt.
*
* @param array $excluded_blocks Array of blocks to exclude. i.e. [ 'core/image', 'newspack/content-gate-countdown' ].
*
* @return array
*/
$excluded_blocks = apply_filters( 'newspack_memberships_excerpt_excluded_blocks', [ 'newspack/content-gate-countdown' ] );
// Remove unwanted blocks from the content.
foreach ( $excluded_blocks as $block ) {
[ $category, $name ] = explode( '/', $block );
if ( ! $category || ! $name ) {
continue;
}
if ( 'core' === $category ) {
$regex = $name;
} else {
$regex = "$category\/$name";
}
$content = preg_replace( "/<!-- wp:$regex {?.*?}? -->.*?<!-- \/wp:$regex -->/s", '', $content );
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't filter this, any inner paragraph blocks within the content gate block will appear in the excerpt despite us explicitly returning an empty string in the render callback.

// Split into paragraphs.
$content = explode( '</p>', $content );
// Extract the first $x paragraphs only.
Expand Down
60 changes: 60 additions & 0 deletions includes/plugins/wc-memberships/class-metering.php
Original file line number Diff line number Diff line change
Expand Up @@ -298,5 +298,65 @@ public static function get_article_view( $activity ) {
self::$article_view = $activity;
return $activity;
}

/**
* Get the metering period for a post.
*
* @param int|null $post_id Post ID. Default is current post.
*
* @return string Metered period (day, week, month).
*/
public static function get_metering_period( $post_id = null ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$gate_post_id = Memberships::get_gate_post_id( $post_id );
return \get_post_meta( $gate_post_id, 'metering_period', true );
}

/**
* Get number of metered views for the user.
*
* @param int|null $user_id User ID. Default is current user.
*
* @return int Number of metered views.
*/
public static function get_metered_views( $user_id = null ) {
if ( ! $user_id ) {
$user_id = get_current_user_id();
}
// For anonymous users, return the anonymous count.
if ( ! $user_id ) {
$gate_post_id = Memberships::get_gate_post_id();
return (int) \get_post_meta( $gate_post_id, 'metering_anonymous_count', true );
}

// For logged-in users, calculate the remaining views based on their metering data.
$gate_post_id = Memberships::get_gate_post_id();
$user_meta_key = self::METERING_META_KEY . '_' . $gate_post_id;
$user_metering_data = \get_user_meta( $user_id, $user_meta_key, true );
if ( ! is_array( $user_metering_data ) || ! isset( $user_metering_data['content'] ) ) {
return $count;
}
return count( $user_metering_data['content'] );
}

/**
* Get total number of metered views for current post.
*
* @param boolean $is_logged_in Whether to check for logged-in or anonymous users. Default is false (anonymous).
*
* @return int|boolean Total number of metered views if metering is enabled, otherwise false.
*/
public static function get_total_metered_views( $is_logged_in = false ) {
$gate_post_id = Memberships::get_gate_post_id( get_the_ID() );
if ( ! $gate_post_id ) {
return false;
}
if ( ! $is_logged_in ) {
return (int) \get_post_meta( $gate_post_id, 'metering_anonymous_count', true );
}
return (int) \get_post_meta( $gate_post_id, 'metering_registered_count', true );
}
}
Metering::init();
1 change: 1 addition & 0 deletions packages/icons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ export { default as searchEmpty } from './src/search-empty';
export { default as tabItem } from './src/tab-item';
export { default as tabs } from './src/tabs';
export { default as theme } from './src/theme';
export { default as countdown } from './src/content-gate-countdown';
13 changes: 13 additions & 0 deletions packages/icons/src/content-gate-countdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* WordPress dependencies
*/
import { SVG, Path } from '@wordpress/primitives';

const countdown = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<Path d="M19 6.5H5a.5.5 0 0 0-.5.5v10a.5.5 0 0 0 .5.5h14a.5.5 0 0 0 .5-.5V7a.5.5 0 0 0-.5-.5ZM5 5h14a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2Z" />
<Path d="M13.202 15V9.4h1.008l2.392 3.616V9.4h1.024V15h-.84l-2.56-3.84V15h-1.024ZM9.147 15.68 11.29 9.4h1l-2.144 6.28h-1ZM6.91 15v-4.152a2.303 2.303 0 0 1-.332.176 1.63 1.63 0 0 1-.372.112l-.088-.864a1.18 1.18 0 0 0 .384-.172c.141-.09.272-.197.392-.32.12-.125.205-.252.256-.38h.784V15H6.91Z" />
</SVG>
);

export default countdown;
39 changes: 39 additions & 0 deletions src/blocks/content-gate-countdown/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "newspack/content-gate-countdown",
"category": "newspack",
"attributes": {
"text": {
"type": "string",
"default": ""
}
},
"supports": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the design, it seems that the block should also support align, so it can also render wide and full width.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Added in c9ed5ab

"align": [ "wide", "full" ],
"html": false,
"color": {
"link": true,
"__experimentalDefaultControls": {
"background": true,
"text": true
}
},
"typography": {
"fontSize": true,
"lineHeight": true,
"textAlign": true,
"__experimentalFontFamily": true,
"__experimentalTextDecoration": true,
"__experimentalFontStyle": true,
"__experimentalFontWeight": true,
"__experimentalLetterSpacing": true,
"__experimentalTextTransform": true,
"__experimentalWritingMode": true,
"__experimentalDefaultControls": {
"fontSize": true
}
}
},
"textdomain": "newspack-plugin"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php
/**
* Content Gate Countdown Block
*
* @package Newspack
*/

namespace Newspack;

defined( 'ABSPATH' ) || exit;

use Newspack\Memberships;
use Newspack\Memberships\Metering;

/**
* Content Gate Countdown Block class.
*/
class Content_Gate_Countdown_Block {
/**
* Initialize the block.
*/
public static function init() {
add_action( 'init', [ __CLASS__, 'register_block' ] );
add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue_scripts' ] );
}

/**
* Enqueue block scripts and styles.
*
* @return void
*/
public static function enqueue_scripts() {
if ( ! Memberships::is_active() || ! is_singular() ) {
return;
}
wp_enqueue_script(
'newspack-content-gate-countdown-block',
\Newspack\Newspack::plugin_url() . '/dist/content-gate-countdown-block.js',
[ 'wp-i18n', 'newspack-memberships-gate-metering' ],
NEWSPACK_PLUGIN_VERSION,
true
);
}

/**
* Register the block.
*/
public static function register_block() {
register_block_type_from_metadata(
__DIR__ . '/block.json',
[
'render_callback' => [ __CLASS__, 'render_block' ],
]
);
}

/**
* Block render callback.
*
* @param array $attributes The block attributes.
* @param string $content The block content.
*
* @return string The block HTML.
*/
public static function render_block( array $attributes, string $content ) {
if ( ! Metering::is_metering() || ! Memberships::is_post_restricted() ) {
return '';
}
$total_views = Metering::get_total_metered_views( \is_user_logged_in() );
if ( false === $total_views ) {
return '';
}
$views = Metering::get_metered_views( get_current_user_id() );
$countdown = sprintf(
/* translators: 1: current number of metered views, 2: total metered views. */
__( '%1$d/%2$d', 'newspack-plugin' ),
$views,
$total_views
);
$text = isset( $attributes['text'] ) ? esc_html( $attributes['text'] ) : '';
if ( empty( $text ) ) {
$text = sprintf(
/* translators: %s - metered content period (week, month, etc. */
__(
'free articles this %s',
'newspack-plugin'
),
Metering::get_metering_period()
);
}
$block_wrapper_attributes = get_block_wrapper_attributes(
[
'class' => 'newspack-content-gate-countdown__wrapper',
]
);
$block_content = "<div $block_wrapper_attributes>
<div class='newspack-content-gate-countdown__content'>
<div class='newspack-content-gate-countdown__text'>
<span class='newspack-content-gate-countdown__countdown'>$countdown</span>
<p>$text</p>
</div>
$content
</div>
</div>";

return $block_content;
}
}

Content_Gate_Countdown_Block::init();
Loading