Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 151
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
WP_REST_Newspack_Articles_Controller
0.00% covered (danger)
0.00%
0 / 151
0.00% covered (danger)
0.00%
0 / 4
132
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 register_routes
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 1
2
 get_items
0.00% covered (danger)
0.00%
0 / 64
0.00% covered (danger)
0.00%
0 / 1
56
 get_attribute_schema
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * WP_REST_Newspack_Articles_Controller file.
4 *
5 * @package WordPress
6 */
7
8// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound
9/**
10 * Class WP_REST_Newspack_Articles_Controller.
11 */
12class WP_REST_Newspack_Articles_Controller extends WP_REST_Controller {
13// phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound
14
15    /**
16     * Attribute schema.
17     *
18     * @var array
19     */
20    public $attribute_schema;
21
22    /**
23     * Constructs the controller.
24     *
25     * @access public
26     */
27    public function __construct() {
28        $this->namespace = 'newspack-blocks/v1';
29    }
30
31    /**
32     * Registers the necessary REST API routes.
33     *
34     * @access public
35     */
36    public function register_routes() {
37        // Endpoint to get articles on the front-end.
38        register_rest_route(
39            $this->namespace,
40            '/articles',
41            [
42                [
43                    'methods'             => WP_REST_Server::READABLE,
44                    'callback'            => [ $this, 'get_items' ],
45                    'args'                => $this->get_attribute_schema(),
46                    'permission_callback' => '__return_true',
47                ],
48            ]
49        );
50
51        // Endpoint to get articles in the editor, in regular/query mode.
52        register_rest_route(
53            $this->namespace,
54            '/newspack-blocks-posts',
55            [
56                'methods'             => \WP_REST_Server::READABLE,
57                'callback'            => [ 'Newspack_Blocks_API', 'posts_endpoint' ],
58                'args'                => array_merge(
59                    $this->get_attribute_schema(),
60                    [
61                        'exclude' => [ // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
62                            'type'    => 'array',
63                            'items'   => array(
64                                'type' => 'integer',
65                            ),
66                            'default' => array(),
67                        ],
68                        'include' => [
69                            'type'    => 'array',
70                            'items'   => array(
71                                'type' => 'integer',
72                            ),
73                            'default' => array(),
74                        ],
75                    ]
76                ),
77                'permission_callback' => function() {
78                    return current_user_can( 'edit_posts' );
79                },
80            ]
81        );
82
83        // Endpoint to get articles in the editor, in specific posts mode.
84        register_rest_route(
85            $this->namespace,
86            '/newspack-blocks-specific-posts',
87            [
88                'methods'             => \WP_REST_Server::READABLE,
89                'callback'            => [ 'Newspack_Blocks_API', 'specific_posts_endpoint' ],
90                'args'                => [
91                    'search'      => [
92                        'sanitize_callback' => 'sanitize_text_field',
93                    ],
94                    'postsToShow' => [
95                        'sanitize_callback' => 'absint',
96                    ],
97                    'postType'    => [
98                        'type'    => 'array',
99                        'items'   => array(
100                            'type' => 'string',
101                        ),
102                        'default' => array(),
103                    ],
104                ],
105                'permission_callback' => function() {
106                    return current_user_can( 'edit_posts' );
107                },
108            ]
109        );
110    }
111
112    /**
113     * Returns a list of rendered posts.
114     *
115     * @param WP_REST_Request $request Request object.
116     * @return WP_REST_Response
117     */
118    public function get_items( $request ) {
119        $page        = (int) $request->get_param( 'page' ) ?? 1;
120        $exclude_ids = $request->get_param( 'exclude_ids' ) ?? [];
121        $next_page   = $page + 1;
122        $attributes  = wp_parse_args(
123            $request->get_params() ?? [],
124            wp_list_pluck( $this->get_attribute_schema(), 'default' )
125        );
126
127        $deduplicate = $request->get_param( 'deduplicate' ) ?? 1;
128        if ( ! $deduplicate ) {
129            $exclude_ids = [];
130        }
131
132        $article_query_args = Newspack_Blocks::build_articles_query( $attributes, apply_filters( 'newspack_blocks_block_name', 'newspack-blocks/homepage-articles' ) );
133
134        // If using exclude_ids, don't worry about pagination. Just get the next postsToShow number of results without the excluded posts. Otherwise, use standard WP pagination.
135        $query = ! empty( $exclude_ids ) ?
136            array_merge(
137                $article_query_args,
138                [
139                    'post__not_in' => $exclude_ids, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
140                ]
141            ) :
142            array_merge(
143                $article_query_args,
144                [
145                    'paged' => $page,
146                ]
147            );
148
149        // Run Query.
150        $article_query = new WP_Query( $query );
151
152        // Defaults.
153        $items    = [];
154        $ids      = [];
155        $next_url = '';
156
157        Newspack_Blocks::filter_excerpt( $attributes );
158
159        // The Loop.
160        while ( $article_query->have_posts() ) {
161            $article_query->the_post();
162            $html = Newspack_Blocks::template_inc(
163                __DIR__ . '/templates/article.php',
164                [
165                    'attributes' => $attributes,
166                ]
167            );
168
169            $items[]['html'] = $html;
170            $ids[]           = get_the_ID();
171        }
172
173        Newspack_Blocks::remove_excerpt_filter();
174
175        // Provide next URL if there are more pages.
176        $show_next_button = ! empty( $exclude_ids ) ? $article_query->max_num_pages > 1 : $article_query->max_num_pages > $next_page;
177        if ( $show_next_button ) {
178            $next_url = add_query_arg(
179                array_merge(
180                    array_map(
181                        function( $attribute ) {
182                            return false === $attribute ? '0' : $attribute;
183                        },
184                        $attributes
185                    ),
186                    [
187                        'exclude_ids' => false,
188                        'page'        => $next_page,
189                    ]
190                ),
191                rest_url( '/newspack-blocks/v1/articles' )
192            );
193        }
194
195        return rest_ensure_response(
196            [
197                'items' => $items,
198                'ids'   => $ids,
199                'next'  => $next_url,
200            ]
201        );
202    }
203
204    /**
205     * Sets up and returns attribute schema.
206     *
207     * @return array
208     */
209    public function get_attribute_schema() {
210        if ( empty( $this->attribute_schema ) ) {
211            $block_json = json_decode(
212                file_get_contents( __DIR__ . '/block.json' ), // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
213                true
214            );
215
216            $this->attribute_schema = array_merge(
217                $block_json['attributes'],
218                [
219                    'exclude_ids' => [
220                        'type'    => 'array',
221                        'default' => [],
222                        'items'   => [
223                            'type' => 'integer',
224                        ],
225                    ],
226                ]
227            );
228        }
229        return $this->attribute_schema;
230    }
231}