diff --git a/includes/class-newspack-listings-api.php b/includes/class-newspack-listings-api.php index 003e3ee2..f5e6cb28 100644 --- a/includes/class-newspack-listings-api.php +++ b/includes/class-newspack-listings-api.php @@ -21,6 +21,12 @@ * Sets up API endpoints and handlers for listings. */ final class Newspack_Listings_Api { + /** + * REST route namespace. + * + * @var Newspack_Listings_Api + */ + protected static $namespace = 'newspack-listings/v1'; /** * The single instance of the class. @@ -56,12 +62,42 @@ public static function register_routes() { // GET listings posts by ID, query args, or title search term. register_rest_route( - 'newspack-listings/v1', + self::$namespace, 'listings', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ __CLASS__, 'get_items' ], + 'args' => [ + 'query' => [ + 'sanitize_callback' => '\Newspack_Listings\Utils\sanitize_array', + ], + 'id' => [ + 'sanitize_callback' => 'absint', + ], + 'type' => [ + 'sanitize_callback' => '\Newspack_Listings\Utils\sanitize_array', + ], + 'attributes' => [ + 'sanitize_callback' => '\Newspack_Listings\Utils\sanitize_array', + ], + 'offset' => [ + 'sanitize_callback' => 'absint', + ], + 'page' => [ + 'sanitize_callback' => 'absint', + ], + 'per_page' => [ + 'sanitize_callback' => 'absint', + ], + 'search' => [ + 'sanitize_callback' => 'sanitize_text_field', + ], + '_fields' => [ + 'sanitize_callback' => 'sanitize_text_field', + ], + + ], 'permission_callback' => '__return_true', ], ] @@ -69,7 +105,7 @@ public static function register_routes() { // GET listings taxonomy terms by name search term. register_rest_route( - 'newspack-listings/v1', + self::$namespace, 'terms', [ [ @@ -82,7 +118,7 @@ public static function register_routes() { // GET listings taxonomy terms by name search term. register_rest_route( - 'newspack-listings/v1', + self::$namespace, 'children', [ [ @@ -95,7 +131,7 @@ public static function register_routes() { // Set listings taxonomy terms. register_rest_route( - 'newspack-listings/v1', + self::$namespace, 'children', [ [ @@ -254,6 +290,7 @@ public static function build_listings_query( $query, $args = [] ) { * @return WP_REST_Response. */ public static function get_items( $request ) { + $response = []; $params = $request->get_params(); $fields = explode( ',', $params['_fields'] ); $search = ! empty( $params['search'] ) ? $params['search'] : null; @@ -297,81 +334,88 @@ public static function get_items( $request ) { $listings_query = new \WP_Query( $args ); if ( $listings_query->have_posts() ) { - $response = new \WP_REST_Response( - array_map( - function( $post ) use ( $attributes, $fields, $is_amp, $next_page, $query ) { - $item = [ - 'id' => $post->ID, - 'title' => $post->post_title, - ]; - - // if $fields includes html, get rendered HTML for the post. - if ( in_array( 'html', $fields ) && ! empty( $attributes ) ) { - $html = Utils\template_include( - 'listing', - [ - 'attributes' => $attributes, - 'post' => $post, - ] - ); - - // If an AMP page, convert to valid AMP HTML. - if ( $is_amp ) { - $html = Utils\generate_amp_partial( $html ); - } - - $item['html'] = $html; + $listings = array_map( + function( $post ) use ( $attributes, $fields, $is_amp, $next_page, $query ) { + $item = [ + 'id' => $post->ID, + 'title' => $post->post_title, + ]; + + // if $fields includes html, get rendered HTML for the post. + if ( in_array( 'html', $fields ) && ! empty( $attributes ) ) { + $html = Utils\template_include( + 'listing', + [ + 'attributes' => $attributes, + 'post' => $post, + ] + ); + + // If an AMP page, convert to valid AMP HTML. + if ( $is_amp ) { + $html = Utils\generate_amp_partial( $html ); } - // If $fields includes category, get the post categories. - if ( in_array( 'category', $fields ) ) { - $item['category'] = get_the_terms( $post->ID, 'category' ); - } - - // If $fields includes tags, get the post tags. - if ( in_array( 'tags', $fields ) ) { - $item['tags'] = get_the_terms( $post->ID, 'post_tag' ); - } + $item['html'] = $html; + } + + // If $fields includes category, get the post categories. + if ( in_array( 'category', $fields ) ) { + $item['category'] = get_the_terms( $post->ID, 'category' ); + } + + // If $fields includes tags, get the post tags. + if ( in_array( 'tags', $fields ) ) { + $item['tags'] = get_the_terms( $post->ID, 'post_tag' ); + } + + // If $fields includes excerpt, get the post excerpt. + if ( in_array( 'excerpt', $fields ) ) { + $item['excerpt'] = Utils\get_listing_excerpt( $post ); + } + + // If $fields includes media, get the featured image + caption. + if ( in_array( 'media', $fields ) ) { + $item['media'] = [ + 'image' => get_the_post_thumbnail_url( $post->ID, 'medium' ), + 'caption' => get_the_post_thumbnail_caption( $post->ID ), + ]; + } - // If $fields includes author and the post isn't set to hide author, get the post author. - if ( in_array( 'author', $fields ) && empty( get_post_meta( $post->ID, 'newspack_listings_hide_author', true ) ) ) { - $item['author'] = get_the_author_meta( 'display_name', $post->post_author ); - } + // If $fields includes meta, get all Newspack Listings meta fields. + if ( in_array( 'meta', $fields ) || in_array( 'author', $fields ) ) { + $item['meta'] = []; + $post_meta = Core::get_meta_values( $post->ID, $post->post_type ); - // If $fields includes excerpt, get the post excerpt. - if ( in_array( 'excerpt', $fields ) ) { - $item['excerpt'] = Utils\get_listing_excerpt( $post ); + if ( ! empty( $post_meta ) ) { + $item['meta'] = $post_meta; } + } - // If $fields includes media, get the featured image + caption. - if ( in_array( 'media', $fields ) ) { - $item['media'] = [ - 'image' => get_the_post_thumbnail_url( $post->ID, 'medium' ), - 'caption' => get_the_post_thumbnail_caption( $post->ID ), - ]; - } + // If $fields includes type, get the post type. + if ( in_array( 'type', $fields ) ) { + $item['type'] = $post->post_type; + } - // If $fields includes meta, get all Newspack Listings meta fields. - if ( in_array( 'meta', $fields ) ) { - $post_meta = Core::get_meta_values( $post->ID, $post->post_type ); + // If $fields includes author and the post isn't set to hide author, get the post author. + if ( in_array( 'author', $fields ) && empty( get_post_meta( $post->ID, 'newspack_listings_hide_author', true ) ) ) { + $item['author'] = get_the_author_meta( 'display_name', $post->post_author ); + } - if ( ! empty( $post_meta ) ) { - $item['meta'] = $post_meta; - } - } + $item['test'] = 'Brody'; - // If $fields includes type, get the post type. - if ( in_array( 'type', $fields ) ) { - $item['type'] = $post->post_type; - } + // If $fields includes sponsors include sponsors info. + if ( in_array( 'sponsors', $fields ) ) { + $item['sponsors'] = Utils\get_sponsors( $post->ID, 'native' ); + } - return $item; - }, - $listings_query->posts - ), - 200 + return $item; + }, + $listings_query->posts ); + $response = new \WP_REST_Response( $listings ); + // Provide next URL if there are more pages. if ( $next_page <= $listings_query->max_num_pages ) { $next_url = add_query_arg( @@ -382,18 +426,16 @@ function( $post ) use ( $attributes, $fields, $is_amp, $next_page, $query ) { 'amp' => $is_amp, '_fields' => 'html', ], - rest_url( '/newspack-listings/v1/listings' ) + rest_url( '/' . self::$namespace . '/listings' ) ); } if ( ! empty( $next_url ) ) { $response->header( 'next-url', $next_url ); } - - return $response; } - return new \WP_REST_Response( [] ); + return rest_ensure_response( $response ); } /** diff --git a/includes/class-newspack-listings-core.php b/includes/class-newspack-listings-core.php index 56314443..19e62329 100644 --- a/includes/class-newspack-listings-core.php +++ b/includes/class-newspack-listings-core.php @@ -75,6 +75,7 @@ public function __construct() { add_filter( 'newspack_listings_hide_author', [ __CLASS__, 'hide_author' ] ); add_filter( 'newspack_listings_hide_publish_date', [ __CLASS__, 'hide_publish_date' ] ); add_filter( 'newspack_theme_featured_image_post_types', [ __CLASS__, 'support_featured_image_options' ] ); + add_filter( 'newspack_sponsors_post_types', [ __CLASS__, 'support_newspack_sponsors' ] ); register_activation_hook( NEWSPACK_LISTINGS_FILE, [ __CLASS__, 'activation_hook' ] ); } @@ -827,6 +828,19 @@ public static function activation_hook() { flush_rewrite_rules(); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.flush_rewrite_rules_flush_rewrite_rules } + /** + * If using the Newspack Sponsors plugin, add support for sponsors to all listings. + * + * @param array $post_types Array of supported post types. + * @return array Filtered array of supported post types. + */ + public static function support_newspack_sponsors( $post_types ) { + return array_merge( + $post_types, + array_values( self::NEWSPACK_LISTINGS_POST_TYPES ) + ); + } + /** * Convert legacy custom taxonomies to regular post categories and tags. * Helpful for sites that have been using v1 of the Listings plugin. diff --git a/includes/newspack-listings-utils.php b/includes/newspack-listings-utils.php index 71b9c93f..f3e31097 100644 --- a/includes/newspack-listings-utils.php +++ b/includes/newspack-listings-utils.php @@ -346,3 +346,25 @@ function generate_amp_partial( $html ) { } return \AMP_DOM_Utils::get_content_from_dom( $dom ); } + +/** + * Get sponsors for the given listing. + * + * @param int $post_id ID of the listing. + * @param string $scope 'Native' or 'underwritten'. + * @param string $type 'Post' or 'archive'. + * + * @return array|boolean Array of sponsors, or false if none. + */ +function get_sponsors( $post_id = null, $scope = null, $type = 'post' ) { + // Bail if we don't have the Sponsors plugin. + if ( ! function_exists( '\Newspack_Sponsors\get_all_sponsors' ) ) { + return false; + } + + if ( null === $post_id ) { + $post_id = get_the_ID(); + } + + return \Newspack_Sponsors\get_all_sponsors( $post_id, $scope, $type ); +} diff --git a/src/assets/shared/listing.scss b/src/assets/shared/listing.scss index b65f21d3..7b307e42 100644 --- a/src/assets/shared/listing.scss +++ b/src/assets/shared/listing.scss @@ -2,6 +2,15 @@ .newspack-listings { &__listing-post { + display: block; + + @media only screen and ( min-width: $tablet_width ) { + .media-position-left &, + .media-position-right & { + display: flex; + } + } + + .is-link { padding-left: 0; padding-right: 0; @@ -50,18 +59,6 @@ } } - &__listing-post, - &__listing-link { - display: block; - - @media only screen and ( min-width: $tablet_width ) { - .media-position-left &, - .media-position-right & { - display: flex; - } - } - } - &__listing-title { margin-top: 0.5rem; @@ -125,4 +122,14 @@ &__column-reverse { flex-direction: row-reverse; } + + &__sponsors { + align-items: center; + display: flex; + + .sponsor-logos { + border-right: 1px solid var( --newspack-listings--grey-light ); + margin-right: 0.75rem; + } + } } diff --git a/src/blocks/curated-list/edit.js b/src/blocks/curated-list/edit.js index 788256b8..f2f069ae 100644 --- a/src/blocks/curated-list/edit.js +++ b/src/blocks/curated-list/edit.js @@ -117,7 +117,7 @@ const CuratedListEditorComponent = ( { const posts = await apiFetch( { path: addQueryArgs( '/newspack-listings/v1/listings', { query: { ...query, maxItems: MAX_EDITOR_ITEMS }, // Get up to MAX_EDITOR_ITEMS listings in the editor so we can show all locations. - _fields: 'id,title,author,category,tags,excerpt,media,meta,type', + _fields: 'id,title,author,category,tags,excerpt,media,meta,type,sponsors', } ), } ); setAttributes( { listingIds: posts.map( post => post.id ) } ); diff --git a/src/blocks/listing/edit.js b/src/blocks/listing/edit.js index 49f8bc26..7482f6b2 100644 --- a/src/blocks/listing/edit.js +++ b/src/blocks/listing/edit.js @@ -69,7 +69,7 @@ const ListingEditorComponent = ( { path: addQueryArgs( '/newspack-listings/v1/listings', { per_page: 100, id: listingId, - _fields: 'id,title,author,category,tags,excerpt,media,meta', + _fields: 'id,title,author,category,tags,excerpt,media,meta,sponsors', } ), } ); diff --git a/src/blocks/listing/listing.js b/src/blocks/listing/listing.js index 7e66b9f0..f77fab63 100644 --- a/src/blocks/listing/listing.js +++ b/src/blocks/listing/listing.js @@ -3,7 +3,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, _x, sprintf } from '@wordpress/i18n'; import { Notice } from '@wordpress/components'; import { Fragment, RawHTML } from '@wordpress/element'; import { decodeEntities } from '@wordpress/html-entities'; @@ -11,6 +11,15 @@ import { decodeEntities } from '@wordpress/html-entities'; export const Listing = ( { attributes, error, post } ) => { // Parent Curated List block attributes. const { showAuthor, showCategory, showTags, showExcerpt, showImage, showCaption } = attributes; + const { + author = '', + category = [], + excerpt = '', + media = {}, + sponsors = false, + tags = [], + title = '', + } = post; return (
@@ -19,46 +28,83 @@ export const Listing = ( { attributes, error, post } ) => { { error } ) } - { showImage && post && post.media && post.media.image && ( + { showImage && post && media && media.image && (
{ - { showCaption && post.media.caption && ( + { showCaption && media.caption && (
- { post.media.caption } + { media.caption }
) }
) } - { post && post.title && ( + { post && (
- { showCategory && post.category.length && ! post.newspack_post_sponsors && ( + { sponsors && 0 < sponsors.length && ( + + { sponsors[ 0 ].sponsor_flag } + + ) } + { showCategory && category.length && ! sponsors && (
- { post.category.map( ( category, index ) => ( + { category.map( ( _category, index ) => ( - { decodeEntities( category.name ) } - { index + 1 < post.category.length && ', ' } + { decodeEntities( _category.name ) } + { index + 1 < _category.length && ', ' } ) ) }
) } -

{ decodeEntities( post.title ) }

- { showAuthor && post.author && ( - { __( 'By', 'newpack-listings' ) + ' ' + decodeEntities( post.author ) } +

{ decodeEntities( title ) }

+ { sponsors && 0 < sponsors.length && ( +
+ + { sponsors.map( sponsor => { + return ( + { + ); + } ) } + + + { sponsors.map( ( sponsor, index ) => + sprintf( + '%s%s%s%s', + 0 === index ? sponsor.sponsor_byline + ' ' : '', + 1 < sponsors.length && index + 1 === sponsors.length + ? __( ' and ', 'newspack-listings' ) + : '', + sponsor.sponsor_name, + 2 < sponsors.length && index + 1 < sponsors.length + ? _x( ', ', 'separator character', 'newspack-listings' ) + : '' + ) + ) } + +
+ ) } + { showAuthor && author && ! sponsors && ( + { __( 'By', 'newpack-listings' ) + ' ' + decodeEntities( author ) } ) } - { showExcerpt && post.excerpt && { post.excerpt } } + { showExcerpt && excerpt && { excerpt } } - { showTags && post.tags.length && ( + { showTags && tags.length && (

{ __( 'Tagged: ', 'newspack-listings' ) } - { post.tags.map( ( tag, index ) => ( + { tags.map( ( tag, index ) => ( { decodeEntities( tag.name ) } - { index + 1 < post.tags.length && ', ' } + { index + 1 < tags.length && ', ' } ) ) }

diff --git a/src/templates/listing.php b/src/templates/listing.php index d93061d2..5b0e23f3 100644 --- a/src/templates/listing.php +++ b/src/templates/listing.php @@ -18,62 +18,139 @@ function( $data ) { return; } + // Get native sponsors. + $sponsors = Utils\get_sponsors( $post->ID, 'native' ); ?>
  • - - - ID, 'large' ); - if ( ! empty( $featured_image ) ) : - ?> - + -
    +
    + + ID, 'category' ); + elseif ( $attributes['showCategory'] ) : + $categories = get_the_terms( $post->ID, 'category' ); + + if ( is_array( $categories ) && 0 < count( $categories ) ) : + ?> + + ID, 'newspack_listings_hide_author', true ) ) ) : ?> + + post_author ) ); ?> + + + -
    - + +