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
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,29 +167,32 @@ Notes:

## Endpoint Coverage and Gaps

WordPressPCL currently provides dedicated clients for the most common `wp/v2` endpoints:
WordPressPCL provides dedicated clients for the standard `wp/v2` endpoints:

- Posts, Pages, Comments, Categories, Tags, Users and Media
- Taxonomies, Post Types, Post Statuses and Settings
- Post revisions via `client.Posts.Revisions(postId)`
- Plugins and Themes
- Search (`client.Search`)
- Block types (`client.BlockTypes`), reusable blocks (`client.Blocks`), templates (`client.Templates`), template parts (`client.TemplateParts`) and global styles (`client.GlobalStyles`)
- Navigation (`client.Navigation`), sidebars (`client.Sidebars`), widgets (`client.Widgets`) and widget types (`client.WidgetTypes`)
- URL details (`client.UrlDetails`)
- Post revisions via `client.Posts.Revisions(postId)` and page revisions via `client.Pages.Revisions(pageId)`
- Post autosaves via `client.Posts.Autosaves(postId)` and page autosaves via `client.Pages.Autosaves(pageId)`

The standard WordPress REST API reference also includes endpoints that do not yet have first-class wrappers in this library, including:
The following endpoints do not yet have first-class wrappers but can be called via `CustomRequest`:

- `wp/v2/search`
- newer block editor endpoints such as block types, blocks, block rendering, templates, template parts and global styles
- navigation, sidebars, widgets and widget types
- `wp/v2/url-details`
- autosaves and page revisions
- `wp/v2/block-renderer` (server-side block rendering)
- `wp/v2/navigation-fallback`
- font families, font faces and pattern directory endpoints

You can still work with unsupported standard endpoints, plugin namespaces and site-specific custom endpoints by using `CustomRequest`.
You can also work with plugin namespaces and site-specific custom endpoints using `CustomRequest`.

```csharp
// Discover the namespaces and routes exposed by a site
dynamic apiIndex = await client.CustomRequest.GetAsync<dynamic>("", ignoreDefaultPath: true);

// Call a standard wp/v2 endpoint that does not have a dedicated client
dynamic searchResults = await client.CustomRequest.GetAsync<dynamic>("search?search=hello", ignoreDefaultPath: false);
dynamic rendered = await client.CustomRequest.PostAsync<dynamic>("block-renderer/core%2Fparagraph", body, ignoreDefaultPath: false);

// Call a plugin or custom namespace route
dynamic customEndpoint = await client.CustomRequest.GetAsync<dynamic>("wc/v3/products", useAuth: true);
Expand Down
40 changes: 29 additions & 11 deletions docs/v3/endpoint-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ WordPressPCL focuses on the most commonly used WordPress REST API endpoints and
| WordPress endpoint | WordPressPCL entry point | Notes |
|--------------------|--------------------------|-------|
| `wp/v2/posts` | `client.Posts` | CRUD, count, sticky/filter helpers |
| `wp/v2/posts/<id>/revisions` | `client.Posts.Revisions(postId)` | Post revisions only |
| `wp/v2/posts/<id>/revisions` | `client.Posts.Revisions(postId)` | Post revisions |
| `wp/v2/posts/<id>/autosaves` | `client.Posts.Autosaves(postId)` | Post autosaves |
| `wp/v2/pages` | `client.Pages` | CRUD |
| `wp/v2/pages/<id>/revisions` | `client.Pages.Revisions(pageId)` | Page revisions |
| `wp/v2/pages/<id>/autosaves` | `client.Pages.Autosaves(pageId)` | Page autosaves |
| `wp/v2/comments` | `client.Comments` | CRUD |
| `wp/v2/categories` | `client.Categories` | CRUD |
| `wp/v2/tags` | `client.Tags` | CRUD |
Expand All @@ -20,17 +23,27 @@ WordPressPCL focuses on the most commonly used WordPress REST API endpoints and
| `wp/v2/settings` | `client.Settings` | Read/update only |
| `wp/v2/plugins` | `client.Plugins` | Install, activate, deactivate, delete |
| `wp/v2/themes` | `client.Themes` | Read/query only |
| `wp/v2/search` | `client.Search` | Read/query only |
| `wp/v2/block-types` | `client.BlockTypes` | Read only |
| `wp/v2/blocks` | `client.Blocks` | CRUD (reusable blocks) |
| `wp/v2/templates` | `client.Templates` | CRUD (requires auth) |
| `wp/v2/template-parts` | `client.TemplateParts` | CRUD (requires auth) |
| `wp/v2/global-styles/<id>` | `client.GlobalStyles` | Read/update by ID or theme |
| `wp/v2/navigation` | `client.Navigation` | CRUD |
| `wp/v2/sidebars` | `client.Sidebars` | Read/update only |
| `wp/v2/widgets` | `client.Widgets` | CRUD |
| `wp/v2/widget-types` | `client.WidgetTypes` | Read only |
| `wp/v2/url-details` | `client.UrlDetails` | Read by URL (requires auth) |

## Standard endpoints without dedicated wrappers

The official WordPress REST API reference includes additional standard endpoints that currently do not have dedicated WordPressPCL clients. The main gaps are:
The following standard WordPress REST API endpoints do not yet have dedicated WordPressPCL clients:

- `wp/v2/search`
- block editor endpoints such as `wp/v2/block-types`, `wp/v2/blocks`, `wp/v2/block-renderer`, `wp/v2/templates`, `wp/v2/template-parts` and `wp/v2/global-styles`
- navigation and site editing endpoints such as `wp/v2/navigation` and `wp/v2/navigation-fallback`
- `wp/v2/sidebars`, `wp/v2/widgets` and `wp/v2/widget-types`
- `wp/v2/url-details`
- autosaves and page revisions
- `wp/v2/block-renderer` (POST to render a block server-side)
- `wp/v2/navigation-fallback` (GET the navigation fallback menu)
- `wp/v2/global-styles/<id>/revisions` (global styles revision history)
- font families and font faces (`wp/v2/font-families`, `wp/v2/font-faces`)
- pattern directory (`wp/v2/pattern-directory/patterns`)

As WordPress adds more standard endpoints, the authoritative way to see what a site exposes is its API index at `/wp-json/`.

Expand All @@ -47,8 +60,8 @@ These examples use `dynamic` for brevity. If you already know the response shape
Use `CustomRequest` for standard endpoints that do not yet have dedicated wrappers:

```csharp
dynamic searchResults = await client.CustomRequest.GetAsync<dynamic>("search?search=hello", ignoreDefaultPath: false);
dynamic pageRevisions = await client.CustomRequest.GetAsync<dynamic>("pages/42/revisions", useAuth: true, ignoreDefaultPath: false);
dynamic rendered = await client.CustomRequest.PostAsync<dynamic>("block-renderer/core%2Fparagraph", body, ignoreDefaultPath: false);
dynamic navFallback = await client.CustomRequest.GetAsync<dynamic>("navigation-fallback", useAuth: true, ignoreDefaultPath: false);
```

Use `CustomRequest` for plugin namespaces and custom site routes:
Expand All @@ -61,4 +74,9 @@ dynamic products = await client.CustomRequest.GetAsync<dynamic>("wc/v3/products"

- Themes are available through `client.Themes`, but they are read/query only.
- Post revisions are available through `client.Posts.Revisions(postId)`.
- Pages do not currently have a dedicated revisions or autosaves helper.
- Page revisions are available through `client.Pages.Revisions(pageId)`.
- Post autosaves are available through `client.Posts.Autosaves(postId)`.
- Page autosaves are available through `client.Pages.Autosaves(pageId)`.
- Templates and template parts use compound string IDs (e.g. `"twentytwentyfour//index"`).
- Sidebars, widgets and widget types use string IDs (e.g. `"sidebar-1"`, `"block-3"`).
- `client.GlobalStyles` exposes `GetByIdAsync(id)`, `GetThemeStylesAsync(stylesheet)` and `UpdateAsync(entity)`.
51 changes: 51 additions & 0 deletions src/WordPressPCL/Client/BlockTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using WordPressPCL.Models;
using WordPressPCL.Utility;

namespace WordPressPCL.Client;

/// <summary>
/// Client class for interaction with the Block Types endpoint WP REST API (<c>wp/v2/block-types</c>).
/// </summary>
public class BlockTypes
{
private readonly HttpHelper _httpHelper;
private const string _methodPath = "block-types";

/// <summary>
/// Constructor
/// </summary>
/// <param name="httpHelper">reference to HttpHelper class for interaction with HTTP</param>
public BlockTypes(HttpHelper httpHelper)
{
_httpHelper = httpHelper;
}

/// <summary>
/// Get all registered block types.
/// </summary>
/// <param name="embed">Include embed info</param>
/// <param name="useAuth">Send request with authentication header</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of block types</returns>
public Task<List<BlockType>> GetAsync(bool embed = false, bool useAuth = false, CancellationToken cancellationToken = default)
{
return _httpHelper.GetRequestAsync<List<BlockType>>(_methodPath, embed, useAuth, cancellationToken: cancellationToken);
}

/// <summary>
/// Get a specific block type by its namespaced name (e.g. "core/paragraph").
/// </summary>
/// <param name="namespace">Block namespace (e.g. "core")</param>
/// <param name="name">Block name (e.g. "paragraph")</param>
/// <param name="embed">Include embed info</param>
/// <param name="useAuth">Send request with authentication header</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The requested block type</returns>
public Task<BlockType> GetByNameAsync(string @namespace, string name, bool embed = false, bool useAuth = false, CancellationToken cancellationToken = default)
{
return _httpHelper.GetRequestAsync<BlockType>($"{_methodPath}/{@namespace}/{name}", embed, useAuth, cancellationToken: cancellationToken);
}
}
20 changes: 20 additions & 0 deletions src/WordPressPCL/Client/Blocks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using WordPressPCL.Models;
using WordPressPCL.Utility;

namespace WordPressPCL.Client;

/// <summary>
/// Client class for interaction with the Reusable Blocks endpoint WP REST API (<c>wp/v2/blocks</c>).
/// </summary>
public class Blocks : CRUDOperation<Block, BlocksQueryBuilder>
{
private const string _methodPath = "blocks";

/// <summary>
/// Constructor
/// </summary>
/// <param name="httpHelper">reference to HttpHelper class for interaction with HTTP</param>
public Blocks(HttpHelper httpHelper) : base(httpHelper, _methodPath)
{
}
}
67 changes: 67 additions & 0 deletions src/WordPressPCL/Client/GlobalStylesClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using WordPressPCL.Models;
using WordPressPCL.Utility;

namespace WordPressPCL.Client;

/// <summary>
/// Client class for interaction with the Global Styles endpoint WP REST API (<c>wp/v2/global-styles/{id}</c>).
/// Global styles objects are identified by an integer ID obtained from the site's API index or theme endpoint.
/// </summary>
public class GlobalStylesClient
{
private readonly HttpHelper _httpHelper;
private const string _methodPath = "global-styles";

/// <summary>
/// Constructor
/// </summary>
/// <param name="httpHelper">reference to HttpHelper class for interaction with HTTP</param>
public GlobalStylesClient(HttpHelper httpHelper)
{
_httpHelper = httpHelper;
}

/// <summary>
/// Get the global styles object for a given ID.
/// </summary>
/// <param name="id">The global styles post ID</param>
/// <param name="embed">Include embed info</param>
/// <param name="useAuth">Send request with authentication header</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The global styles object</returns>
public Task<Models.GlobalStyles> GetByIdAsync(int id, bool embed = false, bool useAuth = true, CancellationToken cancellationToken = default)
{
return _httpHelper.GetRequestAsync<Models.GlobalStyles>($"{_methodPath}/{id}", embed, useAuth, cancellationToken: cancellationToken);
}

/// <summary>
/// Get the global styles for a specific theme by its stylesheet slug.
/// </summary>
/// <param name="stylesheet">The theme stylesheet slug (e.g. "twentytwentyfour")</param>
/// <param name="embed">Include embed info</param>
/// <param name="useAuth">Send request with authentication header</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The theme's global styles object</returns>
public Task<Models.GlobalStyles> GetThemeStylesAsync(string stylesheet, bool embed = false, bool useAuth = true, CancellationToken cancellationToken = default)
{
return _httpHelper.GetRequestAsync<Models.GlobalStyles>($"{_methodPath}/themes/{stylesheet}", embed, useAuth, cancellationToken: cancellationToken);
}

/// <summary>
/// Update an existing global styles object.
/// </summary>
/// <param name="entity">Global styles object with updated values</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Updated global styles object</returns>
public async Task<Models.GlobalStyles> UpdateAsync(Models.GlobalStyles entity, CancellationToken cancellationToken = default)
{
string json = JsonSerializer.Serialize(entity, _httpHelper.JsonSerializerOptions);
using StringContent postBody = new(json, Encoding.UTF8, "application/json");
return (await _httpHelper.PostRequestAsync<Models.GlobalStyles>($"{_methodPath}/{entity.Id}", postBody, cancellationToken: cancellationToken).ConfigureAwait(false)).Item1;
}
}
20 changes: 20 additions & 0 deletions src/WordPressPCL/Client/NavigationClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using WordPressPCL.Models;
using WordPressPCL.Utility;

namespace WordPressPCL.Client;

/// <summary>
/// Client class for interaction with the Navigation endpoint WP REST API (<c>wp/v2/navigation</c>).
/// </summary>
public class NavigationClient : CRUDOperation<Navigation, NavigationQueryBuilder>
{
private const string _methodPath = "navigation";

/// <summary>
/// Constructor
/// </summary>
/// <param name="httpHelper">reference to HttpHelper class for interaction with HTTP</param>
public NavigationClient(HttpHelper httpHelper) : base(httpHelper, _methodPath)
{
}
}
68 changes: 68 additions & 0 deletions src/WordPressPCL/Client/PageAutosaves.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using WordPressPCL.Models;
using WordPressPCL.Utility;

namespace WordPressPCL.Client;

/// <summary>
/// Client class for interaction with the Page Autosaves endpoint WP REST API
/// (<c>wp/v2/pages/{pageId}/autosaves</c>).
/// </summary>
public class PageAutosaves
{
private readonly HttpHelper _httpHelper;
private const string _methodPath = "autosaves";
private readonly int _pageId;

/// <summary>
/// Constructor
/// </summary>
/// <param name="httpHelper">reference to HttpHelper class for interaction with HTTP</param>
/// <param name="pageId">ID of the parent page</param>
public PageAutosaves(HttpHelper httpHelper, int pageId)
{
_httpHelper = httpHelper;
_pageId = pageId;
}

/// <summary>
/// Get the available autosaves for the page.
/// </summary>
/// <param name="embed">Include embed info</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of autosaves</returns>
public Task<List<PostRevision>> GetAsync(bool embed = false, CancellationToken cancellationToken = default)
{
return _httpHelper.GetRequestAsync<List<PostRevision>>($"pages/{_pageId}/{_methodPath}", embed, true, cancellationToken: cancellationToken);
}

/// <summary>
/// Get a specific autosave by ID.
/// </summary>
/// <param name="id">Autosave ID</param>
/// <param name="embed">Include embed info</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The requested autosave</returns>
public Task<PostRevision> GetByIdAsync(int id, bool embed = false, CancellationToken cancellationToken = default)
{
return _httpHelper.GetRequestAsync<PostRevision>($"pages/{_pageId}/{_methodPath}/{id}", embed, true, cancellationToken: cancellationToken);
}

/// <summary>
/// Create an autosave for the page.
/// </summary>
/// <param name="page">The page to autosave</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The created autosave</returns>
public async Task<PostRevision> CreateAsync(Page page, CancellationToken cancellationToken = default)
{
string json = JsonSerializer.Serialize(page, _httpHelper.JsonSerializerOptions);
using StringContent postBody = new(json, Encoding.UTF8, "application/json");
return (await _httpHelper.PostRequestAsync<PostRevision>($"pages/{_pageId}/{_methodPath}", postBody, cancellationToken: cancellationToken).ConfigureAwait(false)).Item1;
}
}
Loading