@automattic/newspack-blocks 4.33.1 → 4.34.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/editor-rtl.css +4 -3
- package/dist/editor.asset.php +1 -1
- package/dist/editor.css +4 -3
- package/dist/editor.js +14 -14
- package/dist/modal.asset.php +1 -1
- package/dist/modal.js +1 -1
- package/includes/class-modal-checkout.php +248 -6
- package/includes/class-newspack-blocks-api.php +8 -0
- package/includes/class-newspack-blocks.php +38 -0
- package/languages/newspack-blocks-de_DE.po +124 -119
- package/languages/newspack-blocks-es_ES.po +124 -119
- package/languages/newspack-blocks-fr_BE.po +124 -119
- package/languages/newspack-blocks-nb_NO.po +124 -119
- package/languages/newspack-blocks-pt_PT.po +124 -119
- package/languages/newspack-blocks.pot +129 -125
- package/newspack-blocks.php +2 -2
- package/package.json +2 -2
- package/src/blocks/author-list/class-wp-rest-newspack-author-list-controller.php +6 -1
- package/src/blocks/author-profile/class-wp-rest-newspack-authors-controller.php +196 -48
- package/src/blocks/author-profile/edit.js +23 -65
- package/src/blocks/author-profile/single-author.js +7 -12
- package/src/blocks/author-profile/social-links.js +25 -0
- package/src/blocks/author-profile/social-links.test.js +42 -0
- package/src/blocks/checkout-button/view.php +4 -2
- package/src/blocks/homepage-articles/templates/article.php +1 -18
- package/src/modal-checkout/checkout-button-trigger.js +162 -24
- package/src/modal-checkout/checkout-button-trigger.test.js +149 -9
- package/src/modal-checkout/modal.js +31 -1
- package/src/shared/js/author-fetch.js +217 -0
- package/src/shared/js/author-fetch.test.js +230 -0
- package/vendor/composer/installed.php +2 -2
package/newspack-blocks.php
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Author URI: https://newspack.com/
|
|
8
8
|
* Text Domain: newspack-blocks
|
|
9
9
|
* Domain Path: /languages
|
|
10
|
-
* Version: 4.
|
|
10
|
+
* Version: 4.34.0-alpha.2
|
|
11
11
|
*
|
|
12
12
|
* @package Newspack_Blocks
|
|
13
13
|
*/
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
define( 'NEWSPACK_BLOCKS__PLUGIN_FILE', __FILE__ );
|
|
16
16
|
define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', 'dist/' );
|
|
17
17
|
define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( NEWSPACK_BLOCKS__PLUGIN_FILE ) );
|
|
18
|
-
define( 'NEWSPACK_BLOCKS__VERSION', '4.
|
|
18
|
+
define( 'NEWSPACK_BLOCKS__VERSION', '4.34.0-alpha.2' );
|
|
19
19
|
|
|
20
20
|
require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks.php';
|
|
21
21
|
require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks-api.php';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/newspack-blocks",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.34.0-alpha.2",
|
|
4
4
|
"author": "Automattic",
|
|
5
5
|
"description": "=== Newspack Blocks === Contributors: (this should be a list of wordpress.org userid's) Donate link: https://example.com/ Tags: comments, spam Requires at least: 4.5 Tested up to: 5.1.1 Stable tag: 0.1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html",
|
|
6
6
|
"repository": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/lodash": "^4.17.23",
|
|
60
|
-
"newspack-components": "4.8.
|
|
60
|
+
"newspack-components": "4.8.1-alpha.1",
|
|
61
61
|
"newspack-scripts": "5.11.0"
|
|
62
62
|
},
|
|
63
63
|
"overrides": {
|
|
@@ -173,6 +173,11 @@ class WP_REST_Newspack_Author_List_Controller extends WP_REST_Newspack_Authors_C
|
|
|
173
173
|
];
|
|
174
174
|
$options = wp_parse_args( $options, $default_options );
|
|
175
175
|
$fields = $options['fields'];
|
|
176
|
+
|
|
177
|
+
// Only the component's own author roles can be listed. A caller-chosen role outside them
|
|
178
|
+
// is dropped, and when nothing usable remains the user query is skipped below rather than
|
|
179
|
+
// run without a role filter, which would match every user on the site.
|
|
180
|
+
$options['author_roles'] = array_values( array_intersect( (array) $options['author_roles'], Newspack_Blocks\get_authors_roles_slugs() ) );
|
|
176
181
|
$current_page = 1;
|
|
177
182
|
|
|
178
183
|
// Array to store all authors.
|
|
@@ -219,7 +224,7 @@ class WP_REST_Newspack_Author_List_Controller extends WP_REST_Newspack_Authors_C
|
|
|
219
224
|
}
|
|
220
225
|
}
|
|
221
226
|
|
|
222
|
-
if ( in_array( $options['author_type'], [ 'all', 'users' ], true ) ) {
|
|
227
|
+
if ( ! empty( $options['author_roles'] ) && in_array( $options['author_type'], [ 'all', 'users' ], true ) ) {
|
|
223
228
|
// Reset current page for new query.
|
|
224
229
|
$current_page = 1;
|
|
225
230
|
$exclude_empty = $options['exclude_empty'] ? true : false;
|
|
@@ -65,6 +65,39 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
65
65
|
return array_values( array_diff( $fields, self::USER_MANAGEMENT_FIELDS ) );
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Most IDs one batched lookup resolves.
|
|
70
|
+
*
|
|
71
|
+
* A page never needs more than it has blocks, and an unbounded list would recreate the long
|
|
72
|
+
* PHP request the batched lookup exists to avoid. The editor splits its batches to match.
|
|
73
|
+
*/
|
|
74
|
+
const MAX_BATCH_IDS = 100;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether a WP user holds one of the roles this component treats as an author.
|
|
78
|
+
*
|
|
79
|
+
* The listing branch has always been narrowed to these roles. The by-id branches have to
|
|
80
|
+
* agree with it, or an id becomes a way to read any user's profile fields.
|
|
81
|
+
*
|
|
82
|
+
* @param WP_User|false $user The user.
|
|
83
|
+
* @return bool
|
|
84
|
+
*/
|
|
85
|
+
public static function is_author_role_user( $user ) {
|
|
86
|
+
return $user && ! empty( array_intersect( (array) $user->roles, \Newspack_Blocks\get_authors_roles_slugs() ) );
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Turn a comma-separated list (or array) of IDs into unique positive integers.
|
|
91
|
+
*
|
|
92
|
+
* @param string|array $value Raw request param.
|
|
93
|
+
* @return int[] At most MAX_BATCH_IDS of them.
|
|
94
|
+
*/
|
|
95
|
+
public static function sanitize_id_list( $value ) {
|
|
96
|
+
$ids = is_array( $value ) ? $value : explode( ',', (string) $value );
|
|
97
|
+
$ids = array_filter( array_map( 'absint', $ids ) );
|
|
98
|
+
return array_slice( array_values( array_unique( $ids ) ), 0, self::MAX_BATCH_IDS );
|
|
99
|
+
}
|
|
100
|
+
|
|
68
101
|
/**
|
|
69
102
|
* Registers the necessary REST API routes.
|
|
70
103
|
*
|
|
@@ -104,6 +137,12 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
104
137
|
'post_id' => [
|
|
105
138
|
'sanitize_callback' => 'absint',
|
|
106
139
|
],
|
|
140
|
+
'author_ids' => [
|
|
141
|
+
'sanitize_callback' => [ __CLASS__, 'sanitize_id_list' ],
|
|
142
|
+
],
|
|
143
|
+
'guest_author_ids' => [
|
|
144
|
+
'sanitize_callback' => [ __CLASS__, 'sanitize_id_list' ],
|
|
145
|
+
],
|
|
107
146
|
],
|
|
108
147
|
'permission_callback' => function() {
|
|
109
148
|
return current_user_can( 'edit_posts' );
|
|
@@ -130,6 +169,14 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
130
169
|
$fields = self::restrict_fields( $fields );
|
|
131
170
|
$include = ! empty( $request->get_param( 'include' ) ) ? explode( ',', $request->get_param( 'include' ) ) : null; // Fetch authors by multiple IDs.
|
|
132
171
|
$post_id = ! empty( $request->get_param( 'post_id' ) ) ? $request->get_param( 'post_id' ) : 0; // Fetch authors for a specific post (contextual mode).
|
|
172
|
+
$author_ids = ! empty( $request->get_param( 'author_ids' ) ) ? $request->get_param( 'author_ids' ) : []; // Fetch several WP users by ID in one request.
|
|
173
|
+
$guest_author_ids = ! empty( $request->get_param( 'guest_author_ids' ) ) ? $request->get_param( 'guest_author_ids' ) : []; // Fetch several guest authors by ID in one request.
|
|
174
|
+
|
|
175
|
+
// If a list of IDs is provided, resolve them all in one response. A list with no valid ID
|
|
176
|
+
// is still a list, and gets an empty response rather than the recent-authors listing.
|
|
177
|
+
if ( $request->has_param( 'author_ids' ) || $request->has_param( 'guest_author_ids' ) ) {
|
|
178
|
+
return $this->get_authors_by_ids( $author_ids, $guest_author_ids, $fields, $avatar_hide_default );
|
|
179
|
+
}
|
|
133
180
|
|
|
134
181
|
// If post_id is provided, get authors for that specific post.
|
|
135
182
|
if ( $post_id ) {
|
|
@@ -190,7 +237,7 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
190
237
|
if ( 0 === $guest_author_total ) {
|
|
191
238
|
$user = get_user_by( 'id', $author_id ); // Get the WP user.
|
|
192
239
|
|
|
193
|
-
if ( $user ) {
|
|
240
|
+
if ( self::is_author_role_user( $user ) ) {
|
|
194
241
|
$users = [ $user ];
|
|
195
242
|
}
|
|
196
243
|
}
|
|
@@ -242,29 +289,9 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
242
289
|
array_reduce(
|
|
243
290
|
! empty( $guest_authors ) ? $guest_authors : [],
|
|
244
291
|
function( $acc, $guest_author ) use ( $fields, $avatar_hide_default ) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
'id' => intval( $guest_author->ID ),
|
|
249
|
-
'registered' => $guest_author->post_date,
|
|
250
|
-
'is_guest' => true,
|
|
251
|
-
'slug' => $guest_author->post_name,
|
|
252
|
-
];
|
|
253
|
-
|
|
254
|
-
$guest_author = ( new CoAuthors_Guest_Authors() )->get_guest_author_by( 'id', $guest_author->ID );
|
|
255
|
-
|
|
256
|
-
if ( in_array( 'avatar', $fields, true ) && function_exists( 'coauthors_get_avatar' ) ) {
|
|
257
|
-
$avatar = coauthors_get_avatar( $guest_author, 256, $avatar_hide_default ? 'blank' : '' );
|
|
258
|
-
|
|
259
|
-
if ( \Newspack_Blocks\is_avatar_displayable( $avatar, $avatar_hide_default ) ) {
|
|
260
|
-
$guest_author_data['avatar'] = $avatar;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
$guest_author_data = self::fill_guest_author_data( $guest_author_data, $guest_author, $fields );
|
|
265
|
-
|
|
266
|
-
$acc[] = $guest_author_data;
|
|
267
|
-
}
|
|
292
|
+
$guest_author_data = self::format_guest_author( $guest_author, $fields, $avatar_hide_default );
|
|
293
|
+
if ( $guest_author_data ) {
|
|
294
|
+
$acc[] = $guest_author_data;
|
|
268
295
|
}
|
|
269
296
|
return $acc;
|
|
270
297
|
},
|
|
@@ -273,24 +300,9 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
273
300
|
array_reduce(
|
|
274
301
|
$users,
|
|
275
302
|
function( $acc, $user ) use ( $fields, $avatar_hide_default ) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
'registered' => $user->data->user_registered,
|
|
280
|
-
'is_guest' => false,
|
|
281
|
-
'slug' => $user->data->user_login,
|
|
282
|
-
];
|
|
283
|
-
|
|
284
|
-
if ( in_array( 'avatar', $fields, true ) ) {
|
|
285
|
-
$avatar = get_avatar( $user->data->ID, 256, $avatar_hide_default ? 'blank' : '' );
|
|
286
|
-
|
|
287
|
-
if ( \Newspack_Blocks\is_avatar_displayable( $avatar, $avatar_hide_default ) ) {
|
|
288
|
-
$user_data['avatar'] = $avatar;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
$user_data = self::fill_user_data( $user_data, $user, $fields );
|
|
293
|
-
$acc[] = $user_data;
|
|
303
|
+
$user_data = self::format_user( $user, $fields, $avatar_hide_default );
|
|
304
|
+
if ( $user_data ) {
|
|
305
|
+
$acc[] = $user_data;
|
|
294
306
|
}
|
|
295
307
|
return $acc;
|
|
296
308
|
},
|
|
@@ -312,6 +324,135 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
312
324
|
return rest_ensure_response( $response );
|
|
313
325
|
}
|
|
314
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Return the given users and guest authors in one response.
|
|
329
|
+
*
|
|
330
|
+
* Backs the editor's batched lookups: a page with many Author Profile blocks resolves every
|
|
331
|
+
* author with a single request instead of one per block. Each ID is formatted the way the
|
|
332
|
+
* single-ID path formats it, so a record matches what a block received on its own, including
|
|
333
|
+
* the guest-to-user fallback: a guest ID means "not known to be a WP user", because blocks
|
|
334
|
+
* saved before the `isGuestAuthor` attribute existed default to it for every author.
|
|
335
|
+
*
|
|
336
|
+
* @param int[] $author_ids WP user IDs.
|
|
337
|
+
* @param int[] $guest_author_ids Guest author post IDs.
|
|
338
|
+
* @param array $fields Fields to include.
|
|
339
|
+
* @param bool $avatar_hide_default Whether to hide the default avatar.
|
|
340
|
+
* @return WP_REST_Response
|
|
341
|
+
*/
|
|
342
|
+
public function get_authors_by_ids( $author_ids, $guest_author_ids, $fields, $avatar_hide_default ) {
|
|
343
|
+
$authors = [];
|
|
344
|
+
|
|
345
|
+
if ( $guest_author_ids ) {
|
|
346
|
+
$guest_authors = get_posts(
|
|
347
|
+
[
|
|
348
|
+
'post_type' => 'guest-author',
|
|
349
|
+
'post__in' => $guest_author_ids,
|
|
350
|
+
'posts_per_page' => count( $guest_author_ids ),
|
|
351
|
+
'orderby' => 'post__in',
|
|
352
|
+
]
|
|
353
|
+
);
|
|
354
|
+
$found = [];
|
|
355
|
+
foreach ( $guest_authors as $guest_author ) {
|
|
356
|
+
$found[] = (int) $guest_author->ID;
|
|
357
|
+
$guest_author_data = self::format_guest_author( $guest_author, $fields, $avatar_hide_default );
|
|
358
|
+
if ( $guest_author_data ) {
|
|
359
|
+
$authors[] = $guest_author_data;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Resolve the rest as WP users, the way the single-ID path and the front-end renderer do.
|
|
363
|
+
$author_ids = array_values( array_unique( array_merge( $author_ids, array_diff( $guest_author_ids, $found ) ) ) );
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if ( $author_ids ) {
|
|
367
|
+
// Two queries for the whole list instead of two per user: get_user_by() reads the user
|
|
368
|
+
// row and its capabilities from the object cache once both are primed.
|
|
369
|
+
cache_users( $author_ids );
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
foreach ( $author_ids as $author_id ) {
|
|
373
|
+
$user = get_user_by( 'id', $author_id );
|
|
374
|
+
if ( ! self::is_author_role_user( $user ) ) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
$user_data = self::format_user( $user, $fields, $avatar_hide_default );
|
|
378
|
+
if ( $user_data ) {
|
|
379
|
+
$authors[] = $user_data;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
$response = new WP_REST_Response( $authors );
|
|
384
|
+
$response->header( 'x-wp-total', count( $authors ) );
|
|
385
|
+
|
|
386
|
+
return rest_ensure_response( $response );
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Format a guest author post as an endpoint record.
|
|
391
|
+
*
|
|
392
|
+
* @param WP_Post|false $guest_author Guest author post.
|
|
393
|
+
* @param array $fields Fields to include.
|
|
394
|
+
* @param bool $avatar_hide_default Whether to hide the default avatar.
|
|
395
|
+
* @return array|null Record, or null when there is nothing to format.
|
|
396
|
+
*/
|
|
397
|
+
public static function format_guest_author( $guest_author, $fields, $avatar_hide_default ) {
|
|
398
|
+
if ( ! $guest_author || ! class_exists( 'CoAuthors_Guest_Authors' ) ) {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
$guest_author_data = [
|
|
403
|
+
'id' => intval( $guest_author->ID ),
|
|
404
|
+
'registered' => $guest_author->post_date,
|
|
405
|
+
'is_guest' => true,
|
|
406
|
+
'slug' => $guest_author->post_name,
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
$guest_author = ( new CoAuthors_Guest_Authors() )->get_guest_author_by( 'id', $guest_author->ID );
|
|
410
|
+
if ( ! $guest_author ) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if ( in_array( 'avatar', $fields, true ) && function_exists( 'coauthors_get_avatar' ) ) {
|
|
415
|
+
$avatar = coauthors_get_avatar( $guest_author, 256, $avatar_hide_default ? 'blank' : '' );
|
|
416
|
+
|
|
417
|
+
if ( \Newspack_Blocks\is_avatar_displayable( $avatar, $avatar_hide_default ) ) {
|
|
418
|
+
$guest_author_data['avatar'] = $avatar;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return self::fill_guest_author_data( $guest_author_data, $guest_author, $fields );
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Format a WP user as an endpoint record.
|
|
427
|
+
*
|
|
428
|
+
* @param WP_User|false $user The user.
|
|
429
|
+
* @param array $fields Fields to include.
|
|
430
|
+
* @param bool $avatar_hide_default Whether to hide the default avatar.
|
|
431
|
+
* @return array|null Record, or null when there is nothing to format.
|
|
432
|
+
*/
|
|
433
|
+
public static function format_user( $user, $fields, $avatar_hide_default ) {
|
|
434
|
+
if ( ! $user ) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
$user_data = [
|
|
439
|
+
'id' => intval( $user->data->ID ),
|
|
440
|
+
'registered' => $user->data->user_registered,
|
|
441
|
+
'is_guest' => false,
|
|
442
|
+
'slug' => $user->data->user_login,
|
|
443
|
+
];
|
|
444
|
+
|
|
445
|
+
if ( in_array( 'avatar', $fields, true ) ) {
|
|
446
|
+
$avatar = get_avatar( $user->data->ID, 256, $avatar_hide_default ? 'blank' : '' );
|
|
447
|
+
|
|
448
|
+
if ( \Newspack_Blocks\is_avatar_displayable( $avatar, $avatar_hide_default ) ) {
|
|
449
|
+
$user_data['avatar'] = $avatar;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return self::fill_user_data( $user_data, $user, $fields );
|
|
454
|
+
}
|
|
455
|
+
|
|
315
456
|
/**
|
|
316
457
|
* Fill guest author data.
|
|
317
458
|
*
|
|
@@ -392,7 +533,7 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
392
533
|
$user_data['url'] = esc_url( get_author_posts_url( $user->data->ID ) );
|
|
393
534
|
}
|
|
394
535
|
if ( false === $fields || in_array( 'social', $fields, true ) ) {
|
|
395
|
-
$user_data['social'] = self::get_social( $user->data->ID );
|
|
536
|
+
$user_data['social'] = self::get_social( $user->data->ID, false );
|
|
396
537
|
}
|
|
397
538
|
|
|
398
539
|
if ( class_exists( '\Newspack\Authors_Custom_Fields' ) ) {
|
|
@@ -458,10 +599,11 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
458
599
|
/**
|
|
459
600
|
* Get social media URLs and SVGs, if available. Only standard WP users have this user meta.
|
|
460
601
|
*
|
|
461
|
-
* @param int
|
|
602
|
+
* @param int $author_id Author ID.
|
|
603
|
+
* @param boolean $is_guest_author Whether the ID is a CAP guest author, whose website lives in post meta.
|
|
462
604
|
* @return array Array of social links and SVGs.
|
|
463
605
|
*/
|
|
464
|
-
public static function get_social( $author_id ) {
|
|
606
|
+
public static function get_social( $author_id, $is_guest_author = true ) {
|
|
465
607
|
$social_profiles = [
|
|
466
608
|
'facebook',
|
|
467
609
|
'twitter',
|
|
@@ -479,9 +621,13 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
479
621
|
|
|
480
622
|
return array_reduce(
|
|
481
623
|
$social_profiles,
|
|
482
|
-
function( $acc, $profile ) use ( $author_id ) {
|
|
624
|
+
function( $acc, $profile ) use ( $author_id, $is_guest_author ) {
|
|
483
625
|
$is_website = 'website' === $profile;
|
|
484
|
-
|
|
626
|
+
if ( $is_website && ! $is_guest_author ) {
|
|
627
|
+
// A WP user has no post meta to read, and reading it costs a query per user.
|
|
628
|
+
return $acc;
|
|
629
|
+
}
|
|
630
|
+
$handle = $is_website ? get_post_meta( $author_id, 'cap-website', true ) : get_the_author_meta( $profile, $author_id );
|
|
485
631
|
|
|
486
632
|
if ( $handle ) {
|
|
487
633
|
$url = 'twitter' === $profile ? esc_url( 'https://x.com/' . $handle ) : esc_url( $handle );
|
|
@@ -510,7 +656,9 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
510
656
|
*/
|
|
511
657
|
protected function get_post_authors( $post_id, $fields, $avatar_hide_default ) {
|
|
512
658
|
$post = get_post( $post_id );
|
|
513
|
-
|
|
659
|
+
|
|
660
|
+
// Who wrote an unpublished post is not the caller's to know unless they can read it.
|
|
661
|
+
if ( ! $post || ! current_user_can( 'read_post', $post->ID ) ) {
|
|
514
662
|
return rest_ensure_response( [] );
|
|
515
663
|
}
|
|
516
664
|
|
|
@@ -40,6 +40,11 @@ import { pencil, postAuthor, pullLeft, pullRight } from '@wordpress/icons';
|
|
|
40
40
|
import { __, sprintf } from '@wordpress/i18n';
|
|
41
41
|
import { addQueryArgs } from '@wordpress/url';
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Internal dependencies
|
|
45
|
+
*/
|
|
46
|
+
import { fetchAuthorById, fetchAuthorList } from '../../shared/js/author-fetch';
|
|
47
|
+
|
|
43
48
|
/**
|
|
44
49
|
* Register block bindings source for author data in the editor.
|
|
45
50
|
* This enables core blocks to display author data via bindings.
|
|
@@ -274,7 +279,6 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
|
|
|
274
279
|
isGuestAuthor,
|
|
275
280
|
isContextual,
|
|
276
281
|
layoutVersion,
|
|
277
|
-
showSocial,
|
|
278
282
|
showEmail,
|
|
279
283
|
textSize,
|
|
280
284
|
showAvatar,
|
|
@@ -398,22 +402,15 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
|
|
|
398
402
|
setError( null );
|
|
399
403
|
setIsLoading( true );
|
|
400
404
|
try {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
405
|
+
// Shared with every other Author Profile block on the page, so a page of profiles
|
|
406
|
+
// costs one request instead of one per block.
|
|
407
|
+
const _author = await fetchAuthorById( {
|
|
408
|
+
authorId,
|
|
409
|
+
isGuestAuthor,
|
|
404
410
|
fields: 'id,name,bio,email,social,avatar,url',
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if ( avatarHideDefault ) {
|
|
408
|
-
params.avatar_hide_default = 1;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const response = await apiFetch( {
|
|
412
|
-
path: addQueryArgs( '/newspack-blocks/v1/authors', params ),
|
|
411
|
+
avatarHideDefault,
|
|
413
412
|
} );
|
|
414
413
|
|
|
415
|
-
const _author = response.pop();
|
|
416
|
-
|
|
417
414
|
if ( ! _author ) {
|
|
418
415
|
throw sprintf(
|
|
419
416
|
/* translators: %s: error text for when no authors are found. */
|
|
@@ -476,21 +473,16 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
|
|
|
476
473
|
fields.push( 'email' );
|
|
477
474
|
}
|
|
478
475
|
const results = await Promise.all(
|
|
479
|
-
bylineAuthorIds.map( id =>
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
476
|
+
bylineAuthorIds.map( id =>
|
|
477
|
+
fetchAuthorById( {
|
|
478
|
+
authorId: id,
|
|
479
|
+
isGuestAuthor: false,
|
|
483
480
|
fields: fields.join( ',' ),
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
return apiFetch( {
|
|
489
|
-
path: addQueryArgs( '/newspack-blocks/v1/authors', params ),
|
|
490
|
-
} );
|
|
491
|
-
} )
|
|
481
|
+
avatarHideDefault,
|
|
482
|
+
} )
|
|
483
|
+
)
|
|
492
484
|
);
|
|
493
|
-
setContextualAuthors( results.
|
|
485
|
+
setContextualAuthors( results.filter( Boolean ) );
|
|
494
486
|
} catch ( e ) {
|
|
495
487
|
setError( e.message || e || __( 'Error fetching byline authors.', 'newspack-blocks' ) );
|
|
496
488
|
setContextualAuthors( [] );
|
|
@@ -535,17 +527,6 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
|
|
|
535
527
|
};
|
|
536
528
|
}, [ authorsToRender, previewAuthorIndex, layoutVersion, clientId ] );
|
|
537
529
|
|
|
538
|
-
// Combine social links and email, which are shown together.
|
|
539
|
-
const getSocialLinks = authorData => {
|
|
540
|
-
const socialLinks = ( showSocial && authorData?.social ) || {};
|
|
541
|
-
if ( showEmail && authorData?.email ) {
|
|
542
|
-
socialLinks.email = authorData.email;
|
|
543
|
-
} else {
|
|
544
|
-
delete socialLinks.email;
|
|
545
|
-
}
|
|
546
|
-
return socialLinks;
|
|
547
|
-
};
|
|
548
|
-
|
|
549
530
|
// Determine if we're in nested layout mode (publisher-controlled composition).
|
|
550
531
|
// In nested mode, hide field toggles since publishers control display by adding/removing blocks.
|
|
551
532
|
const isNestedLayout = layoutVersion === 2;
|
|
@@ -767,16 +748,7 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
|
|
|
767
748
|
if ( authorId && ! error ) {
|
|
768
749
|
return [];
|
|
769
750
|
}
|
|
770
|
-
const
|
|
771
|
-
parse: false,
|
|
772
|
-
path: addQueryArgs( '/newspack-blocks/v1/authors', {
|
|
773
|
-
search,
|
|
774
|
-
offset,
|
|
775
|
-
fields: 'id,name',
|
|
776
|
-
} ),
|
|
777
|
-
} );
|
|
778
|
-
const total = parseInt( response.headers.get( 'x-wp-total' ) || 0, 10 );
|
|
779
|
-
const authors = await response.json();
|
|
751
|
+
const { authors, total } = await fetchAuthorList( { search, offset, fields: 'id,name' } );
|
|
780
752
|
if ( ! maxItemsToSuggest && ! search ) {
|
|
781
753
|
setMaxItemsToSuggest( total );
|
|
782
754
|
}
|
|
@@ -922,11 +894,7 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
|
|
|
922
894
|
{ inspectorControls }
|
|
923
895
|
{ blockControls }
|
|
924
896
|
{ authorsToRender.map( authorData => (
|
|
925
|
-
<SingleAuthor
|
|
926
|
-
key={ authorData.id }
|
|
927
|
-
author={ { ...authorData, social: getSocialLinks( authorData ) } }
|
|
928
|
-
attributes={ attributes }
|
|
929
|
-
/>
|
|
897
|
+
<SingleAuthor key={ authorData.id } author={ authorData } attributes={ attributes } />
|
|
930
898
|
) ) }
|
|
931
899
|
</div>
|
|
932
900
|
);
|
|
@@ -938,7 +906,7 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
|
|
|
938
906
|
<div { ...blockProps }>
|
|
939
907
|
{ inspectorControls }
|
|
940
908
|
{ blockControls }
|
|
941
|
-
<SingleAuthor author={
|
|
909
|
+
<SingleAuthor author={ author } attributes={ attributes } />
|
|
942
910
|
</div>
|
|
943
911
|
);
|
|
944
912
|
}
|
|
@@ -972,17 +940,7 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
|
|
|
972
940
|
return [];
|
|
973
941
|
}
|
|
974
942
|
|
|
975
|
-
const
|
|
976
|
-
parse: false,
|
|
977
|
-
path: addQueryArgs( '/newspack-blocks/v1/authors', {
|
|
978
|
-
search,
|
|
979
|
-
offset,
|
|
980
|
-
fields: 'id,name',
|
|
981
|
-
} ),
|
|
982
|
-
} );
|
|
983
|
-
|
|
984
|
-
const total = parseInt( response.headers.get( 'x-wp-total' ) || 0, 10 );
|
|
985
|
-
const authors = await response.json();
|
|
943
|
+
const { authors, total } = await fetchAuthorList( { search, offset, fields: 'id,name' } );
|
|
986
944
|
|
|
987
945
|
// Set max items for "load more" functionality in suggestions list.
|
|
988
946
|
if ( ! maxItemsToSuggest && ! search ) {
|
|
@@ -12,6 +12,11 @@ import { autop } from '@wordpress/autop';
|
|
|
12
12
|
*/
|
|
13
13
|
import classnames from 'classnames';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Internal dependencies
|
|
17
|
+
*/
|
|
18
|
+
import { getSocialLinks } from './social-links';
|
|
19
|
+
|
|
15
20
|
// Show a link to the author's post archive page, if available.
|
|
16
21
|
const MaybeLink = ( { author, children, showArchiveLink } ) =>
|
|
17
22
|
showArchiveLink && author && author.url ? (
|
|
@@ -25,18 +30,8 @@ const MaybeLink = ( { author, children, showArchiveLink } ) =>
|
|
|
25
30
|
export const SingleAuthor = ( { author, attributes } ) => {
|
|
26
31
|
const { showBio, showSocial, showEmail, showArchiveLink, showAvatar, textSize, avatarAlignment, avatarBorderRadius, avatarSize } = attributes;
|
|
27
32
|
|
|
28
|
-
// Combine social links and
|
|
29
|
-
const socialLinks = ( showSocial
|
|
30
|
-
if ( showEmail && author && author.email ) {
|
|
31
|
-
socialLinks.email = author.email;
|
|
32
|
-
} else {
|
|
33
|
-
delete socialLinks.email;
|
|
34
|
-
}
|
|
35
|
-
if ( attributes.shownewspack_phone_number && author && author.newspack_phone_number ) {
|
|
36
|
-
socialLinks.newspack_phone_number = author.newspack_phone_number;
|
|
37
|
-
} else {
|
|
38
|
-
delete socialLinks.newspack_phone_number;
|
|
39
|
-
}
|
|
33
|
+
// Combine social links and contact links, which are shown together.
|
|
34
|
+
const socialLinks = getSocialLinks( author, { showSocial, showEmail, showPhone: attributes.shownewspack_phone_number } );
|
|
40
35
|
|
|
41
36
|
const employment = [ attributes.shownewspack_role && author.newspack_role, attributes.shownewspack_employer && author.newspack_employer ]
|
|
42
37
|
.filter( Boolean )
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combine an author's social links with the contact links a block chooses to show.
|
|
3
|
+
*
|
|
4
|
+
* Returns a new object. Author records are shared between every block on the page that shows
|
|
5
|
+
* the same author, so a block must not write its own choices into the record it was given.
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} author Author record from the authors endpoint.
|
|
8
|
+
* @param {Object} options
|
|
9
|
+
* @param {boolean} options.showSocial Whether to include the social links.
|
|
10
|
+
* @param {boolean} options.showEmail Whether to include the email link.
|
|
11
|
+
* @param {boolean} options.showPhone Whether to include the phone number link.
|
|
12
|
+
* @return {Object} Links keyed by service.
|
|
13
|
+
*/
|
|
14
|
+
export function getSocialLinks( author, { showSocial, showEmail, showPhone } ) {
|
|
15
|
+
const links = { ...( ( showSocial && author?.social ) || {} ) };
|
|
16
|
+
delete links.email;
|
|
17
|
+
delete links.newspack_phone_number;
|
|
18
|
+
if ( showEmail && author?.email ) {
|
|
19
|
+
links.email = author.email;
|
|
20
|
+
}
|
|
21
|
+
if ( showPhone && author?.newspack_phone_number ) {
|
|
22
|
+
links.newspack_phone_number = author.newspack_phone_number;
|
|
23
|
+
}
|
|
24
|
+
return links;
|
|
25
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getSocialLinks } from './social-links';
|
|
2
|
+
|
|
3
|
+
const author = () => ( {
|
|
4
|
+
social: { twitter: { url: 'https://x.com/a' } },
|
|
5
|
+
email: { url: 'mailto:a@example.test' },
|
|
6
|
+
newspack_phone_number: { url: 'tel:1' },
|
|
7
|
+
} );
|
|
8
|
+
|
|
9
|
+
describe( 'getSocialLinks', () => {
|
|
10
|
+
it( 'adds the email to the social links when it is shown', () => {
|
|
11
|
+
expect( getSocialLinks( author(), { showSocial: true, showEmail: true } ) ).toEqual( {
|
|
12
|
+
twitter: { url: 'https://x.com/a' },
|
|
13
|
+
email: { url: 'mailto:a@example.test' },
|
|
14
|
+
} );
|
|
15
|
+
} );
|
|
16
|
+
|
|
17
|
+
it( 'returns only the email when social links are hidden', () => {
|
|
18
|
+
expect( getSocialLinks( author(), { showSocial: false, showEmail: true } ) ).toEqual( { email: { url: 'mailto:a@example.test' } } );
|
|
19
|
+
} );
|
|
20
|
+
|
|
21
|
+
it( 'adds the phone number when it is shown', () => {
|
|
22
|
+
expect( getSocialLinks( author(), { showSocial: false, showEmail: false, showPhone: true } ) ).toEqual( {
|
|
23
|
+
newspack_phone_number: { url: 'tel:1' },
|
|
24
|
+
} );
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
it( 'leaves the author record untouched', () => {
|
|
28
|
+
const record = author();
|
|
29
|
+
|
|
30
|
+
getSocialLinks( record, { showSocial: true, showEmail: true } );
|
|
31
|
+
expect( record.social ).toEqual( { twitter: { url: 'https://x.com/a' } } );
|
|
32
|
+
|
|
33
|
+
// A record shared between blocks may already carry a link another block added.
|
|
34
|
+
record.social.email = { url: 'mailto:other@example.test' };
|
|
35
|
+
expect( getSocialLinks( record, { showSocial: true, showEmail: false } ) ).toEqual( { twitter: { url: 'https://x.com/a' } } );
|
|
36
|
+
expect( record.social.email ).toEqual( { url: 'mailto:other@example.test' } );
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
it( 'returns an empty object for a missing author', () => {
|
|
40
|
+
expect( getSocialLinks( undefined, { showSocial: true, showEmail: true } ) ).toEqual( {} );
|
|
41
|
+
} );
|
|
42
|
+
} );
|
|
@@ -178,8 +178,10 @@ function render_callback( $attributes ) {
|
|
|
178
178
|
$hidden_fields .= $after_success_behavior ? '<input type="hidden" name="after_success_behavior" value="' . esc_attr( $after_success_behavior ) . '" />' : '';
|
|
179
179
|
$hidden_fields .= $after_success_button_label ? '<input type="hidden" name="after_success_button_label" value="' . esc_attr( $after_success_button_label ) . '" />' : '';
|
|
180
180
|
$hidden_fields .= $after_success_url ? '<input type="hidden" name="after_success_url" value="' . esc_attr( $after_success_url ) . '" />' : '';
|
|
181
|
-
// Vouched for here because this is the last point the destination is known to
|
|
182
|
-
// from the block's own settings
|
|
181
|
+
// Vouched for here because this is the last point the destination is known to
|
|
182
|
+
// come from the block's own settings — or, for a URL-triggered render, from a
|
|
183
|
+
// value already restricted to allowed hosts (see
|
|
184
|
+
// Modal_Checkout::build_url_triggered_button_attrs()).
|
|
183
185
|
$after_success_token = $after_success_url ? Modal_Checkout::get_after_success_token( $after_success_url ) : '';
|
|
184
186
|
$hidden_fields .= $after_success_token ? '<input type="hidden" name="after_success_token" value="' . esc_attr( $after_success_token ) . '" />' : '';
|
|
185
187
|
}
|
|
@@ -142,24 +142,7 @@ call_user_func(
|
|
|
142
142
|
?>
|
|
143
143
|
<div class="newspack-post-subtitle newspack-post-subtitle--in-homepage-block">
|
|
144
144
|
<?php
|
|
145
|
-
$
|
|
146
|
-
'b' => true,
|
|
147
|
-
'strong' => true,
|
|
148
|
-
'i' => true,
|
|
149
|
-
'em' => true,
|
|
150
|
-
'mark' => true,
|
|
151
|
-
'u' => true,
|
|
152
|
-
'small' => true,
|
|
153
|
-
'sub' => true,
|
|
154
|
-
'sup' => true,
|
|
155
|
-
'a' => array(
|
|
156
|
-
'href' => true,
|
|
157
|
-
'target' => true,
|
|
158
|
-
'rel' => true,
|
|
159
|
-
),
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
echo wptexturize( wp_kses( $subtitle, $allowed_tags ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
145
|
+
echo wptexturize( Newspack_Blocks::sanitize_post_subtitle( $subtitle ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
163
146
|
?>
|
|
164
147
|
</div>
|
|
165
148
|
<?php endif; ?>
|