diff --git a/app/functions.php b/app/functions.php
index ddc1ed6..4de91dd 100644
--- a/app/functions.php
+++ b/app/functions.php
@@ -13,6 +13,7 @@ new \Sakura\Helpers\WhoopsHelper();
new \Sakura\Helpers\ViteRequireHelper();
new \Sakura\Helpers\CustomMenuMetaFieldsHelper();
new \Sakura\Helpers\CommentHelper();
+new \Sakura\Helpers\PostQueryHelper('post');
new \Sakura\Routers\ApiRouter();
new \Sakura\Routers\PagesRouter();
diff --git a/app/helpers/post-query-helper.php b/app/helpers/post-query-helper.php
new file mode 100644
index 0000000..9ba730e
--- /dev/null
+++ b/app/helpers/post-query-helper.php
@@ -0,0 +1,51 @@
+post_type = $post_type;
+ add_filter("rest_{$post_type}_query", [$this, 'filter_rest_post_query'], 10, 2);
+ }
+
+ /**
+ * Filter rest posts by category slug
+ *
+ * @param array $args
+ * @param WP_Rest_Rquest $request
+ * @return array $args
+ */
+ public function filter_rest_post_query($args, $request)
+ {
+ $args['tax_query'] = [];
+
+ $taxonomies = wp_list_filter(get_object_taxonomies($this->post_type, 'objects'), array('show_in_rest' => true));
+
+ foreach ($taxonomies as $taxonomy) {
+ $base = !empty($taxonomy->rest_base) ? $taxonomy->rest_base : $taxonomy->name;
+
+ if (!isset($request["{$base}_slug"])) {
+ continue;
+ }
+
+ array_push($args['tax_query'], [
+ 'taxonomy' => $taxonomy->name,
+ 'field' => 'slug',
+ 'terms' => explode(',', $request["{$base}_slug"]),
+ 'include_children' => true,
+ 'operator' => 'IN',
+ ]);
+ }
+ return $args;
+ }
+}
diff --git a/app/helpers/rest-api-filter-helper.php b/app/helpers/rest-api-filter-helper.php
new file mode 100644
index 0000000..a941a77
--- /dev/null
+++ b/app/helpers/rest-api-filter-helper.php
@@ -0,0 +1,58 @@
+post_type}_query", array $args, WP_REST_Request $request ) or filter instead
+ */
+class RestApiFilterHelper
+{
+ public function __construct()
+ {
+ add_action('rest_api_init', [$this, 'rest_api_filter_add_filters']);
+ }
+
+ /**
+ * Add the necessary filter to each post type
+ **/
+ public function rest_api_filter_add_filters()
+ {
+ foreach (get_post_types(array('show_in_rest' => true), 'objects') as $post_type) {
+ add_filter('rest_' . $post_type->name . '_query', [$this, 'rest_api_filter_add_filter_param'], 10, 2);
+ }
+ }
+
+ /**
+ * Add the filter parameter
+ *
+ * @param array $args The query arguments.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array $args.
+ **/
+ public function rest_api_filter_add_filter_param($args, $request)
+ {
+ // Bail out if no filter parameter is set.
+ if (empty($request['filter']) || !is_array($request['filter'])) {
+ return $args;
+ }
+
+ $filter = $request['filter'];
+
+ if (isset($filter['posts_per_page']) && ((int) $filter['posts_per_page'] >= 1 && (int) $filter['posts_per_page'] <= 100)) {
+ $args['posts_per_page'] = $filter['posts_per_page'];
+ }
+
+ global $wp;
+ $vars = apply_filters('rest_query_vars', $wp->public_query_vars);
+
+ // Allow valid meta query vars.
+ $vars = array_unique(array_merge($vars, array('meta_query', 'meta_key', 'meta_value', 'meta_compare')));
+
+ foreach ($vars as $var) {
+ if (isset($filter[$var])) {
+ $args[$var] = $filter[$var];
+ }
+ }
+ return $args;
+ }
+}
diff --git a/app/lib/class-wp-rest-posts-controller.php b/app/lib/class-wp-rest-posts-controller.php
new file mode 100644
index 0000000..8a17176
--- /dev/null
+++ b/app/lib/class-wp-rest-posts-controller.php
@@ -0,0 +1,357 @@
+post_type}_query", array $args, WP_REST_Request $request ) instead
+ */
+class ClassWpRestPostsController extends WP_REST_Posts_Controller
+{
+ /**
+ * Retrieves a collection of posts.
+ * Source: https://github.com/WordPress/wordpress-develop/blob/master/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
+ * Memo: only change $registered = $this->get_collection_params_mod();
+ * Based on commit 5383af8
+ *
+ * @since 4.7.0
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function get_items($request)
+ {
+ // Ensure a search string is set in case the orderby is set to 'relevance'.
+ if (!empty($request['orderby']) && 'relevance' === $request['orderby'] && empty($request['search'])) {
+ return new WP_Error(
+ 'rest_no_search_term_defined',
+ __('You need to define a search term to order by relevance.'),
+ array('status' => 400)
+ );
+ }
+
+ // Ensure an include parameter is set in case the orderby is set to 'include'.
+ if (!empty($request['orderby']) && 'include' === $request['orderby'] && empty($request['include'])) {
+ return new WP_Error(
+ 'rest_orderby_include_missing_include',
+ __('You need to define an include parameter to order by include.'),
+ array('status' => 400)
+ );
+ }
+
+ // Retrieve the list of registered collection query parameters.
+ $registered = $this->get_collection_params_mod();
+ $args = array();
+
+ /*
+ * This array defines mappings between public API query parameters whose
+ * values are accepted as-passed, and their internal WP_Query parameter
+ * name equivalents (some are the same). Only values which are also
+ * present in $registered will be set.
+ */
+ $parameter_mappings = array(
+ 'author' => 'author__in',
+ 'author_exclude' => 'author__not_in',
+ 'exclude' => 'post__not_in',
+ 'include' => 'post__in',
+ 'menu_order' => 'menu_order',
+ 'offset' => 'offset',
+ 'order' => 'order',
+ 'orderby' => 'orderby',
+ 'page' => 'paged',
+ 'parent' => 'post_parent__in',
+ 'parent_exclude' => 'post_parent__not_in',
+ 'search' => 's',
+ 'slug' => 'post_name__in',
+ 'status' => 'post_status',
+ );
+
+ /*
+ * For each known parameter which is both registered and present in the request,
+ * set the parameter's value on the query $args.
+ */
+ foreach ($parameter_mappings as $api_param => $wp_param) {
+ if (isset($registered[$api_param], $request[$api_param])) {
+ $args[$wp_param] = $request[$api_param];
+ }
+ }
+
+ // Check for & assign any parameters which require special handling or setting.
+ $args['date_query'] = array();
+
+ if (isset($registered['before'], $request['before'])) {
+ $args['date_query'][] = array(
+ 'before' => $request['before'],
+ 'column' => 'post_date',
+ );
+ }
+
+ if (isset($registered['modified_before'], $request['modified_before'])) {
+ $args['date_query'][] = array(
+ 'before' => $request['modified_before'],
+ 'column' => 'post_modified',
+ );
+ }
+
+ if (isset($registered['after'], $request['after'])) {
+ $args['date_query'][] = array(
+ 'after' => $request['after'],
+ 'column' => 'post_date',
+ );
+ }
+
+ if (isset($registered['modified_after'], $request['modified_after'])) {
+ $args['date_query'][] = array(
+ 'after' => $request['modified_after'],
+ 'column' => 'post_modified',
+ );
+ }
+
+ // Ensure our per_page parameter overrides any provided posts_per_page filter.
+ if (isset($registered['per_page'])) {
+ $args['posts_per_page'] = $request['per_page'];
+ }
+
+ if (isset($registered['sticky'], $request['sticky'])) {
+ $sticky_posts = get_option('sticky_posts', array());
+ if (!is_array($sticky_posts)) {
+ $sticky_posts = array();
+ }
+ if ($request['sticky']) {
+ /*
+ * As post__in will be used to only get sticky posts,
+ * we have to support the case where post__in was already
+ * specified.
+ */
+ $args['post__in'] = $args['post__in'] ? array_intersect($sticky_posts, $args['post__in']) : $sticky_posts;
+
+ /*
+ * If we intersected, but there are no post IDs in common,
+ * WP_Query won't return "no posts" for post__in = array()
+ * so we have to fake it a bit.
+ */
+ if (!$args['post__in']) {
+ $args['post__in'] = array(0);
+ }
+ } elseif ($sticky_posts) {
+ /*
+ * As post___not_in will be used to only get posts that
+ * are not sticky, we have to support the case where post__not_in
+ * was already specified.
+ */
+ $args['post__not_in'] = array_merge($args['post__not_in'], $sticky_posts);
+ }
+ }
+
+ $args = $this->prepare_tax_query($args, $request);
+
+ // Force the post_type argument, since it's not a user input variable.
+ $args['post_type'] = $this->post_type;
+
+ /**
+ * Filters WP_Query arguments when querying posts via the REST API.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * Possible hook names include:
+ *
+ * - `rest_post_query`
+ * - `rest_page_query`
+ * - `rest_attachment_query`
+ *
+ * Enables adding extra arguments or setting defaults for a post collection request.
+ *
+ * @since 4.7.0
+ * @since 5.7.0 Moved after the `tax_query` query arg is generated.
+ *
+ * @link https://developer.wordpress.org/reference/classes/wp_query/
+ *
+ * @param array $args Array of arguments for WP_Query.
+ * @param WP_REST_Request $request The REST API request.
+ */
+ $args = apply_filters("rest_{$this->post_type}_query", $args, $request);
+ $query_args = $this->prepare_items_query($args, $request);
+ $posts_query = new WP_Query();
+ $query_result = $posts_query->query($query_args);
+
+ // Allow access to all password protected posts if the context is edit.
+ if ('edit' === $request['context']) {
+ add_filter('post_password_required', array($this, 'check_password_required'), 10, 2);
+ }
+
+ $posts = array();
+
+ foreach ($query_result as $post) {
+ if (!$this->check_read_permission($post)) {
+ continue;
+ }
+
+ $data = $this->prepare_item_for_response($post, $request);
+ $posts[] = $this->prepare_response_for_collection($data);
+ }
+
+ // Reset filter.
+ if ('edit' === $request['context']) {
+ remove_filter('post_password_required', array($this, 'check_password_required'));
+ }
+
+ $page = (int) $query_args['paged'];
+ $total_posts = $posts_query->found_posts;
+
+ if ($total_posts < 1) {
+ // Out-of-bounds, run the query again without LIMIT for total count.
+ unset($query_args['paged']);
+
+ $count_query = new WP_Query();
+ $count_query->query($query_args);
+ $total_posts = $count_query->found_posts;
+ }
+
+ $max_pages = ceil($total_posts / (int) $posts_query->query_vars['posts_per_page']);
+
+ if ($page > $max_pages && $total_posts > 0) {
+ return new WP_Error(
+ 'rest_post_invalid_page_number',
+ __('The page number requested is larger than the number of pages available.'),
+ array('status' => 400)
+ );
+ }
+
+ $response = rest_ensure_response($posts);
+
+ $response->header('X-WP-Total', (int) $total_posts);
+ $response->header('X-WP-TotalPages', (int) $max_pages);
+
+ $request_params = $request->get_query_params();
+ $base = add_query_arg(urlencode_deep($request_params), rest_url(sprintf('%s/%s', $this->namespace, $this->rest_base)));
+
+ if ($page > 1) {
+ $prev_page = $page - 1;
+
+ if ($prev_page > $max_pages) {
+ $prev_page = $max_pages;
+ }
+
+ $prev_link = add_query_arg('page', $prev_page, $base);
+ $response->link_header('prev', $prev_link);
+ }
+ if ($max_pages > $page) {
+ $next_page = $page + 1;
+ $next_link = add_query_arg('page', $next_page, $base);
+
+ $response->link_header('next', $next_link);
+ }
+
+ return $response;
+ }
+
+ /**
+ * Prepares the 'tax_query' for a collection of posts.
+ *
+ * @since 5.7.0
+ *
+ * @param array $args WP_Query arguments.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array Updated query arguments.
+ */
+ private function prepare_tax_query(array $args, WP_REST_Request $request)
+ {
+ $relation = $request['tax_relation'];
+
+ if ($relation) {
+ $args['tax_query'] = array('relation' => $relation);
+ }
+
+ $taxonomies = wp_list_filter(
+ get_object_taxonomies($this->post_type, 'objects'),
+ array('show_in_rest' => true)
+ );
+
+ foreach ($taxonomies as $taxonomy) {
+ $base = !empty($taxonomy->rest_base) ? $taxonomy->rest_base : $taxonomy->name;
+
+ $tax_include = $request[$base];
+ $tax_exclude = $request[$base . '_exclude'];
+
+ if ($tax_include) {
+ $terms = array();
+ $include_children = false;
+ $operator = 'IN';
+
+ if (rest_is_array($tax_include)) {
+ $terms = $tax_include;
+ } elseif (rest_is_object($tax_include)) {
+ $terms = empty($tax_include['terms']) ? array() : $tax_include['terms'];
+ $include_children = !empty($tax_include['include_children']);
+
+ if (isset($tax_include['operator']) && 'AND' === $tax_include['operator']) {
+ $operator = 'AND';
+ }
+ }
+
+ if ($terms) {
+ $args['tax_query'][] = array(
+ 'taxonomy' => $taxonomy->name,
+ 'field' => 'slug', // 'term_id',
+ 'terms' => $terms,
+ 'include_children' => $include_children,
+ 'operator' => $operator,
+ );
+ }
+ }
+
+ if ($tax_exclude) {
+ $terms = array();
+ $include_children = false;
+
+ if (rest_is_array($tax_exclude)) {
+ $terms = $tax_exclude;
+ } elseif (rest_is_object($tax_exclude)) {
+ $terms = empty($tax_exclude['terms']) ? array() : $tax_exclude['terms'];
+ $include_children = !empty($tax_exclude['include_children']);
+ }
+
+ if ($terms) {
+ $args['tax_query'][] = array(
+ 'taxonomy' => $taxonomy->name,
+ 'field' => 'term_id',
+ 'terms' => $terms,
+ 'include_children' => $include_children,
+ 'operator' => 'NOT IN',
+ );
+ }
+ }
+ }
+
+ return $args;
+ }
+
+ public function get_public_item_schema_mod()
+ {
+ $schema = $this->get_public_item_schema();
+ $schema['properties']['categories']['items']['type'] = [
+ "string",
+ "integer"
+ ];
+ return $schema;
+ }
+
+ public function get_collection_params_mod()
+ {
+ $params = $this->get_collection_params();
+ $new = [
+ "title" => "Term Slug",
+ "description" => "Match terms with the listed Slug.",
+ "type" => "array",
+ "items" => [
+ "type" => "string"
+ ]
+ ];
+ $params['categories']['oneOf'][0] = $new;
+ return $params;
+ }
+}
diff --git a/app/routers/api-router.php b/app/routers/api-router.php
index bb7b32f..6d588ba 100644
--- a/app/routers/api-router.php
+++ b/app/routers/api-router.php
@@ -13,6 +13,7 @@ use Sakura\Controllers\CategoryController;
use Sakura\Controllers\TagController;
use Sakura\Controllers\CommentController;
use Sakura\Lib\ClassWpRestCommentsController;
+use Sakura\Lib\ClassWpRestPostsController;
class ApiRouter extends WP_REST_Controller
{
@@ -135,6 +136,29 @@ class ApiRouter extends WP_REST_Controller
'schema' => array($this, 'get_public_item_schema'),
)
);
+
+ // @deprecated using PostQueryHelper instaed
+ // $rest_posts_controller = new ClassWpRestPostsController('post');
+ // custom get posts by category slugs
+ // register_rest_route(
+ // $this->namespace,
+ // '/posts',
+ // array(
+ // array(
+ // 'methods' => WP_REST_Server::READABLE,
+ // 'callback' => array($rest_posts_controller, 'get_items'),
+ // 'permission_callback' => array($rest_posts_controller, 'get_items_permissions_check'),
+ // 'args' => $rest_posts_controller->get_collection_params_mod(),
+ // ),
+ // array(
+ // 'methods' => WP_REST_Server::CREATABLE,
+ // 'callback' => array($rest_posts_controller, 'create_item'),
+ // 'permission_callback' => array($rest_posts_controller, 'create_item_permissions_check'),
+ // 'args' => $rest_posts_controller->get_endpoint_args_for_item_schema(WP_REST_Server::CREATABLE),
+ // ),
+ // 'schema' => array($rest_posts_controller, 'get_public_item_schema_mod'),
+ // )
+ // );
});
}
diff --git a/src/api/Wp/v2/index.ts b/src/api/Wp/v2/index.ts
index 46ae761..3ff5370 100644
--- a/src/api/Wp/v2/index.ts
+++ b/src/api/Wp/v2/index.ts
@@ -1,8 +1,6 @@
import request, { AxiosPromise } from '@/utils/http'
import snakecaseKeys from 'snakecase-keys'
-type WpPostObjectFilter = number | string | number[] | string[]
-
/**
* GET /wp/v2/posts
* https://developer.wordpress.org/rest-api/reference/posts/#arguments
@@ -16,8 +14,8 @@ export interface GetPostParams {
author?: string | number
authorExclude?: string | number
before?: string // ISO8601
- exclude?: WpPostObjectFilter // TODO: check this
- include?: WpPostObjectFilter // TODO: check this
+ exclude?: number | number[]
+ include?: number | number[]
offset?: number
order?: 'asc' | 'desc' // default: desc
orderby?:
@@ -31,13 +29,15 @@ export interface GetPostParams {
| 'slug'
| 'include_slugs'
| 'title' // default: date
- slug?: WpPostObjectFilter // TODO: check this
+ slug?: string
status?: string // default: 'publish'
taxRelation?: 'AND' | 'OR'
- categories?: WpPostObjectFilter // TODO: check this
- categoriesExclude?: WpPostObjectFilter // TODO: check this
- tags?: WpPostObjectFilter // TODO: check this
- tagsExclude?: WpPostObjectFilter // TODO: check this
+ categories?: number | number[]
+ categoriesExclude?: number | number[]
+ categoriesSlug?: string
+ tags?: number | number[]
+ tagsExclude?: number | number[]
+ tagsSlug?: string
sticky?: boolean // TODO: check this
}
@@ -48,8 +48,10 @@ export interface GetPageParams
| 'taxRelation'
| 'categories'
| 'categoriesExclude'
+ | 'categoriesSlug'
| 'tags'
| 'tagsExclude'
+ | 'tagsSlug'
| 'sticky'
> {
menuOrder: any // TODO: check this
@@ -79,8 +81,8 @@ export interface GetCommentParams {
authorExclude?: string | number
author_email?: string
before?: string // ISO8601
- exclude?: WpPostObjectFilter // TODO: check this
- include?: WpPostObjectFilter // TODO: check this
+ exclude?: number | number[] // TODO: check this
+ include?: number | number[] // TODO: check this
offset?: number
order?: 'asc' | 'desc' // default: desc
orderby?: 'date' | 'date_gmt' | 'id' | 'include' | 'post' | 'parent' | 'type' // default: date
diff --git a/src/components/image/Image.vue b/src/components/image/Image.vue
index 12ce17f..9169f75 100644
--- a/src/components/image/Image.vue
+++ b/src/components/image/Image.vue
@@ -8,7 +8,13 @@
@error="handleError"
@load="handleLoad"
/>
-
+
diff --git a/src/components/lists/postThembList/PostThumbList.vue b/src/components/lists/postThumbList/PostThumbList.vue
similarity index 93%
rename from src/components/lists/postThembList/PostThumbList.vue
rename to src/components/lists/postThumbList/PostThumbList.vue
index 99c589c..217ee05 100644
--- a/src/components/lists/postThembList/PostThumbList.vue
+++ b/src/components/lists/postThumbList/PostThumbList.vue
@@ -29,6 +29,12 @@ export default defineComponent({
page: { type: Number, default: 1 },
perPage: { type: Number, default: 10 },
autoLoad: { type: Boolean, default: true },
+ fetchParameters: {
+ type: Object,
+ default: () => {
+ return {}
+ },
+ },
},
setup(props) {
const [listContainerRef, setListContainerRef] = useElementRef()
@@ -64,7 +70,12 @@ export default defineComponent({
fetchPost({
state: postsStore,
namespace: props.namespace,
- opts: { page: currentPage.value, perPage: props.perPage, context: 'embed' },
+ opts: {
+ page: currentPage.value,
+ perPage: props.perPage,
+ context: 'embed',
+ ...props.fetchParameters,
+ },
}).then(() => {
get()
setFetchStatus('done')
diff --git a/src/views/Category.vue b/src/views/Category.vue
index 6614b01..e67b42b 100644
--- a/src/views/Category.vue
+++ b/src/views/Category.vue
@@ -1,11 +1,31 @@
- Category
+