From a678e8570b72514b0b5da6fcac4da5534d05fee8 Mon Sep 17 00:00:00 2001 From: Ludwig Gerdes Date: Wed, 13 Mar 2024 06:01:41 -0500 Subject: [PATCH] feat(Wordpress Node): Support WordPress pages (#8852) Signed-off-by: Ludwig Gerdes --- .../nodes/Wordpress/PageDescription.ts | 834 ++++++++++++++++++ .../nodes/Wordpress/PageInterface.ts | 16 + .../nodes/Wordpress/PostDescription.ts | 5 - .../nodes/Wordpress/UserDescription.ts | 5 - .../nodes/Wordpress/Wordpress.node.ts | 174 ++++ 5 files changed, 1024 insertions(+), 10 deletions(-) create mode 100644 packages/nodes-base/nodes/Wordpress/PageDescription.ts create mode 100644 packages/nodes-base/nodes/Wordpress/PageInterface.ts diff --git a/packages/nodes-base/nodes/Wordpress/PageDescription.ts b/packages/nodes-base/nodes/Wordpress/PageDescription.ts new file mode 100644 index 0000000000..32f3afec31 --- /dev/null +++ b/packages/nodes-base/nodes/Wordpress/PageDescription.ts @@ -0,0 +1,834 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const pageOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['page'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a page', + action: 'Create a page', + }, + { + name: 'Get', + value: 'get', + description: 'Get a page', + action: 'Get a page', + }, + { + name: 'Get Many', + value: 'getAll', + description: 'Get many pages', + action: 'Get many pages', + }, + { + name: 'Update', + value: 'update', + description: 'Update a page', + action: 'Update a page', + }, + ], + default: 'create', + }, +]; + +export const pageFields: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* page:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Title', + name: 'title', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['page'], + operation: ['create'], + }, + }, + description: 'The title for the page', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: ['page'], + operation: ['create'], + }, + }, + options: [ + { + displayName: 'Author Name or ID', + name: 'authorId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getAuthors', + }, + default: '', + description: + 'The ID for the author of the object. Choose from the list, or specify an ID using an expression.', + }, + { + displayName: 'Parent ID', + name: 'parent', + type: 'number', + default: '', + description: 'The ID for the parent of the post', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + description: 'The content for the page', + }, + { + displayName: 'Slug', + name: 'slug', + type: 'string', + default: '', + description: 'An alphanumeric identifier for the object unique to its type', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + typeOptions: { password: true }, + default: '', + description: 'A password to protect access to the content and excerpt', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Draft', + value: 'draft', + }, + { + name: 'Future', + value: 'future', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Private', + value: 'private', + }, + { + name: 'Publish', + value: 'publish', + }, + ], + default: 'draft', + description: 'A named status for the page', + }, + { + displayName: 'Comment Status', + name: 'commentStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'Close', + value: 'closed', + }, + ], + default: 'open', + description: 'Whether or not comments are open on the page', + }, + { + displayName: 'Ping Status', + name: 'pingStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'Close', + value: 'closed', + }, + ], + default: 'open', + description: 'If the a message should be send to announce the page', + }, + { + displayName: 'Template', + name: 'pageTemplate', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: false, + }, + options: [ + { + displayName: 'Values', + name: 'values', + values: [ + { + displayName: 'Elementor Template', + name: 'elementor', + type: 'boolean', + default: true, + description: 'Whether site uses elementor page builder', + }, + { + displayName: 'Template', + name: 'template', + type: 'string', + default: '', + description: 'The theme file to use', + displayOptions: { + show: { + elementor: [false], + }, + }, + }, + { + displayName: 'Template', + name: 'template', + type: 'options', + options: [ + { + name: 'Standard', + value: '', + }, + { + name: 'Elementor Canvas', + value: 'elementor_canvas', + }, + { + name: 'Elementor Header Footer', + value: 'elementor_header_footer', + }, + { + name: 'Elementor Theme', + value: 'elementor_theme', + }, + ], + default: '', + description: 'The Elementor template to use', + displayOptions: { + show: { + elementor: [true], + }, + }, + }, + ], + }, + ], + }, + { + displayName: 'Menu Order', + name: 'menuOrder', + type: 'number', + default: 0, + description: 'The order of the page in relation to other pages', + }, + { + displayName: 'Comment Status', + name: 'commentStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'Closed', + value: 'closed', + }, + ], + default: 'open', + description: 'Whether or not comments are open on the page', + }, + { + displayName: 'Featured Media ID', + name: 'featuredMediaId', + type: 'number', + default: '', + description: 'The ID of the featured media for the page', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* page:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Page ID', + name: 'pageId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['page'], + operation: ['update'], + }, + }, + description: 'Unique identifier for the object', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: ['page'], + operation: ['update'], + }, + }, + options: [ + { + displayName: 'Author Name or ID', + name: 'authorId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getAuthors', + }, + default: '', + description: + 'The ID for the author of the object. Choose from the list, or specify an ID using an expression.', + }, + { + displayName: 'Parent ID', + name: 'parent', + type: 'number', + default: '', + description: 'The ID for the parent of the post', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'The title for the page', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + description: 'The content for the page', + }, + { + displayName: 'Slug', + name: 'slug', + type: 'string', + default: '', + description: 'An alphanumeric identifier for the object unique to its type', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + typeOptions: { password: true }, + default: '', + description: 'A password to protect access to the content and excerpt', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Draft', + value: 'draft', + }, + { + name: 'Future', + value: 'future', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Private', + value: 'private', + }, + { + name: 'Publish', + value: 'publish', + }, + ], + default: 'draft', + description: 'A named status for the page', + }, + { + displayName: 'Comment Status', + name: 'commentStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'Close', + value: 'closed', + }, + ], + default: 'open', + description: 'Whether or not comments are open on the page', + }, + { + displayName: 'Ping Status', + name: 'pingStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'Close', + value: 'closed', + }, + ], + default: 'open', + description: 'Whether or not comments are open on the page', + }, + { + displayName: 'Template', + name: 'pageTemplate', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: false, + }, + options: [ + { + displayName: 'Values', + name: 'values', + values: [ + { + displayName: 'Elementor Template', + name: 'elementor', + type: 'boolean', + default: true, + description: 'Whether site uses elementor page builder', + }, + { + displayName: 'Template', + name: 'template', + type: 'string', + default: '', + description: 'The theme file to use', + displayOptions: { + show: { + elementor: [false], + }, + }, + }, + { + displayName: 'Template', + name: 'template', + type: 'options', + options: [ + { + name: 'Standard', + value: '', + }, + { + name: 'Elementor Canvas', + value: 'elementor_canvas', + }, + { + name: 'Elementor Header Footer', + value: 'elementor_header_footer', + }, + { + name: 'Elementor Theme', + value: 'elementor_theme', + }, + ], + default: '', + description: 'The Elementor template to use', + displayOptions: { + show: { + elementor: [true], + }, + }, + }, + ], + }, + ], + }, + { + displayName: 'Menu Order', + name: 'menuOrder', + type: 'number', + default: 0, + description: 'The order of the page in relation to other pages', + }, + { + displayName: 'Comment Status', + name: 'commentStatus', + type: 'options', + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'Closed', + value: 'closed', + }, + ], + default: 'open', + description: 'Whether or not comments are open on the page', + }, + { + displayName: 'Featured Media ID', + name: 'featuredMediaId', + type: 'number', + default: '', + description: 'The ID of the featured media for the page', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* page:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Page ID', + name: 'pageId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['page'], + operation: ['get'], + }, + }, + description: 'Unique identifier for the object', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: ['page'], + operation: ['get'], + }, + }, + options: [ + { + displayName: 'Password', + name: 'password', + type: 'string', + typeOptions: { password: true }, + default: '', + description: 'The password for the page if it is password protected', + }, + { + displayName: 'Context', + name: 'context', + type: 'options', + options: [ + { + name: 'View', + value: 'view', + }, + { + name: 'Embed', + value: 'embed', + }, + { + name: 'Edit', + value: 'edit', + }, + ], + default: 'view', + description: 'Scope under which the request is made; determines fields present in response', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* page:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: ['page'], + operation: ['getAll'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: ['page'], + operation: ['getAll'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'Max number of results to return', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: ['page'], + operation: ['getAll'], + }, + }, + options: [ + { + displayName: 'After', + name: 'after', + type: 'dateTime', + default: '', + description: 'Limit response to pages published after a given ISO8601 compliant date', + }, + { + displayName: 'Author Names or IDs', + name: 'author', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getAuthors', + }, + description: + 'Limit result set to pages assigned to specific authors. Choose from the list, or specify IDs using an expression.', + }, + { + displayName: 'Before', + name: 'before', + type: 'dateTime', + default: '', + description: 'Limit response to pages published before a given ISO8601 compliant date', + }, + { + displayName: 'Context', + name: 'context', + type: 'options', + options: [ + { + name: 'View', + value: 'view', + }, + { + name: 'Embed', + value: 'embed', + }, + { + name: 'Edit', + value: 'edit', + }, + ], + default: 'view', + description: 'Scope under which the request is made; determines fields present in response', + }, + { + displayName: 'Menu Order', + name: 'menuOrder', + type: 'number', + default: 0, + description: 'Limit result set to items with a specific menu order value', + }, + { + displayName: 'Order', + name: 'order', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + }, + ], + default: 'desc', + description: 'Order sort attribute ascending or descending', + }, + { + displayName: 'Order By', + name: 'orderBy', + type: 'options', + options: [ + { + name: 'Author', + value: 'author', + }, + { + name: 'Date', + value: 'date', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Include', + value: 'include', + }, + { + name: 'Include Slugs', + value: 'include_slugs', + }, + { + name: 'Modified', + value: 'modified', + }, + { + name: 'Parent', + value: 'parent', + }, + { + name: 'Relevance', + value: 'relevance', + }, + { + name: 'Slug', + value: 'slug', + }, + { + name: 'Title', + value: 'title', + }, + ], + default: 'id', + description: 'Sort collection by object attribute', + }, + { + displayName: 'Page', + name: 'page', + type: 'number', + default: 1, + description: 'Current page of the collection', + }, + { + displayName: 'Parent Page ID', + name: 'parent', + type: 'number', + default: '', + description: 'Limit result set to items with a particular parent page ID', + }, + { + displayName: 'Search', + name: 'search', + type: 'string', + default: '', + description: 'Limit results to those matching a string', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Draft', + value: 'draft', + }, + { + name: 'Future', + value: 'future', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Private', + value: 'private', + }, + { + name: 'Publish', + value: 'publish', + }, + ], + default: 'publish', + description: 'The status of the page', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* page:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Page ID', + name: 'pageId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['page'], + operation: ['delete'], + }, + }, + description: 'Unique identifier for the object', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: ['page'], + operation: ['delete'], + }, + }, + options: [ + { + displayName: 'Force', + name: 'force', + type: 'boolean', + default: false, + description: 'Whether to bypass trash and force deletion', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Wordpress/PageInterface.ts b/packages/nodes-base/nodes/Wordpress/PageInterface.ts new file mode 100644 index 0000000000..687d5c8e4d --- /dev/null +++ b/packages/nodes-base/nodes/Wordpress/PageInterface.ts @@ -0,0 +1,16 @@ +export interface IPage { + author?: number; + comment_status?: string; + content?: string; + featured_media?: number; + id?: number; + menu_order?: number; + page?: number; + parent?: number; + password?: string; + ping_status?: string; + slug?: string; + status?: string; + template?: string; + title?: string; +} diff --git a/packages/nodes-base/nodes/Wordpress/PostDescription.ts b/packages/nodes-base/nodes/Wordpress/PostDescription.ts index ee4659afad..e2b0cd957c 100644 --- a/packages/nodes-base/nodes/Wordpress/PostDescription.ts +++ b/packages/nodes-base/nodes/Wordpress/PostDescription.ts @@ -18,11 +18,6 @@ export const postOperations: INodeProperties[] = [ description: 'Create a post', action: 'Create a post', }, - // { - // name: 'Delete', - // value: 'delete', - // description: 'Delete a post', - // }, { name: 'Get', value: 'get', diff --git a/packages/nodes-base/nodes/Wordpress/UserDescription.ts b/packages/nodes-base/nodes/Wordpress/UserDescription.ts index fcc72b3989..aae020c6d8 100644 --- a/packages/nodes-base/nodes/Wordpress/UserDescription.ts +++ b/packages/nodes-base/nodes/Wordpress/UserDescription.ts @@ -18,11 +18,6 @@ export const userOperations: INodeProperties[] = [ description: 'Create a user', action: 'Create a user', }, - // { - // name: 'Delete', - // value: 'delete', - // description: 'Delete a user', - // }, { name: 'Get', value: 'get', diff --git a/packages/nodes-base/nodes/Wordpress/Wordpress.node.ts b/packages/nodes-base/nodes/Wordpress/Wordpress.node.ts index a8ff0e8e7b..1d22cac0f7 100644 --- a/packages/nodes-base/nodes/Wordpress/Wordpress.node.ts +++ b/packages/nodes-base/nodes/Wordpress/Wordpress.node.ts @@ -9,8 +9,11 @@ import type { } from 'n8n-workflow'; import { wordpressApiRequest, wordpressApiRequestAllItems } from './GenericFunctions'; import { postFields, postOperations } from './PostDescription'; +import { pageFields, pageOperations } from './PageDescription'; import { userFields, userOperations } from './UserDescription'; + import type { IPost } from './PostInterface'; +import type { IPage } from './PageInterface'; import type { IUser } from './UserInterface'; export class Wordpress implements INodeType { @@ -44,6 +47,10 @@ export class Wordpress implements INodeType { name: 'Post', value: 'post', }, + { + name: 'Page', + value: 'page', + }, { name: 'User', value: 'user', @@ -53,6 +60,8 @@ export class Wordpress implements INodeType { }, ...postOperations, ...postFields, + ...pageOperations, + ...pageFields, ...userOperations, ...userFields, ], @@ -305,6 +314,171 @@ export class Wordpress implements INodeType { ); } } + if (resource === 'page') { + //https://developer.wordpress.org/rest-api/reference/pages/#create-a-page + if (operation === 'create') { + const title = this.getNodeParameter('title', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i); + const body: IPage = { + title, + }; + if (additionalFields.authorId) { + body.author = additionalFields.authorId as number; + } + if (additionalFields.content) { + body.content = additionalFields.content as string; + } + if (additionalFields.slug) { + body.slug = additionalFields.slug as string; + } + if (additionalFields.password) { + body.password = additionalFields.password as string; + } + if (additionalFields.status) { + body.status = additionalFields.status as string; + } + if (additionalFields.commentStatus) { + body.comment_status = additionalFields.commentStatus as string; + } + if (additionalFields.pingStatus) { + body.ping_status = additionalFields.pingStatus as string; + } + if (additionalFields.pageTemplate) { + body.template = this.getNodeParameter( + 'additionalFields.pageTemplate.values.template', + i, + '', + ) as string; + } + if (additionalFields.menuOrder) { + body.menu_order = additionalFields.menuOrder as number; + } + if (additionalFields.parent) { + body.parent = additionalFields.parent as number; + } + if (additionalFields.featuredMediaId) { + body.featured_media = additionalFields.featuredMediaId as number; + } + responseData = await wordpressApiRequest.call(this, 'POST', '/pages', body); + } + //https://developer.wordpress.org/rest-api/reference/pages/#update-a-page + if (operation === 'update') { + const pageId = this.getNodeParameter('pageId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i); + const body: IPage = { + id: parseInt(pageId, 10), + }; + if (updateFields.authorId) { + body.author = updateFields.authorId as number; + } + if (updateFields.title) { + body.title = updateFields.title as string; + } + if (updateFields.content) { + body.content = updateFields.content as string; + } + if (updateFields.slug) { + body.slug = updateFields.slug as string; + } + if (updateFields.password) { + body.password = updateFields.password as string; + } + if (updateFields.status) { + body.status = updateFields.status as string; + } + if (updateFields.commentStatus) { + body.comment_status = updateFields.commentStatus as string; + } + if (updateFields.pingStatus) { + body.ping_status = updateFields.pingStatus as string; + } + if (updateFields.pageTemplate) { + body.template = this.getNodeParameter( + 'updateFields.pageTemplate.values.template', + i, + '', + ) as string; + } + if (updateFields.menuOrder) { + body.menu_order = updateFields.menuOrder as number; + } + if (updateFields.parent) { + body.parent = updateFields.parent as number; + } + if (updateFields.featuredMediaId) { + body.featured_media = updateFields.featuredMediaId as number; + } + responseData = await wordpressApiRequest.call(this, 'POST', `/pages/${pageId}`, body); + } + //https://developer.wordpress.org/rest-api/reference/pages/#retrieve-a-page + if (operation === 'get') { + const pageId = this.getNodeParameter('pageId', i) as string; + const options = this.getNodeParameter('options', i); + if (options.password) { + qs.password = options.password as string; + } + if (options.context) { + qs.context = options.context as string; + } + responseData = await wordpressApiRequest.call(this, 'GET', `/pages/${pageId}`, {}, qs); + } + //https://developer.wordpress.org/rest-api/reference/pages/#list-pages + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i); + const options = this.getNodeParameter('options', i); + if (options.context) { + qs.context = options.context as string; + } + if (options.orderBy) { + qs.orderby = options.orderBy as string; + } + if (options.order) { + qs.order = options.order as string; + } + if (options.search) { + qs.search = options.search as string; + } + if (options.after) { + qs.after = options.after as string; + } + if (options.author) { + qs.author = options.author as number[]; + } + if (options.parent) { + qs.parent = options.parent as number; + } + if (options.menuOrder) { + qs.menu_order = options.menuOrder as number; + } + if (options.status) { + qs.status = options.status as string; + } + if (options.page) { + qs.page = options.page as number; + } + if (returnAll) { + responseData = await wordpressApiRequestAllItems.call(this, 'GET', '/pages', {}, qs); + } else { + qs.per_page = this.getNodeParameter('limit', i); + responseData = await wordpressApiRequest.call(this, 'GET', '/pages', {}, qs); + } + } + //https://developer.wordpress.org/rest-api/reference/pages/#delete-a-page + if (operation === 'delete') { + const pageId = this.getNodeParameter('pageId', i) as string; + const options = this.getNodeParameter('options', i); + if (options.force) { + qs.force = options.force as boolean; + } + responseData = await wordpressApiRequest.call( + this, + 'DELETE', + `/pages/${pageId}`, + {}, + qs, + ); + } + } if (resource === 'user') { //https://developer.wordpress.org/rest-api/reference/users/#create-a-user if (operation === 'create') {