Skip to content
Draft
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [v3.10.0] - 2026-11-12

- Feat: declare REST API route args, so params are documented and validated by WordPress (malformed values now return a 400)
- Fix: only show draft and scheduled themes through draft-preview to logged-in users

## [v3.9.6] - 2026-11-12

- Fix: allow OpenPub taxonomy filter params in REST API queries
Expand Down
2 changes: 1 addition & 1 deletion openpub-base.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Plugin Name: Yard | OpenPub Base
* Plugin URI: https://www.openwebconcept.nl/
* Description: Acts as foundation for other OpenPub related content plugins. This plugin implements actions to allow for other plugins to add and/or change Custom Posttypes, Metaboxes, Taxonomies, en Posts 2 posts relations.
* Version: 3.9.6
* Version: 3.10.0
* Author: Yard | Digital Agency
* Author URI: https://www.yard.nl/
* License: GPL-3.0
Expand Down
2 changes: 1 addition & 1 deletion src/Base/Foundation/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class Plugin
{
public const NAME = 'openpub-base';
public const VERSION = '3.9.6';
public const VERSION = '3.10.0';

protected string $rootPath;
public Config $config;
Expand Down
43 changes: 41 additions & 2 deletions src/Base/RestAPI/Controllers/BaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,47 @@ protected function getPaginatorParams(WP_REST_Request $request, int $limit = 10)
*/
protected function getTaxonomyParams(WP_REST_Request $request): array
{
$queryVars = array_filter(wp_list_pluck(get_object_taxonomies('openpub-item', 'objects'), 'query_var'));
return array_intersect_key($request->get_params(), $this->taxonomyArguments());
}

public function paginatorArguments(): array
{
return [
'limit' => [
'description' => __('Number of items per page, -1 for all items.', 'openpub-base'),
'type' => 'integer',
'minimum' => -1,
'default' => 10,
],
'page' => [
'description' => __('Page number.', 'openpub-base'),
'type' => 'integer',
'minimum' => 1,
'default' => 1,
],
];
}

/**
* Route args for the taxonomy query vars getTaxonomyParams() passes to WP_Query.
*/
public function taxonomyArguments(): array
{
$args = [];

foreach (get_object_taxonomies('openpub-item', 'objects') as $taxonomy) {
if (empty($taxonomy->query_var)) {
continue;
}

// String first, so values reach WP_Query unsplit and keep its "a+b" (AND) syntax.
$args[$taxonomy->query_var] = [
'description' => sprintf(__('Filter on %s term slugs.', 'openpub-base'), $taxonomy->name),
'type' => ['string', 'array'],
'items' => ['type' => 'string'],
];
}

return array_intersect_key($request->get_params(), array_flip($queryVars));
return $args;
}
}
89 changes: 54 additions & 35 deletions src/Base/RestAPI/Controllers/ItemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,67 @@ public function getActiveItems(WP_REST_Request $request): array
return $this->response($items);
}

/**
* Route args for the items and items/active endpoints.
*/
public function collectionArguments(): array
{
return array_merge($this->paginatorArguments(), $this->filterArguments(), [
'highlighted' => [
'description' => __('Only highlighted items (true) or only non-highlighted items (false).', 'openpub-base'),
'type' => 'boolean',
],
], $this->taxonomyArguments());
}

/**
* Route args for the single item endpoints; the filters apply to the related items.
*/
public function singleArguments(): array
{
return array_merge($this->filterArguments(), [
'draft-preview' => [
'description' => __('Include draft and scheduled items, for logged-in users.', 'openpub-base'),
'type' => 'boolean',
],
]);
}

protected function filterArguments(): array
{
return [
'type' => [
'description' => __('Filter on openpub-type term slugs.', 'openpub-base'),
'type' => 'array',
'items' => ['type' => 'string'],
],
'audience' => [
'description' => __('Filter on openpub-audience term slugs.', 'openpub-base'),
'type' => 'array',
'items' => ['type' => 'string'],
],
'source' => [
'description' => __('Filter on the blog ID the items are shown on, when "show on" is enabled.', 'openpub-base'),
'type' => 'integer',
],
];
}

protected function itemQueryBuilder(WP_REST_Request $request = null): Item
{
$items = new Item();
$items = $items->query(apply_filters('owc/openpub/rest-api/items/query', $this->getPaginatorParams($request)));

if ($this->hasHighlightedParam($request)) {
$items->query(Item::addHighlightedParameters($this->getHighlightedParam($request)));
if (null !== $request->get_param('highlighted')) {
$items->query(Item::addHighlightedParameters(rest_sanitize_boolean($request->get_param('highlighted'))));
}

if ($this->getTypeParam($request)) {
$items->query(Item::addTypeParameter($this->getTypeParam($request)));
}

if ($this->showOnParamIsValid($request) && $this->plugin->settings->useShowOn()) {
$items->query(Item::addShowOnParameter($request->get_param('source')));
if ($request->get_param('source') && $this->plugin->settings->useShowOn()) {
$items->query(Item::addShowOnParameter((string) $request->get_param('source')));
}

if ($this->getAudienceParam($request)) {
Expand Down Expand Up @@ -145,8 +191,8 @@ protected function addRelated(array $item, WP_REST_Request $request): array
$items->query(Item::addTypeParameter($type));
}

if ($this->showOnParamIsValid($request) && $this->plugin->settings->useShowOn()) {
$items->query(Item::addShowOnParameter($request->get_param('source')));
if ($request->get_param('source') && $this->plugin->settings->useShowOn()) {
$items->query(Item::addShowOnParameter((string) $request->get_param('source')));
}

if ($this->getAudienceParam($request)) {
Expand Down Expand Up @@ -183,24 +229,6 @@ public function getImageUrl(WP_Post $post): array
return (new FeaturedImageField($this->plugin))->create($post);
}

protected function getHighlightedParam(WP_REST_Request $request): bool
{
return filter_var($request->get_param('highlighted'), FILTER_VALIDATE_BOOLEAN);
}

protected function hasHighlightedParam(WP_REST_Request $request): bool
{
if (empty($request->get_param('highlighted'))) {
return false;
}

if (! $this->validateBoolean($request->get_param('highlighted'))) {
return false;
};

return true;
}

/**
* Get the type slugs already attached to an item, comma-separated.
*
Expand Down Expand Up @@ -236,20 +264,11 @@ protected function getTypeParam(WP_REST_Request $request): string
return is_string($typeParam) ? $typeParam : '';
}

protected function validateBoolean(string $value): bool
{
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

if (null === $value) {
return false;
}

return true;
}

/**
* Validate if show on param is valid.
* Param should be a numeric value.
*
* @deprecated 3.10.0 The source route arg is validated as an integer; kept for child plugins built against older versions.
*/
protected function showOnParamIsValid(WP_REST_Request $request): bool
{
Expand Down
17 changes: 17 additions & 0 deletions src/Base/RestAPI/Controllers/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,21 @@ public function search(WP_REST_Request $request): array

return $this->response($search);
}

public function arguments(): array
{
return array_merge([
's' => [
'description' => __('The search term.', 'openpub-base'),
'type' => 'string',
],
'posts_per_page' => [
'description' => __('Number of results per page, at most 10.', 'openpub-base'),
'type' => 'integer',
'minimum' => 1,
'default' => 10,
],
'page' => $this->paginatorArguments()['page'],
], $this->taxonomyArguments());
}
}
12 changes: 11 additions & 1 deletion src/Base/RestAPI/Controllers/ThemeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ public function getThemes(WP_REST_Request $request)
return $this->response($themes);
}

public function singleArguments(): array
{
return [
'draft-preview' => [
'description' => __('Include draft and scheduled themes, for logged-in users.', 'openpub-base'),
'type' => 'boolean',
],
];
}

/**
* Get an individual post theme by slug.
*
Expand Down Expand Up @@ -58,7 +68,7 @@ public function singleThemeQueryBuilder(WP_REST_Request $request): Theme

$preview = filter_var($request->get_param('draft-preview'), FILTER_VALIDATE_BOOLEAN);

if (true === $preview) {
if (true === $preview && is_user_logged_in()) {
$theme->query(['post_status' => ['publish', 'draft', 'future']]);
}

Expand Down
26 changes: 18 additions & 8 deletions src/Base/RestAPI/RestAPIServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,46 +65,56 @@ public function register(): void
*/
public function registerRoutes(): void
{
$itemController = new ItemController($this->plugin);
$themeController = new ThemeController($this->plugin);
$searchController = new SearchController($this->plugin);

register_rest_route($this->namespace, 'items', [
'methods' => WP_REST_Server::READABLE,
'callback' => [new ItemController($this->plugin), 'getItems'],
'callback' => [$itemController, 'getItems'],
'args' => $itemController->collectionArguments(),
'permission_callback' => '__return_true',
]);

register_rest_route($this->namespace, 'items/active', [
'methods' => WP_REST_Server::READABLE,
'callback' => [new ItemController($this->plugin), 'getActiveItems'],
'callback' => [$itemController, 'getActiveItems'],
'args' => $itemController->collectionArguments(),
'permission_callback' => '__return_true',
]);

register_rest_route($this->namespace, 'items/(?P<id>\d+)', [
'methods' => WP_REST_Server::READABLE,
'callback' => [new ItemController($this->plugin), 'getItem'],
'callback' => [$itemController, 'getItem'],
'args' => $itemController->singleArguments(),
'permission_callback' => '__return_true',
]);

register_rest_route($this->namespace, 'items/(?P<slug>[\w-]+)', [
'methods' => WP_REST_Server::READABLE,
'callback' => [new ItemController($this->plugin), 'getItemBySlug'],
'callback' => [$itemController, 'getItemBySlug'],
'args' => $itemController->singleArguments(),
'permission_callback' => '__return_true',
]);

register_rest_route($this->namespace, 'themes', [
'methods' => WP_REST_Server::READABLE,
'callback' => [new ThemeController($this->plugin), 'getThemes'],
'callback' => [$themeController, 'getThemes'],
'args' => $themeController->paginatorArguments(),
'permission_callback' => '__return_true',
]);

register_rest_route($this->namespace, 'themes/(?P<slug>[\w-]+)', [
'methods' => WP_REST_Server::READABLE,
'callback' => [new ThemeController($this->plugin), 'getThemeBySlug'],
'callback' => [$themeController, 'getThemeBySlug'],
'args' => $themeController->singleArguments(),
'permission_callback' => '__return_true',
]);

register_rest_route($this->namespace, 'search', [
'methods' => WP_REST_Server::READABLE,
'callback' => [new SearchController($this->plugin), 'search'],
'args' => [],
'callback' => [$searchController, 'search'],
'args' => $searchController->arguments(),
'permission_callback' => '__return_true',
]);

Expand Down
Loading