Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
php-version: [8.2, 8.3, 8.4]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -31,7 +31,7 @@ jobs:
done

- name: Cache Composer dependencies
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: |
**/vendor
Expand All @@ -56,7 +56,7 @@ jobs:
run: make build

- name: Upload build artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: plugin-packages-php${{ matrix.php-version }}
path: dist/
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Individual plugin versions are tracked separately in their respective plugin hea

## [Unreleased]

### Changed
- Bumped `actions/checkout` (v4→v7), `actions/cache` (v4→v6), and `actions/upload-artifact` (v4→v7) in both workflows to their Node 24 releases, clearing the "Node.js 20 is deprecated" warnings GitHub Actions now emits on every job step
- Adopted `silverassist/wp-plugin-kernel` in all three sub-plugins, replacing each one's own duplicated `LoadableInterface`/`Plugin` bootstrap with the shared `AbstractPlugin` — components now implement `get_priority()`/`should_load()`/`init()` (the kernel's contract) instead of the old local `priority()`/`register()`. In `contentful-tables`, `TableDataLoader` became a proper singleton (`ShortcodeRegistrar`/`SettingsPage` now fetch it via `TableDataLoader::instance()` instead of constructor injection), since kernel-managed components are instantiated with no arguments.

### Added
- **community-listings:** Admin meta box ("Provider Listings") on the `community` post type editor, city-level posts only — lets editors view/update the `provider_listings` JSON without CLI access. JSON-safe save via `$wpdb->update()`/`insert()` (avoids `update_post_meta()`'s `wp_unslash()` corrupting `\"` escapes), with byte/provider counts, client-side validation, and post-meta cache invalidation after the direct write (#2)

Expand Down
27 changes: 23 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,31 @@ Components load with specific priorities:
- **30**: UI components (admin pages)

### Interface Implementation
All loadable components must implement `LoadableInterface`:
All loadable components must implement `LoadableInterface` from `silverassist/wp-plugin-kernel`, and expose a static `instance()` method (the kernel loads components via `$class::instance()`):

```php
interface LoadableInterface {
public function priority(): int;
public function register(): void;
use SilverAssist\PluginKernel\Interfaces\LoadableInterface;

final class YourComponent implements LoadableInterface {
private static ?self $instance = null;

private function __construct() {}

public static function instance(): self {
return self::$instance ??= new self();
}

public function get_priority(): int {
return 20;
}

public function should_load(): bool {
return true;
}

public function init(): void {
// Register WordPress hooks here.
}
}
```

Expand Down
4 changes: 1 addition & 3 deletions community-listings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ community-listings/
├── includes/
│ ├── Core/
│ │ ├── Activator.php # Activation/deactivation handlers
│ │ ├── Interfaces/
│ │ │ └── LoadableInterface.php # Component contract
│ │ └── Plugin.php # Singleton bootstrap
│ │ └── Plugin.php # Singleton bootstrap (extends wp-plugin-kernel's AbstractPlugin)
│ └── Service/
│ ├── CptRegistrar.php # CPT + meta field registration (priority 10)
│ ├── GraphQLResolver.php # WPGraphQL do_shortcode() (priority 20)
Expand Down
1 change: 1 addition & 0 deletions community-listings/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"license": "proprietary",
"require": {
"php": ">=8.2",
"silverassist/wp-plugin-kernel": "^1.0",
"silverassist/wp-settings-hub": "^1.1"
},
"require-dev": {
Expand Down
44 changes: 41 additions & 3 deletions community-listings/includes/Admin/ProviderListingsMetaBox.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace SilverAssist\CommunityListings\Admin;

use SilverAssist\CommunityListings\Core\Interfaces\LoadableInterface;
use SilverAssist\PluginKernel\Interfaces\LoadableInterface;

/**
* Registers and handles provider_listings meta box editing.
Expand All @@ -21,6 +21,33 @@
*/
final class ProviderListingsMetaBox implements LoadableInterface {

/**
* Singleton instance.
*
* @since 2.3.0
*
* @var self|null
*/
private static ?self $instance = null;

/**
* Prevent direct instantiation — use instance().
*
* @since 2.3.0
*/
private function __construct() {}

/**
* Return the singleton instance.
*
* @since 2.3.0
*
* @return self
*/
public static function instance(): self {
return self::$instance ??= new self();
}

/**
* Nonce action.
*
Expand Down Expand Up @@ -64,18 +91,29 @@ final class ProviderListingsMetaBox implements LoadableInterface {
*
* @return int Loading priority.
*/
public function priority(): int {
public function get_priority(): int {
return 25;
}

/**
* Whether this component should load.
*
* @since 2.3.0
*
* @return bool
*/
public function should_load(): bool {
return true;
}

/**
* Register WordPress hooks.
*
* @since 2.2.5
*
* @return void
*/
public function register(): void {
public function init(): void {
\add_action( 'add_meta_boxes_community', array( $this, 'register_meta_box' ) );
\add_action( 'save_post_community', array( $this, 'save_provider_listings' ), 10, 3 );
\add_action( 'admin_notices', array( $this, 'render_invalid_json_notice' ) );
Expand Down
44 changes: 41 additions & 3 deletions community-listings/includes/Admin/SettingsPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace SilverAssist\CommunityListings\Admin;

use SilverAssist\CommunityListings\Core\Interfaces\LoadableInterface;
use SilverAssist\PluginKernel\Interfaces\LoadableInterface;
use SilverAssist\SettingsHub\SettingsHub;

/**
Expand All @@ -35,25 +35,63 @@ final class SettingsPage implements LoadableInterface {
*/
private const PLUGIN_SLUG = 'community-listings';

/**
* Singleton instance.
*
* @since 2.3.0
*
* @var self|null
*/
private static ?self $instance = null;

/**
* Prevent direct instantiation — use instance().
*
* @since 2.3.0
*/
private function __construct() {}

/**
* Return the singleton instance.
*
* @since 2.3.0
*
* @return self
*/
public static function instance(): self {
return self::$instance ??= new self();
}

/**
* Return the loading priority.
*
* @since 2.1.0
*
* @return int Loading priority.
*/
public function priority(): int {
public function get_priority(): int {
return 30;
}

/**
* Whether this component should load.
*
* @since 2.3.0
*
* @return bool
*/
public function should_load(): bool {
return true;
}

/**
* Register WordPress hooks.
*
* @since 2.1.0
*
* @return void
*/
public function register(): void {
public function init(): void {
\add_action( 'init', array( $this, 'register_with_settings_hub' ) );
}

Expand Down
41 changes: 0 additions & 41 deletions community-listings/includes/Core/Interfaces/LoadableInterface.php

This file was deleted.

78 changes: 16 additions & 62 deletions community-listings/includes/Core/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,82 +14,36 @@

use SilverAssist\CommunityListings\Admin\ProviderListingsMetaBox;
use SilverAssist\CommunityListings\Admin\SettingsPage;
use SilverAssist\CommunityListings\Core\Interfaces\LoadableInterface;
use SilverAssist\CommunityListings\Service\CptRegistrar;
use SilverAssist\CommunityListings\Service\GraphQLResolver;
use SilverAssist\CommunityListings\Service\RestApiFilters;
use SilverAssist\PluginKernel\AbstractPlugin;

/**
* Singleton that bootstraps the plugin.
*
* Singleton access (instance()) and the priority-ordered component loading
* loop are inherited from AbstractPlugin (silverassist/wp-plugin-kernel) —
* this class only declares which components to load.
*
* @since 2.0.0
*/
final class Plugin {

/**
* Singleton instance.
*
* @since 2.0.0
*
* @var self|null
*/
private static ?self $instance = null;

/**
* Registered loadable components.
*
* @since 2.0.0
*
* @var LoadableInterface[]
*/
private array $components = array();

/**
* Prevent direct instantiation.
*
* @since 2.0.0
*/
private function __construct() {}

/**
* Return the singleton instance.
*
* @since 2.0.0
*
* @return self Plugin instance.
*/
public static function instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
final class Plugin extends AbstractPlugin {

/**
* Initialise all plugin components.
* List the component classes this plugin loads.
*
* @since 2.0.0
* @since 2.3.0
*
* @return void
* @return array<class-string>
*/
public function init(): void {
$this->components = array(
new CptRegistrar(),
new RestApiFilters(),
new GraphQLResolver(),
new ProviderListingsMetaBox(),
new SettingsPage(),
protected function get_components(): array {
return array(
CptRegistrar::class,
RestApiFilters::class,
GraphQLResolver::class,
ProviderListingsMetaBox::class,
SettingsPage::class,
);

// Sort by priority ascending.
\usort(
$this->components,
static fn ( LoadableInterface $a, LoadableInterface $b ): int => $a->priority() <=> $b->priority()
);

// Register hooks.
foreach ( $this->components as $component ) {
$component->register();
}
}
}
Loading