Add most minimal setup wizard, welcome page with status and basic health checks (#67)
All checks were successful
PHP Code Checker / PHP Code Checker (push) Successful in 40s
PHPUnit / PHPUnit – PHP 8.1 (push) Successful in 1m5s
PHPUnit / PHPUnit – PHP 8.2 (push) Successful in 1m6s
PHPUnit / PHPUnit – PHP 8.3 (push) Successful in 1m6s

Reviewed-on: Event-Federation/wordpress-activitypub-event-bridge#67
Co-authored-by: André Menrath <andre.menrath@posteo.de>
Co-committed-by: André Menrath <andre.menrath@posteo.de>
This commit is contained in:
André Menrath 2024-10-19 16:46:50 +02:00 committed by André Menrath
parent 580b6b9989
commit 7bc134e135
18 changed files with 482 additions and 75 deletions

12
CHANGELOG.md Normal file
View file

@ -0,0 +1,12 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.0] - 2024-10-20
### Added
* Initial version tag.

View file

@ -0,0 +1,70 @@
.activitypub-event-bridge-settings-page .box {
border: 1px solid #c3c4c7;
background-color: #fff;
padding: 1em 1.5em;
margin-bottom: 1.5em;
}
.activitypub-event-bridge-settings-page .box ul.activitypub-event-bridge-list {
list-style-type: disc;
margin-left: 1.4rem;
}
.activitypub-event-bridge-settings-page .box pre {
padding: 1rem;
min-height: 200px;
box-shadow: none;
border-radius: 15px;
border: 1px solid #dfe0e2;
background-color: #f7f7f7;
}
.activitypub-event-bridge-settings {
max-width: 800px;
margin: 0 auto;
}
.activitypub-event-bridge-settings-header {
text-align: center;
margin: 0 0 1rem;
background: #fff;
border-bottom: 1px solid #dcdcde;
}
.activitypub-event-bridge-settings-title-section {
display: flex;
align-items: center;
justify-content: center;
clear: both;
padding-top: 8px;
}
.activitypub-event-bridge-settings-tabs-wrapper {
display: -ms-inline-grid;
-ms-grid-columns: auto auto auto auto;
vertical-align: top;
display: inline-grid;
grid-template-columns: auto auto auto auto;
}
.activitypub-event-bridge-settings-tab.active {
box-shadow: inset 0 -3px #3582c4;
font-weight: 600;
}
.activitypub-event-bridge-settings-tab {
display: block;
text-decoration: none;
color: inherit;
padding: .5rem 1rem 1rem;
margin: 0 1rem;
transition: box-shadow .5s ease-in-out;
}
.activitypub-event-bridge-settings .box h3 {
font-size: 1.1rem!important;
}
#activitypub_event_bridge_initially_activated {
display: hidden;
}

View file

@ -1,6 +0,0 @@
.activitypub-event-bridge-settings-page .box {
border: 1px solid #c3c4c7;
background-color: #fff;
padding: 1em 1.5em;
margin-bottom: 1.5em;
}

View file

@ -0,0 +1,181 @@
<?php
/**
* Health_Check class.
*
* @package Activitypub_Event_Bridge
*/
namespace ActivityPub_Event_Bridge\Admin;
use Activitypub\Transformer\Factory as Transformer_Factory;
use ActivityPub_Event_Bridge\Plugins\Event_Plugin;
use ActivityPub_Event_Bridge\Setup;
use WP_Query;
/**
* ActivityPub Health_Check Class.
*/
class Health_Check {
/**
* Initialize health checks.
*/
public static function init() {
\add_filter( 'site_status_tests', array( self::class, 'add_tests' ) );
\add_filter( 'debug_information', array( self::class, 'add_debug_information' ) );
}
/**
* Add tests to the Site Health Check.
*
* @param array $tests The test array.
*
* @return array The filtered test array.
*/
public static function add_tests( $tests ) {
$tests['direct']['activitypub_event_bridge_test'] = array(
'label' => __( 'ActivityPub Event Transformer Test', 'activitypub-event-bridge' ),
'test' => array( self::class, 'test_event_transformation' ),
);
return $tests;
}
/**
* The the transformation of the most recent event posts.
*
* @return array
*/
public static function test_event_transformation() {
$result = array(
'label' => \__( 'Transformation of Events to a valid ActivityStreams representation.', 'activitypub' ),
'status' => 'good',
'badge' => array(
'label' => \__( 'ActivityPub Event Bridge', 'activitypub-event-bridge' ),
'color' => 'green',
),
'description' => \sprintf(
'<p>%s</p>',
\__( 'The transformation of your most recent events was successful.', 'activitypub-event-bridge' )
),
'actions' => '',
'test' => 'test_event_transformation',
);
$check = self::transform_most_recent_event_posts();
if ( true === $check ) {
return $result;
}
$result['status'] = 'critical';
$result['label'] = \__( 'One or more of your most recent events failed to transform to ActivityPub', 'activitypub-event-bridge' );
$result['badge']['color'] = 'red';
$result['description'] = \sprintf(
'<p>%s</p>',
$check->get_error_message()
);
return $result;
}
/**
* Test if right transformer gets applied.
*
* @param Event_Plugin $event_plugin The event plugin definition.
*
* @return bool True if the check passed.
*/
public static function test_if_event_transformer_is_used( $event_plugin ) {
// Get a (random) event post.
$event_posts = self::get_most_recent_event_posts( $event_plugin->get_post_type(), 1 );
// If no post is found, we can not do this test.
if ( ! $event_posts || is_wp_error( $event_posts ) || empty( $event_posts ) ) {
return true;
}
// Call the transformer Factory.
$transformer = Transformer_Factory::get_transformer( $event_posts[0] );
// Check that we got the right transformer.
$desired_transformer_class = $event_plugin::get_activitypub_event_transformer_class();
if ( $transformer instanceof $desired_transformer_class ) {
return true;
}
return false;
}
/**
* Retrieves the most recently published event posts of a certain event post type.
*
* @param string $event_post_type The post type of the events.
* @param int $number_of_posts The maximum number of events to return.
*
* @return WP_Post[]|false Array of event posts, or false if none are found.
*/
public static function get_most_recent_event_posts( $event_post_type, $number_of_posts = 5 ) {
$args = array(
'numberposts' => $number_of_posts,
'category' => 0,
'orderby' => 'date',
'order' => 'DESC',
'include' => array(),
'exclude' => array(),
'meta_key' => '',
'meta_value' => '',
'post_type' => $event_post_type,
'suppress_filters' => true,
);
$query = new WP_Query();
return $query->query( $args );
}
/**
* Transform the most recent event posts.
*/
public static function transform_most_recent_event_posts() {
return true;
}
/**
* Retrieves information like name and version from active event plugins.
*/
private static function get_info_about_active_event_plugins() {
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
$info = array();
foreach ( $active_event_plugins as $active_event_plugin ) {
$event_plugin_file = $active_event_plugin->get_plugin_file();
$event_plugin_data = \get_plugin_data( $event_plugin_file );
$event_plugin_name = isset( $event_plugin_data['Plugin Name'] ) ? $event_plugin_data['Plugin Name'] : 'Name not found';
$event_plugin_version = isset( $event_plugin_version['Plugin Version'] ) ? $event_plugin_version['Plugin Version'] : 'Version not found';
$info[] = array(
'event_plugin_name' => $event_plugin_name,
'event_plugin_version' => $event_plugin_version,
'event_plugin_file' => $event_plugin_file,
);
}
}
/**
* Static function for generating site debug data when required.
*
* @param array $info The debug information to be added to the core information page.
* @return array The extended information.
*/
public static function add_debug_information( $info ) {
$info['activitypub_event_bridge'] = array(
'label' => __( 'ActivityPub Event Bridge', 'activitypub-event-bridge' ),
'fields' => array(
'plugin_version' => array(
'label' => __( 'Plugin Version', 'activitypub' ),
'value' => ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_VERSION,
'private' => true,
),
'active_event_plugins' => self::get_info_about_active_event_plugins(),
),
);
return $info;
}
}

View file

@ -89,21 +89,41 @@ class Settings_Page {
* @return void
*/
public static function settings_page(): void {
$plugin_setup = Setup::get_instance();
$event_plugins = $plugin_setup->get_active_event_plugins();
$event_terms = array();
foreach ( $event_plugins as $event_plugin ) {
$event_terms = array_merge( $event_terms, self::get_event_terms( $event_plugin ) );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( empty( $_GET['tab'] ) ) {
$tab = 'welcome';
} else {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$tab = sanitize_key( $_GET['tab'] );
}
$args = array(
'slug' => self::SETTINGS_SLUG,
'event_terms' => $event_terms,
);
switch ( $tab ) {
case 'settings':
$plugin_setup = Setup::get_instance();
\load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/settings.php', true, $args );
$event_plugins = $plugin_setup->get_active_event_plugins();
$event_terms = array();
foreach ( $event_plugins as $event_plugin ) {
$event_terms = array_merge( $event_terms, self::get_event_terms( $event_plugin ) );
}
$args = array(
'slug' => self::SETTINGS_SLUG,
'event_terms' => $event_terms,
);
\load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/settings.php', true, $args );
break;
case 'welcome':
default:
wp_enqueue_script( 'plugin-install' );
add_thickbox();
wp_enqueue_script( 'updates' );
\load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/welcome.php', true );
break;
}
}
}

View file

@ -61,6 +61,16 @@ class Settings {
'sanitize_callback' => array( self::class, 'sanitize_event_category_mappings' ),
)
);
\register_setting(
'activitypub-event-bridge',
'activitypub_event_bridge_initially_activated',
array(
'type' => 'boolean',
'description' => \__( 'Whether the plugin just got activated for the first time.', 'activitypub' ),
'default' => 1,
)
);
}
/**

View file

@ -17,6 +17,7 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use ActivityPub_Event_Bridge\Admin\Event_Plugin_Admin_Notices;
use ActivityPub_Event_Bridge\Admin\General_Admin_Notices;
use ActivityPub_Event_Bridge\Admin\Health_Check;
use ActivityPub_Event_Bridge\Admin\Settings_Page;
use ActivityPub_Event_Bridge\Plugins\Event_Plugin;
@ -167,20 +168,19 @@ class Setup {
add_action( 'admin_init', array( $this, 'do_admin_notices' ) );
add_action( 'admin_init', array( Settings::class, 'register_settings' ) );
add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_styles' ) );
add_action( 'admin_menu', array( Settings_Page::class, 'admin_menu' ) );
add_filter(
'plugin_action_links_' . ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_BASENAME,
array( Settings_Page::class, 'settings_link' )
);
// If we don't have any active event plugins, or the ActivityPub plugin is not enabled, abort here.
if ( empty( $this->active_event_plugins ) || ! $this->activitypub_plugin_is_active ) {
return;
}
add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_styles' ) );
add_action( 'admin_menu', array( Settings_Page::class, 'admin_menu' ) );
add_filter(
'plugin_action_links_' . ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_BASENAME,
array( Settings_Page::class, 'settings_link' )
);
add_action( 'init', array( Health_Check::class, 'init' ) );
// Check if the minimum required version of the ActivityPub plugin is installed.
if ( ! version_compare( $this->activitypub_plugin_version, ACTIVITYPUB_EVENT_BRIDGE_ACTIVITYPUB_PLUGIN_MIN_VERSION ) ) {
@ -287,7 +287,7 @@ class Setup {
* This method handles the activation of the ActivityPub Event Bridge plugin.
*
* @since 1.0.0
*
* @see register_activation_hook()
* @return void
*/
public function activate(): void {

View file

@ -45,12 +45,24 @@ abstract class Event_Plugin {
abstract public static function get_event_category_taxonomy(): string;
/**
* Returns the ID of the main settings page of the plugin.
* Returns the IDs of the admin pages of the plugin.
*
* @return string The settings page url.
* @return array The IDs of one or several admin/settings pages.
*/
public static function get_settings_page(): string {
return '';
public static function get_settings_pages(): array {
return array();
}
/**
* Get the plugins name from the main plugin-file's top-level-file-comment.
*/
final public static function get_plugin_name(): string {
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . static::get_plugin_file() );
if ( isset( $plugin_data['Name'] ) ) {
return $plugin_data['Name'];
} else {
return '';
}
}
/**
@ -62,7 +74,7 @@ abstract class Event_Plugin {
// Check if we are on a edit page for the event, or on the settings page of the event plugin.
$is_event_plugins_edit_page = 'edit' === $screen->base && static::get_post_type() === $screen->post_type;
$is_event_plugins_settings_page = static::get_settings_page() === $screen->id;
$is_event_plugins_settings_page = in_array( $screen->id, static::get_settings_pages(), true );
return $is_event_plugins_edit_page || $is_event_plugins_settings_page;
}

View file

@ -41,12 +41,12 @@ final class Eventin extends Event_plugin {
}
/**
* Returns the ID of the main settings page of the plugin.
* Returns the IDs of the admin pages of the plugin.
*
* @return string The settings page url.
* @return array The settings page url.
*/
public static function get_settings_page(): string {
return 'eventin'; // Base always is wp-admin/admin.php?page=eventin.
public static function get_settings_pages(): array {
return array( 'eventin' ); // Base always is wp-admin/admin.php?page=eventin.
}
/**

View file

@ -41,12 +41,12 @@ final class Events_Manager extends Event_Plugin {
}
/**
* Returns the ID of the main settings page of the plugin.
* Returns the IDs of the admin pages of the plugin.
*
* @return string The settings page url.
* @return array The settings page urls.
*/
public static function get_settings_page(): string {
return 'wp-admin/edit.php?post_type=event&page=events-manager-options#general';
public static function get_settings_page(): array {
return array();
}
/**

View file

@ -41,12 +41,12 @@ final class GatherPress extends Event_Plugin {
}
/**
* Returns the ID of the main settings page of the plugin.
* Returns the IDs of the admin pages of the plugin.
*
* @return string The settings page url.
* @return array The settings page urls.
*/
public static function get_settings_page(): string {
return class_exists( '\GatherPress\Core\Utility' ) ? \GatherPress\Core\Utility::prefix_key( 'general' ) : 'gatherpress_general';
public static function get_settings_pages(): array {
return array( class_exists( '\GatherPress\Core\Utility' ) ? \GatherPress\Core\Utility::prefix_key( 'general' ) : 'gatherpress_general' );
}
/**

View file

@ -42,12 +42,12 @@ final class Modern_Events_Calendar_Lite extends Event_plugin {
}
/**
* Returns the ID of the main settings page of the plugin.
* Returns the IDs of the admin pages of the plugin.
*
* @return string The settings page url.
* @return array The settings page urls.
*/
public static function get_settings_page(): string {
return 'mec-event';
public static function get_settings_pages(): array {
return array( 'MEC-settings', 'MEC-support', 'MEC-ix', 'MEC-wizard', 'MEC-addons', 'mec-intro' );
}
/**

View file

@ -41,17 +41,17 @@ final class The_Events_Calendar extends Event_plugin {
}
/**
* Returns the ID of the main settings page of the plugin.
* Returns the IDs of the admin pages of the plugin.
*
* @return string The settings page url.
* @return array The settings page urls.
*/
public static function get_settings_page(): string {
public static function get_settings_pages(): array {
if ( class_exists( '\Tribe\Events\Admin\Settings' ) ) {
$page = \Tribe\Events\Admin\Settings::$settings_page_id;
} else {
$page = 'tec-events-settings';
}
return sprintf( 'edit.php?post_type=tribe_events&page=%s', $page );
return array( $page );
}
/**

View file

@ -44,12 +44,12 @@ final class VS_Event_List extends Event_Plugin {
}
/**
* Returns the ID of the main settings page of the plugin.
* Returns the IDs of the admin pages of the plugin.
*
* @return string The settings page url.
* @return array The settings page urls.
*/
public static function get_settings_page(): string {
return 'settings_page_vsel';
public static function get_settings_pages(): array {
return array( 'settings_page_vsel' );
}
/**

View file

@ -44,12 +44,12 @@ final class WP_Event_Manager extends Event_Plugin {
}
/**
* Returns the ID of the main settings page of the plugin.
* Returns the IDs of the admin pages of the plugin.
*
* @return string The settings page url.
* @return array The settings page urls.
*/
public static function get_settings_page(): string {
return 'event-manager-settings';
public static function get_settings_pages(): array {
return array( 'event-manager-settings' );
}
/**

View file

@ -0,0 +1,33 @@
<?php
/**
* Template for the header and navigation of the admin pages.
*
* @package ActivityPub_Event_Bridge
*/
/* @var array $args Template arguments. */
$args = wp_parse_args(
$args,
array(
'welcome' => '',
'settings' => '',
)
);
?>
<div class="activitypub-event-bridge-settings-header">
<div class="activitypub-event-bridge-settings-title-section">
<h1><?php \esc_html_e( 'ActivityPub Event Bridge', 'activitypub-event-bridge' ); ?></h1>
</div>
<nav class="activitypub-event-bridge-settings-tabs-wrapper" aria-label="<?php \esc_attr_e( 'Secondary menu', 'activitypub-event-bridge' ); ?>">
<a href="<?php echo \esc_url( admin_url( 'options-general.php?page=activitypub-event-bridge' ) ); ?>" class="activitypub-event-bridge-settings-tab <?php echo \esc_attr( $args['welcome'] ); ?>">
<?php \esc_html_e( 'Welcome', 'activitypub-event-bridge' ); ?>
</a>
<a href="<?php echo \esc_url( admin_url( 'options-general.php?page=activitypub-event-bridge&tab=settings' ) ); ?>" class="activitypub-event-bridge-settings-tab <?php echo \esc_attr( $args['settings'] ); ?>">
<?php \esc_html_e( 'Settings', 'activitypub-event-bridge' ); ?>
</a>
</nav>
</div>
<hr class="wp-header-end">

View file

@ -13,6 +13,14 @@
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\load_template(
__DIR__ . '/admin-header.php',
true,
array(
'settings' => 'active',
)
);
use Activitypub\Activity\Extended_Object\Event;
if ( ! isset( $args ) || ! array_key_exists( 'event_terms', $args ) ) {
@ -31,19 +39,12 @@ $selected_default_event_category = \get_option( 'activitypub_event_bridge_defaul
$current_category_mapping = \get_option( 'activitypub_event_bridge_event_category_mappings', array() );
?>
<div class="activitypub-settings-header">
<div class="activitypub-settings-title-section">
<h1><?php \esc_html_e( 'ActivityPub Event Bridge', 'activitypub-event-bridge' ); ?></h1>
</div>
</div>
<hr class="wp-header-end">
<div class="activitypub-settings activitypub-settings-page activitypub-event-bridge-settings-page hide-if-no-js">
<div class="activitypub-event-bridge-settings activitypub-event-bridge-settings-page hide-if-no-js">
<form method="post" action="options.php">
<?php \settings_fields( 'activitypub-event-bridge' ); ?>
<div class="box">
<h2> <?php esc_html_e( 'Default ActivityPub Event Category', 'activitypub-event-bridge' ); ?> </h2>
<h2> <?php esc_html_e( 'ActivityPub Event Category', 'activitypub-event-bridge' ); ?> </h2>
<p> <?php esc_html_e( 'To help visitors find events more easily, the community created a set of basic event categories. Please select the category that best matches the majority of the events you organize.', 'activitypub-event-bridge' ); ?> </p>
<table class="form-table">
<tr>
@ -59,11 +60,9 @@ $current_category_mapping = \get_option( 'activitypub_event_bridge_event_
</td>
</tr>
</table>
</div>
<?php if ( ! empty( $event_terms ) ) : ?>
<div class="box">
<h2> <?php esc_html_e( 'Advanced Event Category Settings', 'activitypub-event-bridge' ); ?> </h2>
<?php if ( ! empty( $event_terms ) ) : ?>
<h3> <?php esc_html_e( 'Advanced Event Category Settings', 'activitypub-event-bridge' ); ?> </h3>
<p> <?php esc_html_e( 'Take more control by adjusting how your event categories are mapped to the basic category set used in ActivityPub. This option lets you override the default selection above, ensuring more accurate categorization and better visibility for your events.', 'activitypub-event-bridge' ); ?> </p>
<table class="form-table">
<?php foreach ( $event_terms as $event_term ) { ?>
@ -96,8 +95,12 @@ $current_category_mapping = \get_option( 'activitypub_event_bridge_event_
</tr>
<?php } ?>
</table>
<?php endif; ?>
</div>
<!-- This disables the setup wizard. -->
<div class="hidden">
<input type="checkbox" id="activitypub_event_bridge_initially_activated" name="activitypub_event_bridge_initially_activated"/>
</div>
<?php endif; ?>
<?php \submit_button(); ?>
</form>
</div>

72
templates/welcome.php Normal file
View file

@ -0,0 +1,72 @@
<?php
/**
* Status page for the ActivityPub Event Bridge.
*
* @package ActivityPub_Event_Bridge
*/
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use ActivityPub_Event_Bridge\Setup;
use ActivityPub_Event_Bridge\Admin\Settings_Page;
use ActivityPub_Event_Bridge\Admin\Health_Check;
\load_template(
__DIR__ . '/admin-header.php',
true,
array(
'welcome' => 'active',
)
);
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
global $wp_filesystem;
WP_Filesystem();
?>
<div class="activitypub-event-bridge-settings activitypub-event-bridge-settings-page hide-if-no-js">
<div class="box">
<h2><?php \esc_html_e( 'Status', 'activitypub-event-bridge' ); ?></h2>
<p><?php \esc_html_e( 'The ActivityPub Event Bridge detected the following (activated) event plugins:', 'activitypub-event-bridge' ); ?></p>
<ul class="activitypub-event-bridge-list">
<?php foreach ( $active_event_plugins as $active_event_plugin ) { ?>
<li>
<strong><?php echo esc_html( $active_event_plugin->get_plugin_name() ); ?>:</strong>
<br>
<?php
if ( Health_Check::test_if_event_transformer_is_used( $active_event_plugin ) ) {
echo 'The ActivityPub Event Bridge successfully registered to the ActivityPub plugin.';
} else {
echo 'The ActivityPub Event Bridge could not register to the ActivityPub plugin.';
}
?>
</li>
<?php } ?>
</ul>
</div>
<?php if ( get_option( 'activitypub_event_bridge_initially_activated', true ) ) : ?>
<a href="<?php echo esc_url( admin_url( 'options-general.php?page=' . Settings_Page::SETTINGS_SLUG ) . '&tab=settings' ); ?>" role="button">
<button class="button button-primary">
<strong></strong> <?php \esc_html_e( 'Continue your setup', 'activitypub-event-bridge' ); ?>
</button>
</a>
<?php else : ?>
<div class="box">
<h2><?php \esc_html_e( 'Changelog', 'activitypub-event-bridge' ); ?></h2>
<pre>
<?php
$changelog = $wp_filesystem->get_contents( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . '/CHANGELOG.md' );
echo esc_html( substr( $changelog, strpos( $changelog, "\n", 180 ) + 1 ) );
?>
</pre>
</div>
<?php endif; ?>
</div>