updated plugin Event Bridge for ActivityPub version 1.3.0

This commit is contained in:
2026-06-03 21:28:57 +00:00
committed by Gitium
parent 1f3438440f
commit f2d6714572
84 changed files with 1721 additions and 1361 deletions

View File

@ -8,7 +8,7 @@
*/
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
/* @var array $args Template arguments. */
$args = wp_parse_args(

View File

@ -8,7 +8,7 @@
*/
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Event_Bridge_For_ActivityPub\Setup;
@ -24,8 +24,6 @@ if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$activitypub_plugin_is_active = Setup::get_instance()->is_activitypub_plugin_active();
\get_option( 'event_bridge_for_activitypub_event_sources_active', false );
if ( ! isset( $args ) || ! array_key_exists( 'supports_event_sources', $args ) ) {
@ -38,199 +36,198 @@ $event_sources_active = \get_option( 'event_bridge_for_activitypub_event_sourc
$cache_retention_period = \get_option( 'event_bridge_for_activitypub_event_source_cache_retention', DAY_IN_SECONDS );
?>
<?php if ( $activitypub_plugin_is_active ) { ?>
<div class="activitypub-settings hide-if-no-js">
<form method="post" action="options.php">
<?php \settings_fields( 'event-bridge-for-activitypub_event-sources' ); ?>
<div class="box">
<h2><?php \esc_html_e( 'Event Sources', 'event-bridge-for-activitypub' ); ?></h2>
<p id="event-sources-description"><?php esc_html_e( 'This feature allows your WordPress site to retrieve and display events from external sources via ActivityPub. Once enabled, you can add any ActivityPub account as a source of events by following that profile. These events will be cached on your site and seamlessly integrated into your existing event calendar, creating a unified view of events from both internal and external sources.', 'event-bridge-for-activitypub' ); ?></p>
<div class="activitypub-settings hide-if-no-js">
<form method="post" action="options.php">
<?php \settings_fields( 'event-bridge-for-activitypub_event-sources' ); ?>
<div class="box">
<h2><?php \esc_html_e( 'Event Sources', 'event-bridge-for-activitypub' ); ?></h2>
<p id="event-sources-description"><?php esc_html_e( 'This feature allows your WordPress site to retrieve and display events from external sources via ActivityPub. Once enabled, you can add any ActivityPub account as a source of events by following that profile. These events will be cached on your site and seamlessly integrated into your existing event calendar, creating a unified view of events from both internal and external sources.', 'event-bridge-for-activitypub' ); ?></p>
<?php
if ( ! \Activitypub\is_user_type_disabled( 'blog' ) && count( $event_plugins_supporting_event_sources ) ) {
?>
<table class="form-table">
<tbody>
<tr>
<th scope="row">
<label for="event_bridge_for_activitypub_event_sources_active"><?php \esc_html_e( 'Enable External Event Sources', 'event-bridge-for-activitypub' ); ?></label>
</th>
<td>
<input
type="checkbox"
name="event_bridge_for_activitypub_event_sources_active"
id="event_bridge_for_activitypub_event_sources_active"
aria-describedby="event-sources-description"
value="1"
<?php echo \checked( $event_sources_active ); ?>
>
</td>
</tr>
<?php
if ( ! \Activitypub\is_user_type_disabled( 'blog' ) && count( $event_plugins_supporting_event_sources ) ) {
if ( $event_sources_active ) {
?>
<table class="form-table">
<tbody>
<tr>
<th scope="row">
<label for="event_bridge_for_activitypub_event_sources_active"><?php \esc_html_e( 'Enable External Event Sources', 'event-bridge-for-activitypub' ); ?></label>
</th>
<td>
<input
type="checkbox"
name="event_bridge_for_activitypub_event_sources_active"
id="event_bridge_for_activitypub_event_sources_active"
aria-describedby="event-sources-description"
value="1"
<?php echo \checked( $event_sources_active ); ?>
>
</td>
</tr>
<tr>
<th scope="row">
<label for="event_bridge_for_activitypub_integration_used_for_event_sources_feature"><?php \esc_html_e( 'Event Plugin', 'event-bridge-for-activitypub' ); ?></label>
</th>
<td>
<select
name="event_bridge_for_activitypub_integration_used_for_event_sources_feature"
id="event_bridge_for_activitypub_integration_used_for_event_sources_feature"
value="gatherpress"
aria-describedby="event-sources-used-plugin-description"
>
<?php
foreach ( $event_plugins_supporting_event_sources as $event_plugin_class_name => $event_plugin_name ) {
echo '<option value="' . esc_attr( $event_plugin_class_name ) . '" ' . selected( $event_plugin_class_name, Setup::get_event_plugin_integration_used_for_event_sources_feature(), true ) . '>' . esc_attr( $event_plugin_name ) . '</option>';
}
?>
</select>
<p id="event-sources-used-plugin-description"><?php esc_html_e( 'In case you have multiple event plugins installed you might choose which event plugin is utilized.', 'event-bridge-for-activitypub' ); ?></p>
</td>
<tr>
<tr>
<th scope="row">
<label for="event_bridge_for_activitypub_event_source_cache"><?php \esc_html_e( 'Retention Period for External Events', 'event-bridge-for-activitypub' ); ?></label>
</th>
<td>
<select
name="event_bridge_for_activitypub_event_source_cache_retention"
id="event_bridge_for_activitypub_event_source_cache_retention"
aria-describedby="event_bridge_for_activitypub_event-sources-cache-clear-time-frame"
>
<?php
$choices = array(
0 => __( 'Immediately', 'event-bridge-for-activitypub' ),
DAY_IN_SECONDS => __( 'One Day', 'event-bridge-for-activitypub' ),
WEEK_IN_SECONDS => __( 'One Week', 'event-bridge-for-activitypub' ),
MONTH_IN_SECONDS => __( 'One Month', 'event-bridge-for-activitypub' ),
YEAR_IN_SECONDS => __( 'One Year', 'event-bridge-for-activitypub' ),
);
foreach ( $choices as $time => $string ) {
echo '<option value="' . \esc_attr( $time ) . '" ' . \selected( $cache_retention_period, $time, false ) . '>' . \esc_html( $string ) . '</option>';
}
?>
</select>
<p id="event_bridge_for_activitypub_event-sources-cache-clear-time-frame"><?php esc_html_e( 'External events from your event sources will be automatically removed from your site after the selected time period has passed since the event ended. Choose a time frame that works best for your needs.', 'event-bridge-for-activitypub' ); ?></p>
</td>
<tr>
<?php
if ( $event_sources_active ) {
?>
<tr>
<th scope="row">
<label for="event_bridge_for_activitypub_integration_used_for_event_sources_feature"><?php \esc_html_e( 'Event Plugin', 'event-bridge-for-activitypub' ); ?></label>
</th>
<td>
<select
name="event_bridge_for_activitypub_integration_used_for_event_sources_feature"
id="event_bridge_for_activitypub_integration_used_for_event_sources_feature"
value="gatherpress"
aria-describedby="event-sources-used-plugin-description"
>
<?php
foreach ( $event_plugins_supporting_event_sources as $event_plugin_class_name => $event_plugin_name ) {
echo '<option value="' . esc_attr( $event_plugin_class_name ) . '" ' . selected( $event_plugin_class_name, Setup::get_event_plugin_integration_used_for_event_sources_feature(), true ) . '>' . esc_attr( $event_plugin_name ) . '</option>';
}
?>
</select>
<p id="event-sources-used-plugin-description"><?php esc_html_e( 'In case you have multiple event plugins installed you might choose which event plugin is utilized.', 'event-bridge-for-activitypub' ); ?></p>
</td>
<tr>
<tr>
<th scope="row">
<label for="event_bridge_for_activitypub_event_source_cache"><?php \esc_html_e( 'Retention Period for External Events', 'event-bridge-for-activitypub' ); ?></label>
</th>
<td>
<select
name="event_bridge_for_activitypub_event_source_cache_retention"
id="event_bridge_for_activitypub_event_source_cache_retention"
value="0"
aria-describedby="event_bridge_for_activitypub_event-sources-cache-clear-time-frame"
>
<?php
$choices = array(
0 => __( 'Immediately', 'event-bridge-for-activitypub' ),
DAY_IN_SECONDS => __( 'One Day', 'event-bridge-for-activitypub' ),
WEEK_IN_SECONDS => __( 'One Week', 'event-bridge-for-activitypub' ),
MONTH_IN_SECONDS => __( 'One Month', 'event-bridge-for-activitypub' ),
YEAR_IN_SECONDS => __( 'One Year', 'event-bridge-for-activitypub' ),
);
foreach ( $choices as $time => $string ) {
echo '<option value="' . \esc_attr( $time ) . '" ' . \selected( $cache_retention_period, $time, true ) . '>' . \esc_attr( $string ) . '</option>';
}
?>
</select>
<p id="event_bridge_for_activitypub_event-sources-cache-clear-time-frame"><?php esc_html_e( 'External events from your event sources will be automatically removed from your site after the selected time period has passed since the event ended. Choose a time frame that works best for your needs.', 'event-bridge-for-activitypub' ); ?></p>
</td>
<tr>
<?php
}
?>
<tbody>
</table>
<?php
} elseif ( ! \Activitypub\is_user_type_disabled( 'blog' ) ) {
?>
<div class="notice-warning"><p><?php esc_html_e( 'You do not have an Event Plugin installed that supports this feature.', 'event-bridge-for-activitypub' ); ?></p></div>
<p><?php \esc_html_e( 'The following Event Plugins are supported:', 'event-bridge-for-activitypub' ); ?></p>
<?php
$plugins_supporting_event_sources = Setup::detect_event_plugins_supporting_event_sources();
echo '<ul class="event_bridge_for_activitypub-list">';
foreach ( $plugins_supporting_event_sources as $event_plugin ) {
echo '<li>' . esc_attr( $event_plugin->get_plugin_name() ) . '</li>';
}
echo '</ul>';
} else {
$activitypub_plugin_data = \get_plugin_data( ACTIVITYPUB_PLUGIN_FILE );
$notice = sprintf(
/* translators: 1: The name of the ActivityPub plugin. */
_x(
'In order to use this feature your have to enable the Blog-Actor in the the <a href="%1$s">%2$s settings</a>.',
'admin notice',
'event-bridge-for-activitypub'
),
\admin_url( 'options-general.php?page=activitypub&tab=event-bridge-for-activitypub&subpage=settings' ),
\esc_html( $activitypub_plugin_data['Name'] )
);
$allowed_html = array(
'a' => array(
'href' => true,
'title' => true,
),
);
echo '<div class="notice-warning"><p>' . \wp_kses( $notice, $allowed_html ) . '</p></div>';
}
?>
</div>
<?php \submit_button(); ?>
</form>
</div>
<div class="wrap event_bridge_for_activitypub-admin-table-container">
<br>
<?php
if ( \get_option( 'event_bridge_for_activitypub_event_sources_active', false ) ) {
<tbody>
</table>
<?php
} elseif ( ! \Activitypub\is_user_type_disabled( 'blog' ) ) {
?>
<div class="notice-warning"><p><?php esc_html_e( 'You do not have an Event Plugin installed that supports this feature.', 'event-bridge-for-activitypub' ); ?></p></div>
<p><?php \esc_html_e( 'The following Event Plugins are supported:', 'event-bridge-for-activitypub' ); ?></p>
<?php
$plugins_supporting_event_sources = Setup::detect_event_plugins_supporting_event_sources();
echo '<ul class="event_bridge_for_activitypub-list">';
foreach ( $plugins_supporting_event_sources as $event_plugin ) {
echo '<li>' . esc_attr( $event_plugin->get_plugin_name() ) . '</li>';
}
echo '</ul>';
} else {
$activitypub_plugin_data = \get_plugin_data( ACTIVITYPUB_PLUGIN_FILE );
$notice = sprintf(
/* translators: 1: The name of the ActivityPub plugin. */
_x(
'In order to use this feature your have to enable the Blog-Actor in the the <a href="%1$s">%2$s settings</a>.',
'admin notice',
'event-bridge-for-activitypub'
),
\admin_url( 'options-general.php?page=activitypub&tab=event-bridge-for-activitypub&subpage=settings' ),
\esc_html( $activitypub_plugin_data['Name'] )
);
$allowed_html = array(
'a' => array(
'href' => true,
'title' => true,
),
);
echo '<div class="notice-warning"><p>' . \wp_kses( $notice, $allowed_html ) . '</p></div>';
}
?>
<!-- ThickBox content (hidden initially) -->
<div id="Event_Bridge_For_ActivityPub_add_new_source" style="display:none;">
<h2><?php \esc_html_e( 'Add a Trusted Event Source', 'event-bridge-for-activitypub' ); ?></h2>
<ul class="event-bridge-for-activitypub-syntax-list" id="event_bridge_for_activitypub_add_event_source_description">
<?php \esc_html_e( 'Use one of the following syntax:', 'event-bridge-for-activitypub' ); ?>
<li>
<?php \esc_html_e( 'Enter a Fediverse user handle', 'event-bridge-for-activitypub' ); ?> ( <?php esc_html_e( 'e.g.', 'event-bridge-for-activitypub' ); ?> <code>@username@example.social</code>)
</li>
<li>
<?php \esc_html_e( 'ActivityPub account URL or ID', 'event-bridge-for-activitypub' ); ?> ( <?php esc_html_e( 'e.g.', 'event-bridge-for-activitypub' ); ?> <code>https://example.social/user/username</code>)
</li>
<li>
<?php \esc_html_e( 'The domain or URL of a Gancio instance', 'event-bridge-for-activitypub' ); ?> ( <?php esc_html_e( 'e.g.', 'event-bridge-for-activitypub' ); ?> <code>https://demo.gancio.org</code>)
</li>
</ul>
<div class="notice notice-info inline">
<p>
<?php
$number_of_imports = \Event_Bridge_For_ActivityPub\Outbox_Parser::MAX_EVENTS_TO_IMPORT;
$notice = sprintf(
/* translators: 1: The maximum number of imported events. */
__( 'To ensure a smooth start, up to %d upcoming events from this source will be automatically imported soon after adding it.', 'event-bridge-for-activitypub' ),
$number_of_imports
);
echo esc_html( $notice );
?>
</p>
</div>
<form method="post" action="options.php">
<?php \settings_fields( 'event-bridge-for-activitypub_add-event-source' ); ?>
<label for="event_bridge_for_activitypub_add_event_source">
<p>
<?php \esc_html_e( 'Event Source (handle, URL, or instance)', 'event-bridge-for-activitypub' ); ?>:
</p>
</label>
<input
type="text"
style="width: 100%"
name="event_bridge_for_activitypub_add_event_source"
id="event_bridge_for_activitypub_add_event_source"
aria-describedby="event_bridge_for_activitypub_add_event_source_description"
placeholder="@username@example.social or https://example.social/user/username">
<?php \submit_button( __( 'Follow Event Source', 'event-bridge-for-activitypub' ) ); ?>
</form>
</div>
<div class="wrap activitypub-followers-page">
<!-- Table title with add new button like on post edit pages -->
<div class="event_bridge_for_activitypub-admin-table-top">
<h2 class="wp-heading-inline"> <?php esc_html_e( 'Manage Event Sources', 'event-bridge-for-activitypub' ); ?> </h2>
<!-- Button that triggers ThickBox -->
<a href="#TB_inline?width=600&height=400&inlineId=Event_Bridge_For_ActivityPub_add_new_source" class="thickbox page-title-action">
<?php \esc_html_e( 'Add Event Source', 'event-bridge-for-activitypub' ); ?>
</a>
</div>
<form method="get">
<input type="hidden" name="page" value="activitypub" />
<input type="hidden" name="tab" value="event-bridge-for-activitypub" />
<input type="hidden" name="subpage" value="event-sources" />
</div>
<?php \submit_button(); ?>
</form>
</div>
<div class="wrap event_bridge_for_activitypub-admin-table-container">
<br>
<?php
if ( \get_option( 'event_bridge_for_activitypub_event_sources_active', false ) ) {
?>
<!-- ThickBox content (hidden initially) -->
<div id="Event_Bridge_For_ActivityPub_add_new_source" style="display:none;">
<h2><?php \esc_html_e( 'Add a Trusted Event Source', 'event-bridge-for-activitypub' ); ?></h2>
<p><?php \esc_html_e( 'Use one of the following syntax:', 'event-bridge-for-activitypub' ); ?></p>
<ul class="event-bridge-for-activitypub-syntax-list" id="event_bridge_for_activitypub_add_event_source_description">
<li>
<?php \esc_html_e( 'Enter a Fediverse user handle', 'event-bridge-for-activitypub' ); ?> ( <?php esc_html_e( 'e.g.', 'event-bridge-for-activitypub' ); ?> <code>@username@example.social</code>)
</li>
<li>
<?php \esc_html_e( 'ActivityPub account URL or ID', 'event-bridge-for-activitypub' ); ?> ( <?php esc_html_e( 'e.g.', 'event-bridge-for-activitypub' ); ?> <code>https://example.social/user/username</code>)
</li>
<li>
<?php \esc_html_e( 'The domain or URL of a Gancio instance', 'event-bridge-for-activitypub' ); ?> ( <?php esc_html_e( 'e.g.', 'event-bridge-for-activitypub' ); ?> <code>https://demo.gancio.org</code>)
</li>
</ul>
<div class="notice notice-info inline">
<p>
<?php
$table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources();
$table->prepare_items();
$table->search_box( 'Search', 'search' );
$table->display();
$number_of_imports = \Event_Bridge_For_ActivityPub\Outbox_Parser::MAX_EVENTS_TO_IMPORT;
$notice = sprintf(
/* translators: 1: The maximum number of imported events. */
__( 'To ensure a smooth start, up to %d upcoming events from this source will be automatically imported soon after adding it.', 'event-bridge-for-activitypub' ),
$number_of_imports
);
echo esc_html( $notice );
?>
</form>
</p>
</div>
<?php } ?>
</div>
<?php } ?>
<form method="post" action="options.php">
<?php \settings_fields( 'event-bridge-for-activitypub_add-event-source' ); ?>
<p>
<label id="event_bridge_for_activitypub_add_event_source_label" for="event_bridge_for_activitypub_add_event_source">
<?php \esc_html_e( 'Event Source (handle, URL, or instance)', 'event-bridge-for-activitypub' ); ?>:
</label>
</p>
<input
type="text"
style="width: 100%"
name="event_bridge_for_activitypub_add_event_source"
id="event_bridge_for_activitypub_add_event_source"
aria-describedby="event_bridge_for_activitypub_add_event_source_description"
aria-labelledby="event_bridge_for_activitypub_add_event_source_label"
placeholder="@username@example.social or https://example.social/user/username">
<?php \submit_button( __( 'Follow Event Source', 'event-bridge-for-activitypub' ) ); ?>
</form>
</div>
<div class="wrap activitypub-followers-page">
<!-- Table title with add new button like on post edit pages -->
<div class="event_bridge_for_activitypub-admin-table-top">
<h2 class="wp-heading-inline"> <?php esc_html_e( 'Manage Event Sources', 'event-bridge-for-activitypub' ); ?> </h2>
<!-- Button that triggers ThickBox -->
<a href="#TB_inline?width=600&height=400&inlineId=Event_Bridge_For_ActivityPub_add_new_source" class="thickbox page-title-action">
<?php \esc_html_e( 'Add Event Source', 'event-bridge-for-activitypub' ); ?>
</a>
</div>
<form method="get">
<input type="hidden" name="page" value="activitypub" />
<input type="hidden" name="tab" value="event-bridge-for-activitypub" />
<input type="hidden" name="subpage" value="event-sources" />
<?php
$table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources();
$table->prepare_items();
$table->search_box( 'Search', 'search' );
$table->display();
?>
</form>
</div>
<?php } ?>
</div>

View File

@ -12,7 +12,7 @@
*/
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Activity\Extended_Object\Event;
use Event_Bridge_For_ActivityPub\Setup;
@ -25,8 +25,6 @@ use Event_Bridge_For_ActivityPub\Setup;
)
);
$activitypub_plugin_is_active = Setup::get_instance()->is_activitypub_plugin_active();
if ( ! isset( $args ) || ! array_key_exists( 'event_terms', $args ) ) {
return;
}

View File

@ -8,7 +8,7 @@
*/
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Event_Bridge_For_ActivityPub\Setup;
use Event_Bridge_For_ActivityPub\Admin\General_Admin_Notices;
@ -24,7 +24,6 @@ use Event_Bridge_For_ActivityPub\Admin\Health_Check;
);
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
$activitypub_plugin_is_active = Setup::get_instance()->is_activitypub_plugin_active();
$event_bridge_for_activitypub_status_ok = true;
$example_event_post = Health_Check::get_most_recent_event_posts();
@ -46,10 +45,7 @@ WP_Filesystem();
<h2><?php \esc_html_e( 'Status', 'event-bridge-for-activitypub' ); ?></h2>
<p><?php \esc_html_e( 'The Event Bridge for ActivityPub detected the following (activated) event plugins:', 'event-bridge-for-activitypub' ); ?></p>
<?php
if ( ! $activitypub_plugin_is_active ) {
$notice = General_Admin_Notices::get_admin_notice_activitypub_plugin_not_enabled();
echo '<p>⚠' . \wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ) . '</p>';
} elseif ( empty( $active_event_plugins ) ) {
if ( empty( $active_event_plugins ) ) {
$notice = General_Admin_Notices::get_admin_notice_no_supported_event_plugin_active();
echo '<p>⚠' . \wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ) . '</p>';
}
@ -239,7 +235,7 @@ WP_Filesystem();
<div class="box">
<h2><?php \esc_html_e( 'Acknowledgement', 'event-bridge-for-activitypub' ); ?></h2>
<p><a href="https://NLnet.nl"><img src="<?php echo esc_url( plugins_url( '/assets/img/acknowledgement-NLnet.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="Logo NLnet: abstract logo of four people seen from above" class="logo-center"></a> <a href="https://NLnet.nl/NGI0"><img src="<?php echo esc_url( plugins_url( '/assets/img/acknowledgement-NGI0Entrust.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="Logo NGI Zero: letterlogo shaped like a tag" class="logo-center"> </a></p>
<p><a href="https://NLnet.nl"><img src="<?php echo esc_url( plugins_url( '/assets/img/acknowledgement-NLnet.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="" class="logo-center"></a> <a href="https://NLnet.nl/NGI0"><img src="<?php echo esc_url( plugins_url( '/assets/img/acknowledgement-NGI0Entrust.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="" class="logo-center"> </a></p>
<p>The development of this plugin was funded through the <a href="https://NLnet.nl/entrust">NGI0 Entrust</a> Fund, a fund established by <a href="https://nlnet.nl">NLnet</a> with financial support from the European Commission's <a href="https://ngi.eu">Next Generation Internet</a> programme, under the aegis of <a href="https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en">DG Communications Networks, Content and Technology</a> under grant agreement N<sup>o</sup> 101069594.</p>
</div>
<?php endif; ?>

View File

@ -8,6 +8,6 @@
*/
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\Event_Bridge_For_ActivityPub\Admin\Settings_Page::do_settings_page();