nextpress-core 1.0.0
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/lib/acf-functions/core/acf-builder.ts +105 -0
- package/lib/acf-functions/core/acf-component-autoloader.ts +33 -0
- package/lib/acf-functions/core/acf-field-group-autoloader.ts +28 -0
- package/lib/acf-functions/services/define-field-group.ts +10 -0
- package/lib/acf-functions/services/define-layout.ts +10 -0
- package/lib/acf-functions/services/map-fields/helpers/map-choice-object.ts +20 -0
- package/lib/acf-functions/services/map-fields/map-fields.ts +359 -0
- package/lib/acf-functions/types/acf-field-group.d.ts +118 -0
- package/lib/acf-functions/types/acf-field.d.ts +2851 -0
- package/lib/acf-functions/types/acf-layout.d.ts +20 -0
- package/lib/acf-functions/types/acf-values.d.ts +4 -0
- package/lib/acf-functions/types/components/field-props.d.ts +189 -0
- package/lib/acf-functions/types/components/nextpress-component.d.ts +11 -0
- package/lib/ambient.d.ts +15 -0
- package/lib/entities/common.d.ts +30 -0
- package/lib/entities/option/option.interface.d.ts +8 -0
- package/lib/entities/option/option.ts +43 -0
- package/lib/entities/post/post.interface.d.ts +72 -0
- package/lib/entities/post/post.ts +209 -0
- package/lib/entities/term/term.interface.d.ts +8 -0
- package/lib/entities/term/term.ts +82 -0
- package/lib/entities/user/user.interface.d.ts +10 -0
- package/lib/entities/user/user.ts +86 -0
- package/lib/globals/entity-loader/entity-loader-base.ts +141 -0
- package/lib/globals/entity-loader/entity-loader.d.ts +50 -0
- package/lib/globals/entity-loader/option-loader.ts +56 -0
- package/lib/globals/entity-loader/post-loader.ts +59 -0
- package/lib/globals/entity-loader/term-loader.ts +59 -0
- package/lib/globals/entity-loader/user-loader.ts +60 -0
- package/lib/globals/get-field/get-field.ts +75 -0
- package/lib/globals/globals.ts +25 -0
- package/lib/globals/nextpress-config/nextpress-config.interface.d.ts +18 -0
- package/lib/globals/nextpress-config/nextpress-config.ts +7 -0
- package/lib/globals/queried-object/queried-object.ts +124 -0
- package/lib/repository/optionquery/option-query-args.d.ts +21 -0
- package/lib/repository/optionquery/option-query.ts +29 -0
- package/lib/repository/postquery/post-query-args.d.ts +108 -0
- package/lib/repository/postquery/post-query.ts +281 -0
- package/lib/repository/termquery/term-query-args.d.ts +61 -0
- package/lib/repository/termquery/term-query.ts +243 -0
- package/lib/repository/userquery/user-query-args.d.ts +75 -0
- package/lib/repository/userquery/user-query.ts +195 -0
- package/lib/router/helpers.ts +20 -0
- package/lib/router/nextpress-layout.tsx +48 -0
- package/lib/router/nextpress-proxy.ts +45 -0
- package/lib/router/nextpress-static-params.ts +62 -0
- package/lib/router/router.tsx +61 -0
- package/lib/router/routes/api/api-get-admin-bar.ts +29 -0
- package/lib/router/routes/api/api-get-draft-mode.ts +47 -0
- package/lib/router/routes/api/api-get-field-groups.ts +29 -0
- package/lib/router/routes/api/api-post-revalidate.ts +30 -0
- package/lib/router/routes/api/helpers.ts +17 -0
- package/lib/router/routes/author-archive.tsx +75 -0
- package/lib/router/routes/not-found-page.tsx +22 -0
- package/lib/router/routes/post-index-page.tsx +58 -0
- package/lib/router/routes/singular-page.tsx +78 -0
- package/lib/router/routes/site-front-page.tsx +51 -0
- package/lib/router/routes/term-archive.tsx +93 -0
- package/lib/router/types.d.ts +9 -0
- package/lib/services/get-menu.ts +104 -0
- package/lib/services/get-theme-mods.ts +16 -0
- package/lib/services/metadata/get-blogname.ts +9 -0
- package/lib/services/metadata/get-favicon-url.ts +13 -0
- package/lib/services/metadata/get-language-attribute.ts +9 -0
- package/lib/services/services.ts +7 -0
- package/lib/services/utilities/capitalise-first-letter.ts +10 -0
- package/lib/services/utilities/esc-html.ts +12 -0
- package/lib/services/utilities/get-date-time-formatter.ts +49 -0
- package/lib/services/utilities/index.ts +0 -0
- package/lib/services/utilities/kses-post.ts +13 -0
- package/lib/services/utilities/process-url.ts +15 -0
- package/lib/template-heirarchy/_autoloader/template-autoloader.ts +54 -0
- package/lib/template-heirarchy/archive/archive.tsx +28 -0
- package/lib/template-heirarchy/archive/author.tsx +28 -0
- package/lib/template-heirarchy/archive/category.tsx +29 -0
- package/lib/template-heirarchy/archive/posttypearchive.tsx +33 -0
- package/lib/template-heirarchy/archive/tag.tsx +30 -0
- package/lib/template-heirarchy/archive/taxonomy.tsx +28 -0
- package/lib/template-heirarchy/home/home.tsx +28 -0
- package/lib/template-heirarchy/index.tsx +30 -0
- package/lib/template-heirarchy/not-found.tsx/not-found.tsx +28 -0
- package/lib/template-heirarchy/page/page.tsx +28 -0
- package/lib/template-heirarchy/page/posttype.tsx +34 -0
- package/lib/template-heirarchy/page/single.tsx +28 -0
- package/lib/template-heirarchy/page/singular.tsx +28 -0
- package/lib/ui/render-attachment-image.tsx +29 -0
- package/lib/ui/render-components.tsx +12 -0
- package/lib/ui/render-the-admin-bar.tsx +79 -0
- package/lib/ui/render-the-logo.tsx +24 -0
- package/lib/wpdb/wpdb.interface.d.ts +171 -0
- package/lib/wpdb/wpdb.ts +36 -0
- package/package.json +38 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { cache } from 'react';
|
|
2
|
+
|
|
3
|
+
import './nextpress-config/nextpress-config'
|
|
4
|
+
import './entity-loader/post-loader';
|
|
5
|
+
import './entity-loader/term-loader';
|
|
6
|
+
import './entity-loader/user-loader';
|
|
7
|
+
import './entity-loader/option-loader';
|
|
8
|
+
import './get-field/get-field';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Initializes and returns request-scoped storage for the application state.
|
|
12
|
+
* Uses React cache to prevent state bleeding across requests.
|
|
13
|
+
*
|
|
14
|
+
* @returns {{ currentState: any, loaderStates: Record<string, any> }} Object containing current queried object state and loader states.
|
|
15
|
+
*/
|
|
16
|
+
export const queriedObjectState = cache(() => {
|
|
17
|
+
return {
|
|
18
|
+
currentState: null as any,
|
|
19
|
+
loaderStates: {} as Record<string, any>
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
import './queried-object/queried-object';
|
|
24
|
+
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type NextpressConfig = {
|
|
2
|
+
/** Custom taxonomies used to categorize and group content (like categories or tags). Expects the taxonomy's URL prefix, so tag instead of post_tag */
|
|
3
|
+
readonly publicTaxonomies?: string[],
|
|
4
|
+
|
|
5
|
+
/** Custom post types registered for public consumption (like posts, pages, or portfolio). */
|
|
6
|
+
readonly publicPostTypes?: string[],
|
|
7
|
+
|
|
8
|
+
/** Post types that should display archive pages on the frontend. */
|
|
9
|
+
readonly archivedPostTypes?: string[],
|
|
10
|
+
|
|
11
|
+
/** The word/character count limit for automatically generated post teasers. */
|
|
12
|
+
readonly excerptLength?: number,
|
|
13
|
+
|
|
14
|
+
/** Database option keys (from wp_options) to fetch early for performance optimization. */
|
|
15
|
+
readonly preLoadOptions?: string[],
|
|
16
|
+
} & {
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { IPost } from "../../entities/post/post.interface";
|
|
2
|
+
import { ITerm } from "../../entities/term/term.interface";
|
|
3
|
+
import { IUser } from "../../entities/user/user.interface";
|
|
4
|
+
import { queriedObjectState } from "../globals";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents the state of the queried object.
|
|
8
|
+
*/
|
|
9
|
+
interface IQueriedObject {
|
|
10
|
+
/** Type of the queried object. */
|
|
11
|
+
objectType: 'post' | 'term' | 'user' | null,
|
|
12
|
+
/** Array of post IDs associated with the query. */
|
|
13
|
+
posts: number[],
|
|
14
|
+
/** Current page number. */
|
|
15
|
+
page: number,
|
|
16
|
+
/** Total page count. */
|
|
17
|
+
pageCount: number,
|
|
18
|
+
/** ID of the main queried term. */
|
|
19
|
+
mainTerm?: number,
|
|
20
|
+
/** Array of term IDs associated with the query. */
|
|
21
|
+
terms: number[],
|
|
22
|
+
/** ID of the queried user. */
|
|
23
|
+
user?: number,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a blank queried object state.
|
|
28
|
+
*
|
|
29
|
+
* @returns {IQueriedObject} Blank state object.
|
|
30
|
+
*/
|
|
31
|
+
const createBlankState = (): IQueriedObject => ({
|
|
32
|
+
objectType: null,
|
|
33
|
+
posts: [],
|
|
34
|
+
page: 1,
|
|
35
|
+
pageCount: 1,
|
|
36
|
+
terms: [],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
declare global {
|
|
40
|
+
/** The current queried object. */
|
|
41
|
+
var queriedObject: IQueriedObject
|
|
42
|
+
/**
|
|
43
|
+
* Retrieves the first post from the queried object.
|
|
44
|
+
* @returns {Promise<IPost | undefined>} The post.
|
|
45
|
+
*/
|
|
46
|
+
var getThePost: () => Promise<IPost | undefined>
|
|
47
|
+
/**
|
|
48
|
+
* Retrieves all posts from the queried object.
|
|
49
|
+
* @returns {Promise<IPost[]>} Array of posts.
|
|
50
|
+
*/
|
|
51
|
+
var getThePosts: () => Promise<IPost[]>
|
|
52
|
+
/**
|
|
53
|
+
* Retrieves the current page number.
|
|
54
|
+
* @returns {number} Page number.
|
|
55
|
+
*/
|
|
56
|
+
var getThePage: () => number
|
|
57
|
+
/**
|
|
58
|
+
* Retrieves the total page count.
|
|
59
|
+
* @returns {number} Page count.
|
|
60
|
+
*/
|
|
61
|
+
var getThePageCount: () => number
|
|
62
|
+
/**
|
|
63
|
+
* Retrieves the main term from the queried object.
|
|
64
|
+
* @returns {Promise<ITerm | undefined>} The term.
|
|
65
|
+
*/
|
|
66
|
+
var getTheTerm: () => Promise<ITerm | undefined>
|
|
67
|
+
/**
|
|
68
|
+
* Retrieves all terms from the queried object.
|
|
69
|
+
* @returns {Promise<ITerm[]>} Array of terms.
|
|
70
|
+
*/
|
|
71
|
+
var getTheTerms: () => Promise<ITerm[]>
|
|
72
|
+
/**
|
|
73
|
+
* Retrieves the user from the queried object.
|
|
74
|
+
* @returns {Promise<IUser | undefined>} The user.
|
|
75
|
+
*/
|
|
76
|
+
var getTheUser: () => Promise<IUser | undefined>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Object.defineProperty(globalThis, 'queriedObject', {
|
|
80
|
+
configurable: true,
|
|
81
|
+
enumerable: true,
|
|
82
|
+
get() {
|
|
83
|
+
const state = queriedObjectState();
|
|
84
|
+
return state.currentState || createBlankState();
|
|
85
|
+
},
|
|
86
|
+
set(newData: IQueriedObject) {
|
|
87
|
+
const store = queriedObjectState();
|
|
88
|
+
|
|
89
|
+
if (newData.posts) postLoader.prime(newData.posts);
|
|
90
|
+
if (newData.mainTerm) termLoader.prime([newData.mainTerm]);
|
|
91
|
+
if (newData.terms) termLoader.prime(newData.terms);
|
|
92
|
+
if (newData.user) userLoader.prime([newData.user]);
|
|
93
|
+
|
|
94
|
+
if (!store.currentState) store.currentState = {};
|
|
95
|
+
Object.assign(store.currentState, newData);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
globalThis.getThePost = async () => {
|
|
100
|
+
return (await postLoader.get(globalThis.queriedObject.posts))[0];
|
|
101
|
+
};
|
|
102
|
+
globalThis.getThePosts = () => {
|
|
103
|
+
return postLoader.get(globalThis.queriedObject.posts);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
globalThis.getThePage = () => globalThis.queriedObject.page;
|
|
107
|
+
|
|
108
|
+
globalThis.getThePageCount = () => globalThis.queriedObject.pageCount;
|
|
109
|
+
|
|
110
|
+
globalThis.getTheTerm = async () => {
|
|
111
|
+
if (!globalThis.queriedObject.mainTerm) return;
|
|
112
|
+
return (await termLoader.get([globalThis.queriedObject.mainTerm]))[0];
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
globalThis.getTheTerms = () => {
|
|
116
|
+
return termLoader.get(globalThis.queriedObject.terms);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
globalThis.getTheUser = async () => {
|
|
120
|
+
if (!globalThis.queriedObject.user) return;
|
|
121
|
+
return (await userLoader.get([globalThis.queriedObject.user]))[0];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DB, WpOption } from "../../wpdb/wpdb.interface";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Defines the arguments for querying options.
|
|
5
|
+
*/
|
|
6
|
+
interface OptionQueryArgs {
|
|
7
|
+
/**
|
|
8
|
+
* Database column reference to query against.
|
|
9
|
+
*/
|
|
10
|
+
column: ReferenceExpression<DB, WpOption>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Comparison operator for the query.
|
|
14
|
+
*/
|
|
15
|
+
operand?: ComparisonOperatorExpression;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Value or array of values to compare.
|
|
19
|
+
*/
|
|
20
|
+
value: string | string[];
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ComparisonOperatorExpression } from "kysely";
|
|
2
|
+
import { EntityQuery } from "../../globals/entity-loader/entity-loader";
|
|
3
|
+
import { OptionQueryArgs } from "./option-query-args";
|
|
4
|
+
import { IOption } from "../../entities/option/option.interface";
|
|
5
|
+
import { wpdb } from "@/wpdb/wpdb";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Executes database queries to retrieve option IDs (or keys) based on provided arguments.
|
|
9
|
+
*/
|
|
10
|
+
export class OptionQuery implements EntityQuery<IOption>
|
|
11
|
+
{
|
|
12
|
+
constructor(
|
|
13
|
+
private args: OptionQueryArgs
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
public getCount(): number | undefined {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async getIds(): Promise<number[]> {
|
|
21
|
+
const operand: ComparisonOperatorExpression = this.args.operand || '=';
|
|
22
|
+
|
|
23
|
+
return (await wpdb
|
|
24
|
+
.selectFrom('wpOptions')
|
|
25
|
+
.select('optionId')
|
|
26
|
+
.where(this.args.column, operand, this.args.value)
|
|
27
|
+
.execute()).map(option => option.optionId);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
type WPQueryOrderByParam =
|
|
2
|
+
| 'none' | 'id' | 'author' | 'title' | 'name' | 'date' | 'modified'
|
|
3
|
+
| 'parent' | 'menuOrder'| 'commentCount' | 'rand' | `RAND(${number})`
|
|
4
|
+
| (string & {});
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Defines the arguments for querying posts.
|
|
8
|
+
* Maps standard WordPress WP_Query parameters.
|
|
9
|
+
*/
|
|
10
|
+
interface PostQueryArgs {
|
|
11
|
+
/** Post ID. */
|
|
12
|
+
postId?: number;
|
|
13
|
+
/** An array of post IDs to retrieve, sticky posts will be included. */
|
|
14
|
+
postIn?: number[];
|
|
15
|
+
/** An array of post IDs not to retrieve. */
|
|
16
|
+
postNotIn?: number[];
|
|
17
|
+
/** Path to post */
|
|
18
|
+
path?: string;
|
|
19
|
+
/** A post type slug (string) or array of post type slugs. */
|
|
20
|
+
postType?: string | string[];
|
|
21
|
+
/** A post type slug (string) or array of post type slugs to NOT include. */
|
|
22
|
+
postTypeNot?: string | string[];
|
|
23
|
+
/** A post status (string) or array of post statuses. */
|
|
24
|
+
postStatus?: string | string[];
|
|
25
|
+
/** A post slug (string) or array of post slugs. */
|
|
26
|
+
postSlug?: string | string[];
|
|
27
|
+
/** A post id to retrieve anscestry tree off */
|
|
28
|
+
postAncestryOf?: string;
|
|
29
|
+
/** Post title. */
|
|
30
|
+
title?: string;
|
|
31
|
+
/** The mime type of the post. Used for 'attachment' post_type. */
|
|
32
|
+
postMimeType?: string;
|
|
33
|
+
/** Page ID to retrieve child pages for. Use 0 to only retrieve top-level pages. */
|
|
34
|
+
postParent?: number;
|
|
35
|
+
/** An array containing parent page IDs to query child pages from. */
|
|
36
|
+
postParentIn?: number[];
|
|
37
|
+
/** An array containing parent page IDs not to query child pages from. */
|
|
38
|
+
postParentNotIn?: number[];
|
|
39
|
+
/** Term ID. */
|
|
40
|
+
termId?: number;
|
|
41
|
+
/** An array of term IDs (AND in). */
|
|
42
|
+
termAnd?: number[];
|
|
43
|
+
/** An array of term IDs (OR in). */
|
|
44
|
+
termIn?: number[];
|
|
45
|
+
/** An array of term IDs (NOT in). */
|
|
46
|
+
termNotIn?: number[];
|
|
47
|
+
/** Use term slug (not name, this or any children). */
|
|
48
|
+
termSlug?: string;
|
|
49
|
+
/** An array of term slugs (AND in). */
|
|
50
|
+
termSlugAnd?: string[];
|
|
51
|
+
/** An array of term slugs (OR in). */
|
|
52
|
+
termSlugIn?: string[];
|
|
53
|
+
/** Author ID. */
|
|
54
|
+
authorId?: number;
|
|
55
|
+
/** User 'user_nicename'. */
|
|
56
|
+
authorName?: string;
|
|
57
|
+
/** An array of author IDs to query from. */
|
|
58
|
+
authorIn?: number[];
|
|
59
|
+
/** An array of author IDs not to query from. */
|
|
60
|
+
authorNotIn?: number[];
|
|
61
|
+
/** search keyword(s). */
|
|
62
|
+
search?: string;
|
|
63
|
+
/** Whether to search by exact keyword. Default false. */
|
|
64
|
+
exact?: boolean;
|
|
65
|
+
/** array of column names to be searched. accepts 'postTitle', 'postExcerpt' and 'postContent'. */
|
|
66
|
+
searchColumns?: ('postTitle' | 'postExcerpt' | 'postContent')[];
|
|
67
|
+
/** Combination YearMonth. Accepts any four-digit year and month numbers 01-12. */
|
|
68
|
+
yyyymm?: number;
|
|
69
|
+
/** The four-digit year. Default empty. Accepts any four-digit year. */
|
|
70
|
+
year?: number;
|
|
71
|
+
/** The two-digit month. Default empty. Accepts numbers 1-12. */
|
|
72
|
+
monthnum?: number;
|
|
73
|
+
/** The week number of the year. Default empty. Accepts numbers 0-53. */
|
|
74
|
+
w?: number;
|
|
75
|
+
/** Day of the month. Default empty. Accepts numbers 1-31. */
|
|
76
|
+
day?: number;
|
|
77
|
+
/** Hour of the day. Default empty. Accepts numbers 0-23. */
|
|
78
|
+
hour?: number;
|
|
79
|
+
/** Minute of the hour. Default empty. Accepts numbers 0-59. */
|
|
80
|
+
minute?: number;
|
|
81
|
+
/** Second of the minute. Default empty. Accepts numbers 0-59. */
|
|
82
|
+
second?: number;
|
|
83
|
+
/** Whether to ignore sticky posts or not. Default false. */
|
|
84
|
+
ignoreStickyPosts?: boolean;
|
|
85
|
+
/** Designates ascending or descending order of posts. Default 'DESC'. */
|
|
86
|
+
order?: 'ASC' | 'DESC';
|
|
87
|
+
/** Sort retrieved posts by parameter. */
|
|
88
|
+
orderBy?: WPQueryOrderByParam;
|
|
89
|
+
/** Show posts that would show up on page X of a static front page. */
|
|
90
|
+
page?: number;
|
|
91
|
+
/** Show all posts (true) or paginate (false). Default false. */
|
|
92
|
+
noPaging?: boolean;
|
|
93
|
+
/** The number of posts to offset before retrieval. */
|
|
94
|
+
offset?: number;
|
|
95
|
+
/** The number of posts to query for. Use -1 to request all posts. */
|
|
96
|
+
postsPerPage?: number;
|
|
97
|
+
/** Whether to skip counting the total rows found. Default false. */
|
|
98
|
+
noFoundRows?: boolean;
|
|
99
|
+
/** Meta query */
|
|
100
|
+
metaQuery?: {
|
|
101
|
+
/** Meta key */
|
|
102
|
+
metaKey: string,
|
|
103
|
+
/** Variable name to save result to */
|
|
104
|
+
as: string
|
|
105
|
+
}[]
|
|
106
|
+
/** Whether to get multiple, default true */
|
|
107
|
+
multiple?: bool
|
|
108
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { QueryCreator, sql } from "kysely";
|
|
2
|
+
import { DB } from "../../wpdb/wpdb.interface";
|
|
3
|
+
import * as phpSerialize from "php-serialize";
|
|
4
|
+
import { EntityQuery } from "../../globals/entity-loader/entity-loader";
|
|
5
|
+
import { IPost } from "../../entities/post/post.interface";
|
|
6
|
+
import { wpdb } from "@/wpdb/wpdb";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Executes database queries to retrieve post IDs and counts based on provided arguments.
|
|
10
|
+
*/
|
|
11
|
+
export class PostQuery implements EntityQuery<IPost>
|
|
12
|
+
{
|
|
13
|
+
private postCount?: number;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private args: PostQueryArgs
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
public getCount(): number | undefined {
|
|
20
|
+
return this.postCount;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public async getIds(): Promise<number[]> {
|
|
24
|
+
let builder = wpdb as QueryCreator<any>;
|
|
25
|
+
|
|
26
|
+
if (this.args.postAncestryOf) {
|
|
27
|
+
builder = builder.withRecursive('decendent_tree', (qb) =>
|
|
28
|
+
qb.selectFrom('wpPosts')
|
|
29
|
+
.select(['wpPosts.ID', 'wpPosts.postName', 'wpPosts.postParent'])
|
|
30
|
+
.where('wpPosts.postName', '=', this.args.postAncestryOf)
|
|
31
|
+
.unionAll(
|
|
32
|
+
qb.selectFrom('wpPosts as p')
|
|
33
|
+
.select(['p.ID', 'p.postName', 'p.postParent'])
|
|
34
|
+
.innerJoin('decendent_tree as d', 'p.postParent', 'd.ID')
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let query = (builder as QueryCreator<DB>).selectFrom('wpPosts');
|
|
40
|
+
|
|
41
|
+
if (this.args.postAncestryOf) {
|
|
42
|
+
query = query.where('wpPosts.postName', 'in', (qb: any) =>
|
|
43
|
+
qb.selectFrom('decendent_tree').select('postName')
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- ID Filters ---
|
|
48
|
+
if (this.args.postId) query = query.where('wpPosts.ID', '=', this.args.postId);
|
|
49
|
+
if (this.args.postIn) query = query.where('wpPosts.ID', 'in', this.args.postIn);
|
|
50
|
+
if (this.args.postNotIn) query = query.where('wpPosts.ID', 'not in', this.args.postNotIn);
|
|
51
|
+
|
|
52
|
+
if (this.args.path) {
|
|
53
|
+
query = query.where((eb) => eb.exists(
|
|
54
|
+
eb.selectFrom('wpPostmeta')
|
|
55
|
+
.select('postId')
|
|
56
|
+
.whereRef('postId', '=', 'wpPosts.ID')
|
|
57
|
+
.where('wpPostmeta.metaKey', '=', '_nextpress_path')
|
|
58
|
+
.where('wpPostmeta.metaValue', '=', this.args.path as string)
|
|
59
|
+
));
|
|
60
|
+
}
|
|
61
|
+
// --- Type Filters ---
|
|
62
|
+
if (this.args.postType) {
|
|
63
|
+
const types = Array.isArray(this.args.postType) ? this.args.postType : [this.args.postType];
|
|
64
|
+
query = query.where('wpPosts.postType', 'in', types);
|
|
65
|
+
}
|
|
66
|
+
if (this.args.postTypeNot) {
|
|
67
|
+
const types = Array.isArray(this.args.postTypeNot) ? this.args.postTypeNot : [this.args.postTypeNot];
|
|
68
|
+
query = query.where('wpPosts.postType', 'not in', types);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --- Status Filters ---
|
|
72
|
+
if (this.args.postStatus) {
|
|
73
|
+
const statuses = Array.isArray(this.args.postStatus) ? this.args.postStatus : [this.args.postStatus];
|
|
74
|
+
query = query.where('wpPosts.postStatus', 'in', statuses);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --- Content Filters ---
|
|
78
|
+
if (this.args.postSlug) {
|
|
79
|
+
const postSlugs = Array.isArray(this.args.postSlug) ? this.args.postSlug : [this.args.postSlug];
|
|
80
|
+
query = query.where('wpPosts.postName', 'in', postSlugs);
|
|
81
|
+
}
|
|
82
|
+
if (this.args.title) query = query.where('wpPosts.postTitle', '=', this.args.title);
|
|
83
|
+
if (this.args.postMimeType) query = query.where('wpPosts.postMimeType', 'like', `%${this.args.postMimeType}%`);
|
|
84
|
+
|
|
85
|
+
// --- Parent Filters ---
|
|
86
|
+
if (this.args.postParent !== undefined) query = query.where('wpPosts.postParent', '=', this.args.postParent);
|
|
87
|
+
if (this.args.postParentIn) query = query.where('wpPosts.postParent', 'in', this.args.postParentIn);
|
|
88
|
+
if (this.args.postParentNotIn) query = query.where('wpPosts.postParent', 'not in', this.args.postParentNotIn);
|
|
89
|
+
|
|
90
|
+
// --- Term Filters ---
|
|
91
|
+
if (this.args.termId) {
|
|
92
|
+
query = query.innerJoin('wpTermRelationships', 'wpPosts.ID', 'wpTermRelationships.objectId')
|
|
93
|
+
.where('wpTermRelationships.termTaxonomyId', '=', this.args.termId);
|
|
94
|
+
}
|
|
95
|
+
if (this.args.termAnd) {
|
|
96
|
+
query = query.innerJoin('wpTermRelationships', 'wpPosts.ID', 'wpTermRelationships.objectId')
|
|
97
|
+
.where('wpTermRelationships.termTaxonomyId', 'in', this.args.termAnd)
|
|
98
|
+
.groupBy('wpPosts.ID')
|
|
99
|
+
.having((eb) => eb.fn.count('wpTermRelationships.termTaxonomyId'), '=', this.args.termAnd!.length);
|
|
100
|
+
}
|
|
101
|
+
if (this.args.termIn) {
|
|
102
|
+
query = query.innerJoin('wpTermRelationships', 'wpPosts.ID', 'wpTermRelationships.objectId')
|
|
103
|
+
.where('wpTermRelationships.termTaxonomyId', 'in', this.args.termIn);
|
|
104
|
+
}
|
|
105
|
+
if (this.args.termNotIn) {
|
|
106
|
+
query = query.where('wpPosts.ID', 'not in',
|
|
107
|
+
(qb) =>
|
|
108
|
+
qb.selectFrom('wpTermRelationships')
|
|
109
|
+
.select('wpTermRelationships.objectId')
|
|
110
|
+
.where('wpTermRelationships.termTaxonomyId', 'in', this.args.termNotIn!)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (this.args.termSlug) {
|
|
114
|
+
query = query.innerJoin('wpTermRelationships', 'wpPosts.ID', 'wpTermRelationships.objectId')
|
|
115
|
+
.innerJoin('wpTerms', 'wpTermRelationships.termTaxonomyId', 'wpTerms.termId')
|
|
116
|
+
.where('wpTerms.slug', '=', this.args.termSlug)
|
|
117
|
+
.groupBy('wpPosts.ID');
|
|
118
|
+
}
|
|
119
|
+
if (this.args.termSlugAnd) {
|
|
120
|
+
query = query.innerJoin('wpTermRelationships', 'wpPosts.ID', 'wpTermRelationships.objectId')
|
|
121
|
+
.innerJoin('wpTerms', 'wpTermRelationships.termTaxonomyId', 'wpTerms.termId')
|
|
122
|
+
.where('wpTerms.slug', 'in', this.args.termSlugAnd)
|
|
123
|
+
.groupBy('wpPosts.ID')
|
|
124
|
+
.having((eb) => eb.fn.count('wpTermRelationships.termTaxonomyId'), '=', this.args.termSlugAnd!.length);
|
|
125
|
+
}
|
|
126
|
+
if (this.args.termSlugIn) {
|
|
127
|
+
query = query.innerJoin('wpTermRelationships', 'wpPosts.ID', 'wpTermRelationships.objectId')
|
|
128
|
+
.innerJoin('wpTerms', 'wpTermRelationships.termTaxonomyId', 'wpTerms.termId')
|
|
129
|
+
.where('wpTerms.slug', 'in', this.args.termSlugIn)
|
|
130
|
+
.groupBy('wpPosts.ID');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --- Author Filters ---
|
|
134
|
+
if (this.args.authorId) query = query.where('wpPosts.postAuthor', '=', this.args.authorId);
|
|
135
|
+
if (this.args.authorIn) query = query.where('wpPosts.postAuthor', 'in', this.args.authorIn);
|
|
136
|
+
if (this.args.authorNotIn) query = query.where('wpPosts.postAuthor', 'not in', this.args.authorNotIn);
|
|
137
|
+
if (this.args.authorName) {
|
|
138
|
+
query = query.innerJoin('wpUsers', 'wpPosts.postAuthor', 'wpUsers.ID')
|
|
139
|
+
.where('wpUsers.displayName', '=', this.args.authorName);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- Search Filters ---
|
|
143
|
+
if (this.args.search) {
|
|
144
|
+
const searchTerm = this.args.exact ? this.args.search : `%${this.args.search}%`;
|
|
145
|
+
const searchCols = this.args.searchColumns || ['postTitle', 'postContent', 'postExcerpt'];
|
|
146
|
+
const isExact = this.args.exact;
|
|
147
|
+
|
|
148
|
+
query = query.where((eb) => {
|
|
149
|
+
const orClauses = searchCols.map(col => eb(col, isExact ? '=' : 'like', searchTerm));
|
|
150
|
+
return eb.or(orClauses);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// --- Date Filters ---
|
|
155
|
+
if (this.args.yyyymm) {
|
|
156
|
+
const mStr = String(this.args.yyyymm);
|
|
157
|
+
const y = Number(mStr.substring(0, 4));
|
|
158
|
+
const mon = Number(mStr.substring(4, 6));
|
|
159
|
+
query = query.where((eb) => eb.fn('YEAR', [eb.ref('wpPosts.postDate')]), '=', y)
|
|
160
|
+
.where((eb) => eb.fn('MONTH', [eb.ref('wpPosts.postDate')]), '=', mon);
|
|
161
|
+
}
|
|
162
|
+
if (this.args.year) query = query.where((eb) => eb.fn('YEAR', [eb.ref('wpPosts.postDate')]), '=', this.args.year);
|
|
163
|
+
if (this.args.monthnum) query = query.where((eb) => eb.fn('MONTH', [eb.ref('wpPosts.postDate')]), '=', this.args.monthnum);
|
|
164
|
+
if (this.args.w) query = query.where((eb) => eb.fn('WEEK', [eb.ref('wpPosts.postDate')]), '=', this.args.w);
|
|
165
|
+
if (this.args.day) query = query.where((eb) => eb.fn('DAY', [eb.ref('wpPosts.postDate')]), '=', this.args.day);
|
|
166
|
+
if (this.args.hour !== undefined) query = query.where((eb) => eb.fn('HOUR', [eb.ref('wpPosts.postDate')]), '=', this.args.hour);
|
|
167
|
+
if (this.args.minute !== undefined) query = query.where((eb) => eb.fn('MINUTE', [eb.ref('wpPosts.postDate')]), '=', this.args.minute);
|
|
168
|
+
if (this.args.second !== undefined) query = query.where((eb) => eb.fn('SECOND', [eb.ref('wpPosts.postDate')]), '=', this.args.second);
|
|
169
|
+
|
|
170
|
+
// -- Calculate Total Count (Before Limits/Offsets) --
|
|
171
|
+
if (!this.args.noFoundRows) {
|
|
172
|
+
try {
|
|
173
|
+
const countQueryBase = query.clearSelect().select('wpPosts.ID').distinct();
|
|
174
|
+
const countResult = await wpdb.selectFrom(countQueryBase.as('sub'))
|
|
175
|
+
.select(sql<number>`count(*)`.as('count'))
|
|
176
|
+
.executeTakeFirst();
|
|
177
|
+
|
|
178
|
+
this.postCount = countResult ? Number(countResult.count) : undefined;
|
|
179
|
+
} catch (error: any) {
|
|
180
|
+
console.error('WPPostQuery: Cannot get row count: ', error.message);
|
|
181
|
+
this.postCount = undefined;
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
this.postCount = undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// -- Apply ORDER BY --
|
|
188
|
+
if (!this.args.ignoreStickyPosts) {
|
|
189
|
+
const rawStickyPostIds = await getOption('sticky_posts');
|
|
190
|
+
|
|
191
|
+
let stickyPosts: number[] = [];
|
|
192
|
+
try {
|
|
193
|
+
const parsed = phpSerialize.unserialize(rawStickyPostIds ?? 'a:0:{}');
|
|
194
|
+
stickyPosts = Object.values(parsed as Record<string, number> | number[]);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.warn('WPPostQuery: Failed to parse sticky_posts option. Defaulting to empty.', error);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (stickyPosts.length > 0) {
|
|
200
|
+
query = query.orderBy(
|
|
201
|
+
sql`CASE WHEN wp_posts.ID IN (${sql.join(stickyPosts)}) THEN 0 ELSE 1 END`,
|
|
202
|
+
'asc'
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const orderDirection = this.args.order === 'ASC' ? 'asc' : 'desc';
|
|
208
|
+
const orderBy = this.args.orderBy || 'none';
|
|
209
|
+
|
|
210
|
+
switch(orderBy) {
|
|
211
|
+
case 'none': break;
|
|
212
|
+
case 'id': query = query.orderBy('wpPosts.ID', orderDirection); break;
|
|
213
|
+
case 'author': query = query.orderBy('wpPosts.postAuthor', orderDirection).orderBy('wpPosts.ID', orderDirection); break;
|
|
214
|
+
case 'title': query = query.orderBy('wpPosts.postTitle', orderDirection).orderBy('wpPosts.ID', orderDirection); break;
|
|
215
|
+
case 'name': query = query.orderBy('wpPosts.postName', orderDirection).orderBy('wpPosts.ID', orderDirection); break;
|
|
216
|
+
case 'date': query = query.orderBy('wpPosts.postDate', orderDirection).orderBy('wpPosts.ID', orderDirection); break;
|
|
217
|
+
case 'modified': query = query.orderBy('wpPosts.postModified', orderDirection).orderBy('wpPosts.ID', orderDirection); break;
|
|
218
|
+
case 'parent': query = query.orderBy('wpPosts.postParent', orderDirection).orderBy('wpPosts.ID', orderDirection); break;
|
|
219
|
+
case 'menuOrder': query = query.orderBy('wpPosts.menuOrder', orderDirection).orderBy('wpPosts.ID', orderDirection); break;
|
|
220
|
+
case 'commentCount': query = query.orderBy('wpPosts.commentCount', orderDirection).orderBy('wpPosts.ID', orderDirection); break;
|
|
221
|
+
case 'rand': query = query.orderBy((eb) => eb.fn('RAND', [])).orderBy('wpPosts.ID', orderDirection); break;
|
|
222
|
+
default:
|
|
223
|
+
if (orderBy.startsWith('RAND(')) {
|
|
224
|
+
const seed = parseInt(orderBy.replace(/\D/g, ''), 10);
|
|
225
|
+
query = query.orderBy((eb) => eb.fn('RAND', [eb.val(seed)])).orderBy('wpPosts.ID', orderDirection);
|
|
226
|
+
} else {
|
|
227
|
+
query = query.orderBy('wpPosts.postDate', orderDirection).orderBy('wpPosts.ID', orderDirection);
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// -- Apply LIMIT & OFFSET --
|
|
233
|
+
if (!this.args.noPaging) {
|
|
234
|
+
const perPage = this.args.postsPerPage ?? 10;
|
|
235
|
+
|
|
236
|
+
if (perPage > -1) {
|
|
237
|
+
query = query.limit(perPage);
|
|
238
|
+
|
|
239
|
+
const page = this.args.page ?? 1;
|
|
240
|
+
const baseOffset = this.args.offset ?? 0;
|
|
241
|
+
const offsetAmount = ((page - 1) * perPage) + baseOffset;
|
|
242
|
+
|
|
243
|
+
if (offsetAmount > 0) {
|
|
244
|
+
query = query.offset(offsetAmount);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (this.args.metaQuery) {
|
|
250
|
+
let dynamicQuery: any = query;
|
|
251
|
+
|
|
252
|
+
for (let metaQuery of this.args.metaQuery) {
|
|
253
|
+
const uniqueAlias = `meta_${metaQuery.as}`;
|
|
254
|
+
|
|
255
|
+
dynamicQuery = dynamicQuery.leftJoin(`wpPostmeta as ${uniqueAlias}`, (join: any) =>
|
|
256
|
+
join
|
|
257
|
+
.onRef(`${uniqueAlias}.postId`, '=', 'wpPosts.ID')
|
|
258
|
+
.on(`${uniqueAlias}.metaKey`, '=', metaQuery.metaKey)
|
|
259
|
+
)
|
|
260
|
+
.select(`${uniqueAlias}.metaValue as ${metaQuery.as}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
query = dynamicQuery;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
if(this.args.multiple !== false) {
|
|
268
|
+
return (await query
|
|
269
|
+
.select('wpPosts.ID as id')
|
|
270
|
+
.groupBy('wpPosts.ID')
|
|
271
|
+
.execute()).map(res => res.id);
|
|
272
|
+
} else {
|
|
273
|
+
return (await query
|
|
274
|
+
.select('wpPosts.ID as id')
|
|
275
|
+
.execute()).map(res => res.id);
|
|
276
|
+
}
|
|
277
|
+
} catch (error: any) {
|
|
278
|
+
throw new Error(`WPPostQuery: Cannot get posts: ${error.message}`, { cause: error });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|