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
4 changes: 4 additions & 0 deletions .github/changelog/integrations
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add integrations framework for third-party plugin support.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
},
"autoload": {
"classmap": [
"includes/"
"includes/",
"integrations/"
]
},
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions includes/class-atmosphere.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Atmosphere\Transformer\Document;
use Atmosphere\Transformer\Publication;
use Atmosphere\Transformer\TID;
use Atmosphere\Integrations\Load;
use Atmosphere\WP_Admin\Admin;

/**
Expand Down Expand Up @@ -43,6 +44,9 @@ public function init(): void {
// JSON preview for AT Protocol records.
\add_action( 'template_redirect', array( $this, 'preview' ) );

// Plugin integrations.
Load::init();

// Post lifecycle hooks.
\add_action( 'transition_post_status', array( $this, 'on_status_change' ), 10, 3 );

Expand Down
17 changes: 17 additions & 0 deletions includes/content-parser/class-markpub.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ private static function transform_block( array $block ): ?string {
return self::inline_html_to_markdown( $html );
}

/**
* Filters the markdown output for a single block.
*
* Integrations can hook this to handle custom block types
* (e.g. jetpack/slideshow, woocommerce/product). Return a
* string to use as the block's markdown, or null to let
* the default handler process it.
*
* @param string|null $markdown Markdown output. Default null.
* @param array $block Parsed WordPress block.
*/
$markdown = \apply_filters( 'atmosphere_markpub_block', null, $block );

if ( null !== $markdown ) {
return $markdown;
}

return match ( $block['blockName'] ) {
'core/paragraph' => self::paragraph( $block ),
'core/heading' => self::heading( $block ),
Expand Down
74 changes: 74 additions & 0 deletions integrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Integrations

Plugin-specific integrations that teach ATmosphere how to handle custom block types and content from third-party plugins.

## How it works

When ATmosphere converts a WordPress post to an AT Protocol record, the Markpub content parser walks through each Gutenberg block and converts it to markdown. Core blocks (paragraph, heading, image, list, etc.) are handled by default. For custom blocks from other plugins, integrations hook into the `atmosphere_markpub_block` filter to provide their own conversion.

## Adding an integration

1. Create `class-{plugin-name}.php` in this directory.
2. Register the integration in `class-load.php` with a check for the target plugin.
3. Hook into `atmosphere_markpub_block` to handle the plugin's custom blocks.

### Example: Jetpack

**`class-jetpack.php`**

```php
<?php
namespace Atmosphere\Integrations;

\defined( 'ABSPATH' ) || exit;

class Jetpack {

public static function init(): void {
\add_filter( 'atmosphere_markpub_block', array( self::class, 'transform_block' ), 10, 2 );
}

public static function transform_block( ?string $markdown, array $block ): ?string {
return match ( $block['blockName'] ) {
'jetpack/slideshow' => self::slideshow( $block ),
'jetpack/tiled-gallery' => self::gallery( $block ),
default => $markdown,
};
}

private static function slideshow( array $block ): ?string {
// Convert slideshow block to markdown image list.
}

private static function gallery( array $block ): ?string {
// Convert gallery block to markdown image list.
}
}
```

**In `class-load.php`**

```php
public static function register(): void {
if ( \defined( 'JETPACK__VERSION' ) ) {
Jetpack::init();
}
}
```

## Available filters

| Filter | Arguments | Description |
|---|---|---|
| `atmosphere_markpub_block` | `?string $markdown`, `array $block` | Handle a custom block type. Return markdown string or `null` to pass through to default handling. |
| `atmosphere_content_parser` | `Content_Parser\|null $parser`, `WP_Post $post` | Replace the entire content parser or return `null` to disable. |
| `atmosphere_document_content` | `array $content`, `WP_Post $post`, `Content_Parser $parser` | Modify the parsed content object before it is added to the document record. |
| `atmosphere_html_to_markdown` | `string $markdown`, `string $content` | Override the final markdown output from the Markpub parser. |

## Conventions

- One class per plugin, all methods static.
- File naming: `class-{plugin-name}.php`.
- Namespace: `Atmosphere\Integrations`.
- Always guard with a plugin check (`\defined()`, `\class_exists()`, etc.) in `class-load.php`.
- Return `$markdown` (the first argument) unchanged from the filter when the block is not yours.
37 changes: 37 additions & 0 deletions integrations/class-load.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* Integration loader.
*
* Conditionally loads plugin-specific integrations when their
* target plugin is active. Each integration is a static class
* with an init() method that registers hooks.
*
* @package Atmosphere
*/

namespace Atmosphere\Integrations;

\defined( 'ABSPATH' ) || exit;

/**
* Integration loader.
*/
class Load {

/**
* Initialize all available integrations.
*
* Runs on plugins_loaded at priority 20 so all plugins
* have registered their constants and classes.
*/
public static function init(): void {
\add_action( 'plugins_loaded', array( self::class, 'register' ), 20 );
}

/**
* Register integrations whose target plugin is active.
*/
public static function register(): void {
// Integrations are registered here as they are added.
}
}