Skip to content

Commit 1e5718b

Browse files
authored
feat(content-gating): enable content rule exclusions (#4346)
* refactor(content-gating): separate Newspack & Memberships content gate CPTs * fix: update Content Gates UI * fix: allow Memberships sidebar to appear in editor * feat: allow content gates to be trashed and restored * feat: implement gate status controls * perf: only fetch published gates on the front-end * test: add unit tests for content gate methods * feat: content rule exclusion logic and tests * fix: remove unnecessary props from ContentRuleControlTaxonomy * fix: remove unnecessary meta fields and taxonomy support * test: add unit test for post with no categories
1 parent e52ab43 commit 1e5718b

File tree

6 files changed

+300
-25
lines changed

6 files changed

+300
-25
lines changed

includes/content-gate/class-content-restriction-control.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ public static function get_post_gates( $post_id = null ) {
121121
}
122122

123123
foreach ( $content_rules as $content_rule ) {
124+
$is_exclusion = isset( $content_rule['exclusion'] ) && $content_rule['exclusion'];
124125
if ( $content_rule['slug'] === 'post_types' ) {
125126
$post_type = get_post_type( $post_id );
126-
if ( ! in_array( $post_type, $content_rule['value'], true ) ) {
127+
if ( $is_exclusion ? in_array( $post_type, $content_rule['value'], true ) : ! in_array( $post_type, $content_rule['value'], true ) ) {
127128
continue 2;
128129
}
129130
} else {
@@ -132,10 +133,10 @@ public static function get_post_gates( $post_id = null ) {
132133
continue 2;
133134
}
134135
$terms = wp_get_post_terms( $post_id, $content_rule['slug'], [ 'fields' => 'ids' ] );
135-
if ( ! $terms || is_wp_error( $terms ) ) {
136+
if ( ( ! $is_exclusion && ! $terms ) || is_wp_error( $terms ) ) {
136137
continue 2;
137138
}
138-
if ( empty( array_intersect( $terms, $content_rule['value'] ) ) ) {
139+
if ( $is_exclusion ? ! empty( array_intersect( $terms, $content_rule['value'] ) ) : empty( array_intersect( $terms, $content_rule['value'] ) ) ) {
139140
continue 2;
140141
}
141142
}

includes/wizards/audience/class-audience-content-gates.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,9 @@ public function register_api_endpoints() {
242242
'items' => [
243243
'type' => 'object',
244244
'properties' => [
245-
'slug' => [ 'type' => 'string' ],
246-
'value' => [ 'type' => 'mixed' ],
245+
'slug' => [ 'type' => 'string' ],
246+
'value' => [ 'type' => 'mixed' ],
247+
'exclusion' => [ 'type' => 'boolean' ],
247248
],
248249
],
249250
],
@@ -388,12 +389,18 @@ public function sanitize_content_rule( $content_rule ) {
388389
}
389390
}
390391

391-
$value = array_values( array_filter( array_map( 'sanitize_text_field', $content_rule['value'] ) ) );
392+
$value = array_values( array_filter( array_map( 'sanitize_text_field', $content_rule['value'] ) ) );
393+
$exclusion = isset( $content_rule['exclusion'] ) ? boolval( $content_rule['exclusion'] ) : false;
392394

393-
return [
395+
$sanitized_rule = [
394396
'slug' => $slug,
395397
'value' => $value,
396398
];
399+
if ( $exclusion ) {
400+
$sanitized_rule['exclusion'] = $exclusion;
401+
}
402+
403+
return $sanitized_rule;
397404
}
398405

399406
/**

src/wizards/audience/views/content-gates/content-rule-control.tsx

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,16 @@ import { CheckboxControl } from '@wordpress/components';
1414
import ContentRuleControlTaxonomy from './content-rule-control-taxonomy';
1515
import { FormTokenField } from '../../../../../packages/components/src';
1616

17-
const noop = () => {};
18-
19-
export default function ContentRuleControl( { slug, value, onChange }: GateContentRuleControlProps ) {
17+
export default function ContentRuleControl( { slug, value, exclusion, onChange, onChangeExclusion }: GateContentRuleControlProps ) {
2018
const rule = window.newspackAudienceContentGates.available_content_rules[ slug ];
2119

2220
if ( ! rule || ! Array.isArray( value ) ) {
2321
return null;
2422
}
2523

26-
if ( rule.options && rule.options.length > 0 ) {
27-
return (
28-
<div className="newspack-content-gates__content-rule-control">
24+
return (
25+
<div className="newspack-content-gates__content-rule-control">
26+
{ rule.options && rule.options.length > 0 ? (
2927
<FormTokenField
3028
label={ rule.name }
3129
value={ rule.options.filter( o => value.includes( o.value ) ).map( o => o.label ) }
@@ -34,15 +32,15 @@ export default function ContentRuleControl( { slug, value, onChange }: GateConte
3432
__experimentalExpandOnFocus
3533
__next40pxDefaultSize
3634
/>
37-
<CheckboxControl
38-
label={ __( 'Exclusion rule', 'newspack-plugin' ) }
39-
help={ __( 'Apply this rule to everything EXCEPT the items matching the above.', 'newspack-plugin' ) }
40-
checked={ false }
41-
onChange={ noop }
42-
/>
43-
</div>
44-
);
45-
}
46-
47-
return <ContentRuleControlTaxonomy slug={ slug } value={ value } onChange={ onChange } />;
35+
) : (
36+
<ContentRuleControlTaxonomy slug={ slug } value={ value } onChange={ onChange } />
37+
) }
38+
<CheckboxControl
39+
label={ __( 'Exclusion rule', 'newspack-plugin' ) }
40+
help={ __( 'Apply this rule to everything EXCEPT the items matching the above.', 'newspack-plugin' ) }
41+
checked={ exclusion ?? false }
42+
onChange={ e => onChangeExclusion?.( e ) }
43+
/>
44+
</div>
45+
);
4846
}

src/wizards/audience/views/content-gates/content-rules.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ export default function ContentRules( { rules, onChange }: ContentRulesProps ) {
5050
[ onChange, rules ]
5151
);
5252

53+
const handleChangeExclusion = useCallback(
54+
( slug: string ) => ( e: boolean ) => {
55+
onChange( rules.map( r => ( r.slug === slug ? { ...r, exclusion: e } : r ) ) );
56+
},
57+
[ onChange, rules ]
58+
);
59+
5360
return (
5461
<ActionCard
5562
title={ __( 'Content Rules', 'newspack-plugin' ) }
@@ -67,7 +74,14 @@ export default function ContentRules( { rules, onChange }: ContentRulesProps ) {
6774
>
6875
<Grid columns={ 2 } gutter={ 32 } noMargin={ true }>
6976
{ rules.map( ( rule: GateContentRule ) => (
70-
<ContentRuleControl key={ rule.slug } slug={ rule.slug } value={ rule.value } onChange={ handleChange( rule.slug ) } />
77+
<ContentRuleControl
78+
key={ rule.slug }
79+
slug={ rule.slug }
80+
value={ rule.value }
81+
exclusion={ rule.exclusion }
82+
onChange={ handleChange( rule.slug ) }
83+
onChangeExclusion={ handleChangeExclusion( rule.slug ) }
84+
/>
7185
) ) }
7286
</Grid>
7387
</ActionCard>

src/wizards/audience/views/content-gates/types/index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,15 @@ type GateAccessRuleControlProps = {
5050
type GateContentRule = {
5151
slug: string;
5252
value: string[];
53+
exclusion?: boolean;
5354
};
5455

5556
type GateContentRuleControlProps = {
5657
slug: string;
5758
value: GateContentRuleValue;
59+
exclusion?: boolean;
5860
onChange: (value: GateContentRuleValue) => void;
61+
onChangeExclusion?: (value: boolean) => void;
5962
};
6063

6164
type GateStatus = 'publish' | 'draft' | 'pending' | 'future' | 'private' | 'trash';

0 commit comments

Comments
 (0)