2
0
Fork 0
mirror of https://github.com/discourse/wp-discourse.git synced 2025-08-17 18:11:19 +08:00

Update and improve webhooks (#519)

* Update and improve webhooks

Changes:
- Add email matching as an identification basis for Update Userdata webhook.
- Standardise webhook data processing and responses.
- Add get_discourse_webhook_data utility method.
- Improve Webhook admin panel UX and descriptions.

Misc:
- Update phpcs usage

* Fix php 5.6 and 7.0 syntax checks

* Fix phpcs issues arising from update

* Remove ineffective nbsp from admin page
This commit is contained in:
Angus McLeod 2024-06-19 18:08:22 +02:00 committed by GitHub
parent 7c5e32b266
commit deacab2662
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 1437 additions and 1039 deletions

View file

@ -287,7 +287,6 @@ class CommentSettings {
</label>
</div>
<?php
}
/**

View file

@ -168,7 +168,7 @@ class DiscourseSidebar {
array(
array(
'methods' => \WP_REST_Server::READABLE,
'permission_callback' => function( $data ) {
'permission_callback' => function ( $data ) {
return $this->get_api_category_permissions( $data, 'get_categories_nonce' );
},
@ -183,7 +183,7 @@ class DiscourseSidebar {
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function( $data ) {
'permission_callback' => function ( $data ) {
return $this->get_api_post_permissions( $data, 'update_topic_nonce' );
},
'callback' => array( $this, 'update_topic' ),
@ -197,7 +197,7 @@ class DiscourseSidebar {
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function( $data ) {
'permission_callback' => function ( $data ) {
return $this->get_api_post_permissions( $data, 'publish_topic_nonce' );
},
'callback' => array( $this, 'publish_topic' ),
@ -211,7 +211,7 @@ class DiscourseSidebar {
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function( $data ) {
'permission_callback' => function ( $data ) {
return $this->get_api_post_permissions( $data, 'unlink_post_nonce' );
},
'callback' => array( $this, 'unlink_post' ),
@ -225,7 +225,7 @@ class DiscourseSidebar {
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function( $data ) {
'permission_callback' => function ( $data ) {
return $this->get_api_post_permissions( $data, 'link_topic_nonce' );
},
'callback' => array( $this, 'link_topic' ),
@ -239,7 +239,7 @@ class DiscourseSidebar {
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function( $data ) {
'permission_callback' => function ( $data ) {
return $this->get_api_post_permissions( $data, 'set_publish_meta_nonce' );
},
'callback' => array( $this, 'set_publish_meta' ),
@ -253,7 +253,7 @@ class DiscourseSidebar {
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function( $data ) {
'permission_callback' => function ( $data ) {
return $this->get_api_post_permissions( $data, 'set_category_meta_nonce' );
},
@ -268,7 +268,7 @@ class DiscourseSidebar {
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function( $data ) {
'permission_callback' => function ( $data ) {
return $this->get_api_post_permissions( $data, 'set_tag_meta_nonce' );
},
@ -283,7 +283,7 @@ class DiscourseSidebar {
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function( $data ) {
'permission_callback' => function ( $data ) {
return $this->get_api_post_permissions( $data, 'set_pin_meta_nonce' );
},

View file

@ -268,7 +268,6 @@ class FormHelper {
name='<?php echo esc_attr( $option ); ?>'><?php echo esc_textarea( $value ); ?></textarea>
<p class="description"><?php echo esc_html( $description ); ?></p>
<?php
}
/**

View file

@ -111,7 +111,7 @@ class NetworkOptions {
add_settings_field(
'discourse_webhook_match_old_topics',
__( 'Match Old Topics', 'wp-discourse' ),
__( 'Match Topics by Title', 'wp-discourse' ),
array(
$this,
'webhook_match_old_topics_checkbox',
@ -302,7 +302,7 @@ class NetworkOptions {
$this->input(
'api-key',
__( 'Found on your forum at ', 'wp-discourse' ) . '<a href="' . esc_url( $url ) .
'/admin/api/keys" target="_blank" rel="noreferrer noopener">' . esc_url( $url ) . '/admin/api/keys</a>. ' .
'/admin/api/keys" target="_blank" rel="noreferrer noopener">' . esc_url( $url ) . '/admin/api/keys</a>. ' .
"If you haven't yet created an API key, Click 'New API Key', set User Level to 'Single User', set 'User' to an admin account, select 'Global Key' and click 'Save'. Copy and paste the API key here.",
'wp-discourse'
);
@ -365,7 +365,7 @@ class NetworkOptions {
$discourse_url = $this->get_site_option( 'url' );
if ( ! empty( $discourse_url ) ) {
$discourse_webhooks_url = '<a href="' . esc_url( $discourse_url ) . '/admin/api/web_hooks" target="_blank" rel="noreferrer noopener">' .
esc_url( $discourse_url ) . '/admin/api/web_hooks</a>';
esc_url( $discourse_url ) . '/admin/api/web_hooks</a>';
} else {
$discourse_webhooks_url = 'http://forum.example.com/admin/api/web_hooks';
}
@ -373,8 +373,8 @@ class NetworkOptions {
$description = sprintf(
// translators: Discourse webhook description. Placeholder: discourse_webhook_url, webhook_payload_url.
__(
'Before enabling this setting, create a new webhook on your forum (found at %1$s.) In the webhook\'s Payload URL field, enter the
URL <code>%2$s</code>. Make sure that the \'Post Event\' and the \'Active\' checkboxes are enabled.',
'<strong>URL:</strong><code>%2$s</code>
<strong>Events:</strong> "Post is created", "Post is updated".',
'wp-discourse'
),
$discourse_webhooks_url,
@ -384,8 +384,7 @@ class NetworkOptions {
$this->checkbox_input(
'use-discourse-webhook',
__(
'Use a webhook
to sync comment data between Discourse and WordPress.',
'Enable the Sync Comment Data webhook endpoint.',
'wp-discourse'
),
$description
@ -399,15 +398,11 @@ class NetworkOptions {
$this->checkbox_input(
'webhook-match-old-topics',
__(
'Match WordPress posts
published prior to WP Discourse version 1.4.0.',
'Match WordPress posts with Discourse topics by title.',
'wp-discourse'
),
__(
"By default, posts
are matched to Discourse topics through their discourse_topic_id metadata. That value isn't available for posts
published through WP Discourse prior to version 1.4.0. Enabling this setting will match posts with the post_type
'post' to Discourse topics through their titles.",
'Sync Comment Data will attempt to match posts to topics by title if other methods of matching have not worked.',
'wp-discourse'
)
);
@ -421,7 +416,7 @@ class NetworkOptions {
$url = $this->get_site_option( 'url' );
if ( ! empty( $url ) ) {
$discourse_webhooks_url = '<a href="' . esc_url( $url ) . '/admin/api/web_hooks" target="_blank" rel="noreferrer noopener">' .
esc_url( $url ) . '/admin/api/web_hooks</a>';
esc_url( $url ) . '/admin/api/web_hooks</a>';
} else {
$discourse_webhooks_url = 'http://forum.example.com/admin/api/web_hooks';
}
@ -429,17 +424,15 @@ class NetworkOptions {
$description = sprintf(
// translators: Discourse webhook description. Placeholder: discourse_webhook_url, webhook_payload_url.
__(
'This webhook is only active when WordPress is enabled as the DiscourseConnect Provider for Discourse (this can be overridden by
hooking into the \'wpdc_use_discourse_user_webhook\' filter.) It supplies the Discourse username to WordPress when a user is created or updated on your forum.
Before enabling this setting, create a new webhook on your forum (found at %1$s.) In the webhook\'s Payload URL field, enter the
URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enabled.',
'<strong>URL:</strong><code>%2$s</code>
<strong>Events:</strong> "User is created", "User is Updated".',
'wp-discourse'
),
$discourse_webhooks_url,
$webhook_payload_url
);
$this->checkbox_input( 'use-discourse-user-webhook', __( 'Use a webhook to sync user data with Discourse.', 'wp-discourse' ), $description );
$this->checkbox_input( 'use-discourse-user-webhook', __( 'Enable the Update Userdata webhook endpoint.', 'wp-discourse' ), $description );
}
/**
@ -449,43 +442,24 @@ URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enable
$this->checkbox_input(
'webhook-match-user-email',
__(
'Match users with Discourse
through their email address.',
'Match WordPress users with Discourse users by email.',
'wp-discourse'
),
__(
'Used for syncing accounts that were created before enabling the
Update Userdata webhook. Existing accounts are synced when the user updates and saves their profile on Discourse.
<strong>Note: only enable this setting if you are certain that email addresses match between Discourse
and WordPress.</strong>',
'Update Userdata will attempt to match users by email if other methods of matching have not worked.',
'wp-discourse'
)
);
}
/**
* Outputs markup for webhook-secret input.
*/
public function webhook_secret_input() {
$url = $this->get_site_option( 'url' );
if ( ! empty( $url ) ) {
$discourse_webhooks_url = '<a href="' . esc_url( $url ) . '/admin/api/web_hooks" target="_blank" rel="noreferrer noopener">' .
esc_url( $url ) . '/admin/api/web_hooks</a>';
} else {
$discourse_webhooks_url = 'http://forum.example.com/admin/api/web_hooks';
}
$description = sprintf(
// translators: Webhook secret input. Placeholder: discourse_webhooks_url.
__(
'The secret key used to verify Discourse webhook requests. Set it to a string of text, at least 12
characters long. It needs to match the key set at %1$s.',
'wp-discourse'
),
$discourse_webhooks_url
);
$description = __(
'String of text at least 12 characters long.',
'wp-discourse'
);
$this->input( 'webhook-secret', $description );
}
@ -517,13 +491,13 @@ URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enable
$this->checkbox_input(
'hide-discourse-name-field',
__(
'Removes the Discourse Name field
'Removes the Discourse Username field
from the WordPress user profile page.',
'wp-discourse'
),
__(
'If you enable this setting and also enable the Update
Userdata webhook, new users created on Discourse will have the their Discourse Name automatically filled in and be
Userdata webhook, new users created on Discourse will have the their Discourse Username automatically filled in and be
uneditable on WordPress.',
'wp-discourse'
)
@ -610,7 +584,7 @@ URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enable
$url = $this->get_site_option( 'url' );
if ( ! empty( $url ) ) {
$discourse_sso_url = '<a href="' . esc_url( $url ) . '/admin/site_settings/category/all_results?filter=sso" target="_blank" rel="noreferrer noopener">' .
esc_url( $url ) . '/admin/site_settings/category/all_results?filter=sso</a>';
esc_url( $url ) . '/admin/site_settings/category/all_results?filter=sso</a>';
} else {
$discourse_sso_url = 'http://forum.example.com/admin/site_settings/category/all_results?filter=sso';
}
@ -664,7 +638,7 @@ URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enable
</h2>
<form class="wp-discourse-network-options-form" action="<?php echo esc_url( $action_url ); ?>"
method="post">
method="post">
<?php wp_nonce_field( 'update_discourse_network_options', 'update_discourse_network_options_nonce' ); ?>
<?php
settings_fields( 'discourse_network_options' );
@ -683,7 +657,7 @@ URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enable
*/
public function save_network_settings() {
if ( ! isset( $_POST['update_discourse_network_options_nonce'] ) || // Input var okay.
! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['update_discourse_network_options_nonce'] ) ), 'update_discourse_network_options' ) // Input var okay.
! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['update_discourse_network_options_nonce'] ) ), 'update_discourse_network_options' ) // Input var okay.
) {
return null;
@ -855,7 +829,7 @@ URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enable
}
if ( ( ! empty( $use_discourse_webhook ) && empty( $webhook_secret ) ) ||
( ! empty( $use_discourse_user_webhook ) && empty( $webhook_secret ) )
( ! empty( $use_discourse_user_webhook ) && empty( $webhook_secret ) )
) {
$notices .= '<div class="notice notice-error is-dismissible"><p>' .
__( 'To use Discourse webhooks, you need to supply a webhook secret key.', 'wp-discourse' ) .
@ -942,8 +916,8 @@ URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enable
?>
<input id='discourse-<?php echo esc_attr( $option ); ?>'
name='<?php echo 'wpdc_site_options[' . esc_attr( $option ) . ']'; ?>'
type="<?php echo isset( $type ) ? esc_attr( $type ) : 'text'; ?>"
name='<?php echo 'wpdc_site_options[' . esc_attr( $option ) . ']'; ?>'
type="<?php echo isset( $type ) ? esc_attr( $type ) : 'text'; ?>"
<?php
if ( isset( $min ) ) {
echo 'min="' . esc_attr( $min ) . '"';
@ -954,7 +928,7 @@ URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enable
echo 'max="' . esc_attr( $max ) . '"';
}
?>
value='<?php echo esc_attr( $value ); ?>' class="regular-text ltr"/>
value='<?php echo esc_attr( $value ); ?>' class="regular-text ltr"/>
<p class="description"><?php echo wp_kses( $description, $allowed ); ?></p>
<?php
}
@ -982,12 +956,12 @@ URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enable
?>
<label>
<input name='<?php echo 'wpdc_site_options[' . esc_attr( $option ) . ']'; ?>'
type='hidden'
value='0'/>
type='hidden'
value='0'/>
<input id='discourse-<?php echo esc_attr( $option ); ?>'
name='<?php echo 'wpdc_site_options[' . esc_attr( $option ) . ']'; ?>'
type='checkbox'
value='1' <?php echo esc_attr( $checked ); ?> />
name='<?php echo 'wpdc_site_options[' . esc_attr( $option ) . ']'; ?>'
type='checkbox'
value='1' <?php echo esc_attr( $checked ); ?> />
<?php echo wp_kses( $label, $allowed ); ?>
</label>
<p class="description"><?php echo wp_kses( $description, $allowed ); ?></p>

View file

@ -13,6 +13,7 @@ use WPDiscourse\Shared\PluginUtilities;
*/
class UserProfile {
use PluginUtilities;
/**
* Gives access to the plugin options.
*

View file

@ -81,7 +81,7 @@ class WebhookSettings {
add_settings_field(
'discourse_webhook_match_old_topics',
__( 'Match Old Topics', 'wp-discourse' ),
__( 'Match Posts by Title', 'wp-discourse' ),
array(
$this,
'webhook_match_old_topics_checkbox',
@ -103,7 +103,7 @@ class WebhookSettings {
add_settings_field(
'discourse_webhook_match_user_email',
__( 'Match Users by Email Address', 'wp-discourse' ),
__( 'Match Users by Email', 'wp-discourse' ),
array(
$this,
'webhook_match_user_email_checkbox',
@ -155,7 +155,7 @@ class WebhookSettings {
if ( ! empty( $this->options['url'] ) ) {
$discourse_webhooks_url = '<a href="' . esc_url( $this->options['url'] ) . '/admin/api/web_hooks" target="_blank" rel="noreferrer noopener">' .
esc_url( $this->options['url'] ) . '/admin/api/web_hooks</a>';
esc_url( $this->options['url'] ) . '/admin/api/web_hooks</a>';
} else {
$discourse_webhooks_url = 'http://forum.example.com/admin/api/web_hooks';
}
@ -163,8 +163,8 @@ class WebhookSettings {
$description = sprintf(
// translators: Discourse webhook description. Placeholder: discourse_webhook_url, webhook_payload_url.
__(
'Before enabling this setting, create a new webhook on your forum (found at %1$s.) In the webhook\'s Payload URL field, enter the
URL <code class="wpdc-select-all">%2$s</code>. Make sure that the \'Post Event\' and the \'Active\' checkboxes are enabled.',
'<strong>URL:</strong><code>%2$s</code>
<strong>Events:</strong> "Post is created", "Post is updated".',
'wp-discourse'
),
$discourse_webhooks_url,
@ -175,8 +175,7 @@ class WebhookSettings {
'use-discourse-webhook',
'discourse_webhook',
__(
'Use a webhook
to sync comment data between Discourse and WordPress.',
'Enable the Sync Comment Data webhook endpoint.',
'wp-discourse'
),
$description
@ -191,15 +190,11 @@ class WebhookSettings {
'webhook-match-old-topics',
'discourse_webhook',
__(
'Match WordPress posts
published prior to WP Discourse version 1.4.0.',
'Match WordPress posts with Discourse topics by title.',
'wp-discourse'
),
__(
"By default, posts
are matched to Discourse topics through their discourse_topic_id metadata. That value isn't available for posts
published through WP Discourse prior to version 1.4.0. Enabling this setting will match posts with the post_type
'post' to Discourse topics through their titles.",
'Sync Comment Data will attempt to match posts to topics by title if other methods of matching have not worked.',
'wp-discourse'
)
);
@ -212,7 +207,7 @@ class WebhookSettings {
$webhook_payload_url = home_url( '/wp-json/wp-discourse/v1/update-user' );
if ( ! empty( $this->options['url'] ) ) {
$discourse_webhooks_url = '<a href="' . esc_url( $this->options['url'] ) . '/admin/api/web_hooks" target="_blank" rel="noreferrer noopener">' .
esc_url( $this->options['url'] ) . '/admin/api/web_hooks</a>';
esc_url( $this->options['url'] ) . '/admin/api/web_hooks</a>';
} else {
$discourse_webhooks_url = 'http://forum.example.com/admin/api/web_hooks';
}
@ -220,10 +215,8 @@ class WebhookSettings {
$description = sprintf(
// translators: Discourse webhook description. Placeholder: discourse_webhook_url, webhook_payload_url.
__(
'Used to automatically fill in the WordPress user\'s Discourse Name field and store their Discourse Id as metadata.
This setting will only be activated if your site is functioning as the DiscourseConnect provider for Discourse (this can be overridden by hooking into the
\'wpdc_use_discourse_user_webhook\' filter.) Before enabling this setting, create a new webhook on your forum (found at %1$s.) In the webhook\'s Payload URL field, enter the
URL <code>%2$s</code>. Make sure that only the \'User Event\' checkbox is enabled.',
'<strong>URL:</strong><code>%2$s</code>
<strong>Events:</strong> "User is created", "User is Updated".',
'wp-discourse'
),
$discourse_webhooks_url,
@ -234,13 +227,11 @@ This setting will only be activated if your site is functioning as the Discourse
'use-discourse-user-webhook',
'discourse_webhook',
__(
'Use a webhook
to sync user data with Discourse.',
'Enable the Update Userdata webhook endpoint.',
'wp-discourse'
),
$description
);
}
/**
@ -251,15 +242,11 @@ This setting will only be activated if your site is functioning as the Discourse
'webhook-match-user-email',
'discourse_webhook',
__(
'Match users with Discourse
through their email address.',
'Match WordPress users with Discourse users by email.',
'wp-discourse'
),
__(
'Used for syncing accounts that were created before enabling the
Update Userdata webhook. Existing accounts are synced when the user updates and saves their profile on Discourse.
<strong>Note: only enable this setting if you are certain that email addresses match between Discourse
and WordPress.</strong>',
'Update Userdata will attempt to match users by email if other methods of matching have not worked.',
'wp-discourse'
)
);
@ -269,23 +256,10 @@ This setting will only be activated if your site is functioning as the Discourse
* Outputs markup for webhook-secret input.
*/
public function webhook_secret_input() {
if ( ! empty( $this->options['url'] ) ) {
$discourse_webhooks_url = '<a href="' . esc_url( $this->options['url'] ) . '/admin/api/web_hooks" target="_blank" rel="noreferrer noopener">' .
esc_url( $this->options['url'] ) . '/admin/api/web_hooks</a>';
} else {
$discourse_webhooks_url = 'http://forum.example.com/admin/api/web_hooks';
}
$description = sprintf(
// translators: Webhook secret input. Placeholder: discourse_webhooks_url.
__(
'The secret key used to verify Discourse webhook requests. Set it to a string of text, at least 12
characters long. It needs to match the key set at %1$s.',
'wp-discourse'
),
$discourse_webhooks_url
);
$description = __(
'String of text at least 12 characters long.',
'wp-discourse'
);
$this->form_helper->input( 'webhook-secret', 'discourse_webhook', $description );
}
@ -308,20 +282,13 @@ This setting will only be activated if your site is functioning as the Discourse
* Details for the 'webhook_options' tab.
*/
public function webhook_settings_tab_details() {
$setup_howto_url = 'https://meta.discourse.org/t/wp-discourse-plugin-installation-and-setup/50752';
$discourse_meta_url = 'https://meta.discourse.org/';
$setup_howto_url = 'https://meta.discourse.org/t/311552';
?>
<p class="wpdc-options-documentation">
<p class="wpdc-options-documentation">
<em>
<?php
esc_html_e(
"Webhooks can be used to sync data between Discourse and WordPress. Their use is optional, but they're easy to
setup. The WP Discourse plugin has two webhook endpoints, Sync Comment Data and Update Userdata. The
Sync Comment Data webhook is used to let the plugin know when a Discourse topic has had a new post added to it.
Using it will reduce the number of API requests made between WordPress and your forum. The Update Userdata
webhook will only be functional when WordPress is used as the DiscourseConnect Provider for Discourse. If enabled, it
automatically fills in the user's WordPress name field when a new account is created or updated on Discourse
through DiscourseConnect.",
'This section is for configuring Discourse webhooks.',
'wp-discourse'
);
?>
@ -330,25 +297,9 @@ This setting will only be activated if your site is functioning as the Discourse
<?php if ( $this->display_webhook_options ) : ?>
<p class="wpdc-options-documentation">
<em>
<?php
esc_html_e(
"There are some issues with syncing posts that were published from WordPress to
Discourse before WP Discourse version 1.4.0. Old posts can be synced with their corresponding Discourse
topic if they are using the post type 'post' and the title of the post matches the title of the Discourse
topic. To enable this functionality, select the 'Match Old Topics' option.",
'wp-discourse'
);
?>
</em>
</p>
<p class="wpdc-options-documentation">
<em>
<?php esc_html_e( 'For detailed instructions on setting up webhooks, see the ', 'wp-discourse' ); ?>
<?php esc_html_e( 'For instructions on setting up webhooks see ', 'wp-discourse' ); ?>
<a href="<?php echo esc_url( $setup_howto_url ); ?>"
target="_blank" rel="noreferrer noopener"><?php esc_html_e( 'WP Discourse plugin installation and setup', 'wp-discourse' ); ?></a>
<?php esc_html_e( 'topic on the ', 'wp-discourse' ); ?>
<a href="<?php echo esc_url( $discourse_meta_url ); ?>" target="_blank" rel="noreferrer noopener">Discourse Meta</a>
<?php esc_html_e( 'forum.', 'wp-discourse' ); ?>
target="_blank" rel="noreferrer noopener"><?php esc_html_e( 'Configure WP Discourse webhooks.', 'wp-discourse' ); ?></a>
</em>
</p>
<?php else : ?>

View file

@ -5,8 +5,8 @@
* @package WPDiscourse
*/
use \WPDiscourse\DiscourseCommentFormatter\DiscourseCommentFormatter;
use \WPDiscourse\DiscourseComment\DiscourseComment;
use WPDiscourse\DiscourseCommentFormatter\DiscourseCommentFormatter;
use WPDiscourse\DiscourseComment\DiscourseComment;
/**
* Renders the `wp-discourse/comments` block on the server.

View file

@ -31,14 +31,13 @@
"10up/wp_mock": "0.4.2",
"phpunit/phpunit": "9.6",
"phpunit/php-code-coverage": "^9.2.13",
"phpcompatibility/php-compatibility": "^9.3.5",
"wp-coding-standards/wpcs": "^2.3",
"phpcompatibility/php-compatibility": "*",
"wp-coding-standards/wpcs": "^3.0",
"monolog/monolog": "^1.25",
"yoast/phpunit-polyfills": "^1.0.1",
"dms/phpunit-arraysubset-asserts": "^0.5.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"classmap": [
"vendor_namespaced/"

619
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,8 +9,6 @@ These formatters will be applied on each pull request in Github Actions (via ``.
#### PHPCS
> Note: Use -d error_reporting=E_ALL^E_DEPRECATED until https://github.com/PHPCompatibility/PHPCompatibility/issues/1651 is resolved
The ``phpcs`` configuration is handled in the ``.phpcs.xml`` file, a type of [Annotated Ruleset](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-Ruleset). Install the development composer packages by running ``composer install`` prior to using ``phpcs``, and run it using ``vendor/bin/phpcs``, for example
```
@ -28,7 +26,7 @@ All errors must be addressed, and all warnings should be addressed as far as pos
The ``.github/workflows/ci.yml`` applies a syntax check for each supported version of PHP by searching for all ``.php`` files in the repository, running the relevant version of the PHP interpreter, and catching syntax errors via ``xargs``:
```
find -L . -name '*.php' -not -path "./vendor/*" -print0 | xargs -0 -n 1 -P 4 php -l
find -L . -name '*.php' -not -path "./vendor/*" -not -path "./tests/*" -print0 | xargs -0 -n 1 -P 4 php -l
```
To perform this locally:

View file

@ -77,7 +77,7 @@ class DiscourseComment extends DiscourseBase {
array(
array(
'methods' => \WP_REST_Server::READABLE,
'permission_callback' => function() {
'permission_callback' => function () {
return true;
},
'callback' => array( $this, 'get_discourse_comments' ),

View file

@ -209,7 +209,7 @@ class Discourse {
* Initializes the plugin configuration, loads the text domain etc.
*/
public function initialize_plugin() {
load_plugin_textdomain( 'wp-discourse', false, basename( dirname( __FILE__ ) ) . '/languages' );
load_plugin_textdomain( 'wp-discourse', false, basename( __DIR__ ) . '/languages' );
$this->options = $this->get_options();
// Set the Discourse domain name option.

View file

@ -8,7 +8,7 @@
namespace WPDiscourse\Logs;
use \WPDiscourse\Monolog\Handler\StreamHandler;
use WPDiscourse\Monolog\Handler\StreamHandler;
/**
* Class FileHandler
@ -189,7 +189,7 @@ class FileHandler extends StreamHandler {
$b_date = $this->get_date_from_url( $b );
if ( $a_date > $b_date ) {
return -1;
return -1;
}
if ( $a_date < $b_date ) {
@ -200,7 +200,7 @@ class FileHandler extends StreamHandler {
$b_number = $this->get_number_from_url( $b );
if ( $a_number > $b_number ) {
return -1;
return -1;
}
if ( $a_number < $b_number ) {
@ -252,17 +252,17 @@ class FileHandler extends StreamHandler {
foreach ( array_slice( $files, ( $this->max_files - 1 ) ) as $file ) {
if ( is_writable( $file ) ) {
// Note from monolog/monolog:
// "suppress errors here as unlink() might fail if two processes
// are cleaning up/rotating at the same time.".
// "suppress errors here as unlink() might fail if two processes
// are cleaning up/rotating at the same time.".
// phpcs:disable WordPress.PHP.DevelopmentFunctions
set_error_handler(
function () {
set_error_handler(
function () {
return false;
}
);
unlink( $file );
restore_error_handler();
// phpcs:enabled WordPress.PHP.DevelopmentFunctions
}
);
unlink( $file );
restore_error_handler();
// phpcs:enabled WordPress.PHP.DevelopmentFunctions
}
}
}

View file

@ -12,10 +12,10 @@ require_once __DIR__ . '/handlers/file-handler.php';
require_once __DIR__ . '/handlers/null-handler.php';
require_once __DIR__ . '/formatters/line-formatter.php';
use \WPDiscourse\Logs\NullHandler;
use \WPDiscourse\Logs\FileHandler;
use \WPDiscourse\Logs\FileManager;
use \WPDiscourse\Logs\LineFormatter;
use WPDiscourse\Logs\NullHandler;
use WPDiscourse\Logs\FileHandler;
use WPDiscourse\Logs\FileManager;
use WPDiscourse\Logs\LineFormatter;
/**
* Class Logger.

View file

@ -214,7 +214,7 @@ trait PluginUtilities {
}
$wordpress_scopes = array_filter(
$scopes, function( $scope ) {
$scopes, function ( $scope ) {
return 'wordpress' === $scope->resource; // phpcs:ignore WordPress.WP.CapitalPDangit
}
);
@ -642,40 +642,6 @@ trait PluginUtilities {
return apply_filters( 'wpdc_sso_params', $params, $user );
}
/**
* Verify that the request originated from a Discourse webhook and the the secret keys match.
*
* @param \WP_REST_Request $data The WP_REST_Request object.
*
* @return \WP_Error|\WP_REST_Request
*/
public function verify_discourse_webhook_request( $data ) {
$options = isset( $this->options ) ? $this->options : $this->get_options();
// The X-Discourse-Event-Signature consists of 'sha256=' . hamc of raw payload.
// It is generated by computing `hash_hmac( 'sha256', $payload, $secret )`.
$sig = substr( $data->get_header( 'X-Discourse-Event-Signature' ), 7 );
if ( $sig ) {
$payload = $data->get_body();
// Key used for verifying the request - a matching key needs to be set on the Discourse webhook.
$secret = ! empty( $options['webhook-secret'] ) ? $options['webhook-secret'] : '';
if ( ! $secret ) {
return new \WP_Error( 'discourse_webhook_configuration_error', 'The webhook secret key has not been set.' );
}
if ( hash_hmac( 'sha256', $payload, $secret ) === $sig ) {
return $data;
} else {
return new \WP_Error( 'discourse_webhook_authentication_error', 'Discourse Webhook Request Error: signatures did not match.' );
}
}
return new \WP_Error( 'discourse_webhook_authentication_error', 'Discourse Webhook Request Error: the X-Discourse-Event-Signature was not set for the request.' );
}
/**
* Saves the topic_id/blog_id to the wpdc_topic_blog table.
*

View file

@ -8,7 +8,7 @@
namespace WPDiscourse\DiscourseSSO;
use WPDiscourse\DiscourseBase;
use \WPDiscourse\SSO\SSO;
use WPDiscourse\SSO\SSO;
/**
* Class DiscourseSSO
@ -94,7 +94,6 @@ class DiscourseSSO extends DiscourseBase {
} else {
return esc_url_raw( $login_url );
}
}
/**

View file

@ -11,11 +11,13 @@
namespace WPDiscourse\SyncDiscourseTopic;
use WPDiscourse\DiscourseBase;
use WPDiscourse\Shared\WebhookUtilities;
/**
* Class SyncDiscourseTopic
*/
class SyncDiscourseTopic extends DiscourseBase {
use WebhookUtilities;
/**
* The current version of the wpdc_topic_blog database table.
@ -41,20 +43,25 @@ class SyncDiscourseTopic extends DiscourseBase {
add_action( 'init', array( $this, 'setup_logger' ) );
add_action( 'rest_api_init', array( $this, 'initialize_update_content_route' ) );
add_action( 'plugins_loaded', array( $this, 'maybe_create_db' ) );
$this->supported_events = array(
'post_created',
'post_updated',
);
}
/**
* Registers the Rest API route wp-discourse/v1/update-topic-content.
*/
public function initialize_update_content_route() {
if ( ! empty( $this->options['use-discourse-webhook'] ) ) {
if ( $this->webhook_enabled() ) {
register_rest_route(
'wp-discourse/v1',
'update-topic-content',
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function() {
'permission_callback' => function () {
return true;
},
'callback' => array( $this, 'update_topic_content' ),
@ -67,28 +74,24 @@ class SyncDiscourseTopic extends DiscourseBase {
/**
* Handles the REST request.
*
* @param \WP_REST_Request $data The WP_REST_Request data object.
* @param \WP_REST_Request $request The WP_REST_Request data object.
*
* @return null|\WP_Error
*/
public function update_topic_content( $data ) {
// This function call is used to verify the request. For clarity, the permission callback should be updated to call this function.
$data = $this->verify_discourse_webhook_request( $data );
public function update_topic_content( $request ) {
$data = $this->get_webhook_data( $request );
if ( is_wp_error( $data ) ) {
$this->logger->error( 'update_topic_content.webhook_verification_error', array( 'message', $data->get_error_message() ) );
return $data;
}
if ( is_wp_error( $data ) ) {
return $this->failed_response( $data->get_error_message() );
}
$json = $data->get_json_params();
do_action( 'wpdc_before_webhook_post_update', $json );
do_action( 'wpdc_before_webhook_post_update', $data->json );
if ( ! is_wp_error( $json ) && ! empty( $json['post'] ) ) {
$post_data = $json['post'];
$use_multisite_configuration = is_multisite() && ! empty( $this->options['multisite-configuration-enabled'] );
$post_ids = array();
$post_data = $data->json['post'];
$use_multisite_configuration = is_multisite() && ! empty( $this->options['multisite-configuration-enabled'] );
$post_ids = array();
if ( $use_multisite_configuration ) {
if ( $use_multisite_configuration ) {
global $wpdb;
$table_name = $wpdb->base_prefix . 'wpdc_topic_blog';
$topic_id = intval( $post_data['topic_id'] );
@ -96,20 +99,21 @@ class SyncDiscourseTopic extends DiscourseBase {
$blog_id = $wpdb->get_var( $wpdb->prepare( $query, $topic_id ) );
if ( $blog_id ) {
switch_to_blog( $blog_id );
$post_ids = $this->update_post_metadata( $post_data );
restore_current_blog();
}
} else {
switch_to_blog( $blog_id );
$post_ids = $this->update_post_metadata( $post_data );
restore_current_blog();
}
} else {
$post_ids = $this->update_post_metadata( $post_data );
}
}
do_action( 'wpdc_after_webhook_post_update', $post_ids );
} else {
$this->logger->error( 'update_topic_content.response_body_error' );
}
do_action( 'wpdc_after_webhook_post_update', $post_ids );
return null;
if ( ! empty( $post_ids ) ) {
return $this->success_response( 'The posts have been updated.' );
} else {
return $this->failed_response( 'No posts were found.' );
}
}
/**
@ -294,4 +298,11 @@ class SyncDiscourseTopic extends DiscourseBase {
return null;
}
/**
* Common webhook enabled function.
*/
protected function webhook_enabled() {
return ! empty( $this->options['use-discourse-webhook'] );
}
}

View file

@ -8,11 +8,14 @@
namespace WPDiscourse\SyncDiscourseUser;
use WPDiscourse\DiscourseBase;
use WPDiscourse\Shared\WebhookUtilities;
/**
* Class SyncDiscourseUser
*/
class SyncDiscourseUser extends DiscourseBase {
use WebhookUtilities;
/**
* Logger context
*
@ -28,22 +31,25 @@ class SyncDiscourseUser extends DiscourseBase {
add_action( 'init', array( $this, 'setup_options' ) );
add_action( 'init', array( $this, 'setup_logger' ) );
add_action( 'rest_api_init', array( $this, 'initialize_update_user_route' ) );
$this->supported_events = array(
'user_created',
'user_updated',
);
}
/**
* Registers the Rest API route wp-discourse/v1/update-user.
*/
public function initialize_update_user_route() {
$initialize_route = ! empty( $this->options['enable-sso'] ) && ! empty( $this->options['use-discourse-user-webhook'] );
$initialize_route = apply_filters( 'wpdc_use_discourse_user_webhook', $initialize_route );
if ( $initialize_route ) {
if ( $this->webhook_enabled() ) {
register_rest_route(
'wp-discourse/v1',
'update-user',
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'permission_callback' => function() {
'permission_callback' => function () {
return true;
},
'callback' => array( $this, 'update_user' ),
@ -56,90 +62,81 @@ class SyncDiscourseUser extends DiscourseBase {
/**
* Update WordPress user metadata from a Discourse webhook.
*
* @param \WP_REST_Request $data The WP_REST_Request object.
* @param \WP_REST_Request $request The WP_REST_Request object.
*
* @return null|\WP_Error
*/
public function update_user( $data ) {
$use_webhook_sync = ! empty( $this->options['use-discourse-user-webhook'] ) && ( ! empty( $this->options['enable-sso'] ) );
$use_webhook_sync = apply_filters( 'wpdc_use_discourse_user_webhook', $use_webhook_sync );
public function update_user( $request ) {
$data = $this->get_webhook_data( $request );
if ( ! $use_webhook_sync ) {
if ( is_wp_error( $data ) ) {
return $this->failed_response( $data->get_error_message() );
}
return new \WP_Error( 'discourse_webhook_error', __( 'The Discourse User webhook is not enabled for your site.' ) );
}
$discourse_user = $data->json['user'];
$discourse_email = sanitize_email( $discourse_user['email'] );
$discourse_id = intval( $discourse_user['id'] );
$external_id = ! empty( $discourse_user['external_id'] ) ? intval( $discourse_user['external_id'] ) : null;
$wordpress_user = null;
// This function call is used to verify the request. For clarity, the permission callback should be updated to call this function.
$data = $this->verify_discourse_webhook_request( $data );
if ( is_wp_error( $data ) ) {
$this->logger->error( 'update_user.webhook_verification_error', array( 'message', $data->get_error_message() ) );
return $data;
}
if ( 'user_created' === $data->event ) {
do_action( 'wpdc_webhook_user_created', $discourse_user );
$event_type = $data->get_header( 'x_discourse_event_type' );
$event_action = $data->get_header( 'x_discourse_event' );
$json = $data->get_json_params();
if ( ! is_wp_error( $json ) && ! empty( $json['user'] ) ) {
$discourse_user = $json['user'];
$discourse_email = sanitize_email( $discourse_user['email'] );
$discourse_id = intval( $discourse_user['id'] );
$external_id = ! empty( $discourse_user['external_id'] ) ? intval( $discourse_user['external_id'] ) : null;
$wordpress_user = null;
if ( 'user_created' === $event_action ) {
do_action( 'wpdc_webhook_user_created', $discourse_user );
if ( $external_id ) {
$wordpress_user = get_user_by( 'id', $external_id );
} else {
// It's safe to find the user by email when they are first created through SSO.
$wordpress_user = get_user_by( 'email', $discourse_email );
if ( $external_id ) {
$wordpress_user = get_user_by( 'id', $external_id );
}
} elseif ( 'user' === $event_type ) {
do_action( 'wpdc_webhook_user_updated', $discourse_user );
if ( $external_id ) {
$wordpress_user = get_user_by( 'id', $external_id );
} else {
$user_query = new \WP_User_Query(
array(
'meta_key' => 'discourse_sso_user_id',
'meta_value' => $discourse_id,
'number' => 1,
)
);
if ( ! $wordpress_user && ! empty( $discourse_email ) ) {
$wordpress_user = get_user_by( 'email', $discourse_email );
}
}
$user_query_results = $user_query->get_results();
if ( 'user_updated' === $data->event ) {
do_action( 'wpdc_webhook_user_updated', $discourse_user );
// For updating users created prior to version 1.4.0.
if ( empty( $user_query_results ) && ! empty( $this->options['webhook-match-user-email'] ) ) {
$wordpress_user = get_user_by( 'email', $discourse_email );
} elseif ( ! is_wp_error( $user_query_results ) ) {
$wordpress_user = $user_query_results[0];
if ( $external_id ) {
$wordpress_user = get_user_by( 'id', $external_id );
}
if ( ! $wordpress_user && ! empty( $discourse_id ) ) {
$user_query = new \WP_User_Query(
array(
'meta_key' => 'discourse_sso_user_id',
'meta_value' => $discourse_id,
'number' => 1,
)
);
$user_query_results = $user_query->get_results();
if ( ! empty( $user_query_results ) && ! is_wp_error( $user_query_results ) ) {
$wordpress_user = $user_query_results[0];
}
}
}
}
if ( $wordpress_user && ! is_wp_error( $wordpress_user ) ) {
do_action( 'wpdc_webhook_before_update_user_data', $wordpress_user, $discourse_user, $event_type );
if ( ! $wordpress_user && ! empty( $this->options['webhook-match-user-email'] ) && ! empty( $discourse_email ) ) {
$wordpress_user = get_user_by( 'email', $discourse_email );
}
}
if ( $wordpress_user && ! is_wp_error( $wordpress_user ) ) {
do_action( 'wpdc_webhook_before_update_user_data', $wordpress_user, $discourse_user, $data->event_type );
$user_id = $wordpress_user->ID;
$this->update_user_data( $user_id, $discourse_user );
} else {
return $this->success_response( 'User updated.' );
} else {
$log_args = array(
'event_type' => $event_type,
'event_action' => $event_action,
'event_type' => $data->event_type,
'event' => $data->event,
'discourse_id' => $discourse_id,
'external_id' => $external_id,
);
$this->logger->warn( 'update_user.user_not_found', $log_args );
}
} else {
$this->logger->error( 'update_user.response_body_error' );
}
return null;
return $this->failed_response( 'User not found.' );
}
}
/**
@ -164,4 +161,15 @@ class SyncDiscourseUser extends DiscourseBase {
$this->logger->info( 'update_user.update_user_data_success', $log_args );
}
}
/**
* Common Webhook enabled function.
*/
protected function webhook_enabled() {
$enabled = ! empty( $this->options['use-discourse-user-webhook'] ) && (
! empty( $this->options['enable-sso'] ) || ! empty( $this->options['webhook-match-user-email'] )
);
return apply_filters( 'wpdc_use_discourse_user_webhook', $enabled );
}
}

View file

@ -141,7 +141,7 @@ trait TemplateFunctions {
$poll->parentNode->removeChild( $poll );
}
$poll_number ++;
$poll_number++;
}
$parsed = $doc->saveHTML( $doc->documentElement );

View file

@ -0,0 +1,35 @@
<?php
/**
* Public utility functions.
*
* @package WPDiscourse
*/
namespace WPDiscourse\Utilities;
use WPDiscourse\Shared\PluginUtilities;
use WPDiscourse\Shared\WebhookUtilities;
/**
* Class PublicUtilities
*
* @package WPDiscourse
*/
class PublicUtilities {
use PluginUtilities {
get_options as public;
validate as public;
get_discourse_categories as public;
get_discourse_user as public;
get_discourse_user_by_email as public;
sync_sso as public;
discourse_request as public;
get_api_credentials as public;
get_sso_params as public;
}
use WebhookUtilities {
get_discourse_webhook_data as public;
verify_discourse_webhook_request as public;
}
}

View file

@ -7,27 +7,7 @@
namespace WPDiscourse\Utilities;
use WPDiscourse\Shared\PluginUtilities;
/**
* Class PublicPluginUtilities
*
* @package WPDiscourse
*/
class PublicPluginUtilities {
use PluginUtilities {
get_options as public;
validate as public;
get_discourse_categories as public;
get_discourse_user as public;
get_discourse_user_by_email as public;
sync_sso as public;
discourse_request as public;
get_api_credentials as public;
get_sso_params as public;
verify_discourse_webhook_request as public;
}
}
use WPDiscourse\Utilities\PublicUtilities;
/**
* Class Utilities
@ -41,7 +21,7 @@ class Utilities {
* @return array
*/
public static function get_options() {
$utils = new PublicPluginUtilities();
$utils = new PublicUtilities();
return $utils->get_options();
}
@ -53,7 +33,7 @@ class Utilities {
* @return int
*/
public static function validate( $response ) {
$utils = new PublicPluginUtilities();
$utils = new PublicUtilities();
return $utils->validate( $response );
}
@ -63,7 +43,7 @@ class Utilities {
* @return \WP_Error|array
*/
public static function get_discourse_categories() {
$utils = new PublicPluginUtilities();
$utils = new PublicUtilities();
return $utils->get_discourse_categories();
}
@ -76,7 +56,7 @@ class Utilities {
* @return int|string|\WP_Error
*/
public static function sync_sso_record( $sso_params, $user_id = null ) {
$utils = new PublicPluginUtilities();
$utils = new PublicUtilities();
return $utils->sync_sso( $sso_params, $user_id );
}
@ -89,7 +69,7 @@ class Utilities {
* @return array|mixed|object|\WP_Error
*/
public static function get_discourse_user( $user_id, $match_by_email = false ) {
$utils = new PublicPluginUtilities();
$utils = new PublicUtilities();
return $utils->get_discourse_user( $user_id, $match_by_email );
}
@ -101,7 +81,7 @@ class Utilities {
* @return object \WP_Error
*/
public static function get_discourse_user_by_email( $email ) {
$utils = new PublicPluginUtilities();
$utils = new PublicUtilities();
return $utils->get_discourse_user_by_email( $email );
}
@ -114,7 +94,7 @@ class Utilities {
* @return array|\WP_Error|void
*/
public static function discourse_request( $path, $args = array() ) {
$utils = new PublicPluginUtilities();
$utils = new PublicUtilities();
return $utils->discourse_request( $path, $args );
}
@ -127,20 +107,34 @@ class Utilities {
* @return array
*/
public static function get_sso_params( $user, $sso_options = array() ) {
$utils = new PublicPluginUtilities();
$utils = new PublicUtilities();
return $utils->get_sso_params( $user, $sso_options );
}
/**
* Get data from a request originating from a Discourse webhook.
*
* @param \WP_REST_Request $request The WP_REST_Request object.
* @param array $supported_events An optional array of supported events.
* @param string $logger_context An optional logger context.
*
* @return object|\WP_Error
*/
public static function get_discourse_webhook_data( $request, $supported_events = null, $logger_context = null ) {
$utils = new PublicUtilities();
return $utils->get_discourse_webhook_data( $request, $supported_events, $logger_context );
}
/**
* Verify that the request originated from a Discourse webhook and the the secret keys match.
* Verify that the request originated from a Discourse webhook and the secret keys match.
*
* @param \WP_REST_Request $data The WP_REST_Request object.
* @param \WP_REST_Request $request The WP_REST_Request object.
*
* @return \WP_Error|\WP_REST_Request
*/
public static function verify_discourse_webhook_request( $data ) {
$utils = new PublicPluginUtilities();
return $utils->verify_discourse_webhook_request( $data );
public static function verify_discourse_webhook_request( $request ) {
$utils = new PublicUtilities();
return $utils->verify_discourse_webhook_request( $request );
}
/**
@ -152,7 +146,7 @@ class Utilities {
* @return int|\WP_Error
*/
public static function create_discourse_user( $user, $require_activation = true ) {
$utils = new PublicPluginUtilities();
$utils = new PublicUtilities();
$api_credentials = $utils->get_api_credentials();
if ( is_wp_error( $api_credentials ) ) {
@ -179,10 +173,10 @@ class Utilities {
);
$user_data = static::discourse_request(
$create_user_url, array(
'body' => $body,
'method' => 'POST',
)
$create_user_url, array(
'body' => $body,
'method' => 'POST',
)
);
if ( is_wp_error( $user_data ) ) {
@ -352,8 +346,8 @@ class Utilities {
*/
public static function extract_groups( $raw_groups ) {
return array_reduce(
$raw_groups,
function( $result, $group ) {
$raw_groups,
function ( $result, $group ) {
if ( empty( $group->automatic ) ) {
$result[] = static::discourse_munge( $group, static::GROUP_SCHEMA );
}
@ -382,7 +376,7 @@ class Utilities {
$result->{$key} = intval( $value );
break;
case 'bool':
$result->{$key} = true == $value;
$result->{$key} = true === $value;
break;
case 'text':
$result->{$key} = sanitize_text_field( $value );
@ -405,19 +399,19 @@ class Utilities {
return $result;
}
/**
* Publishes a post to a Discourse.
*
* @param string $post_id ID of the post to publish.
* @param array $options An optional array of options to pass to DiscoursePublish.
*
* @return void|\WP_Error;
*/
public static function publish_to_discourse( $post_id, $options = array() ) {
/**
* Publishes a post to a Discourse.
*
* @param string $post_id ID of the post to publish.
* @param array $options An optional array of options to pass to DiscoursePublish.
*
* @return void|\WP_Error;
*/
public static function publish_to_discourse( $post_id, $options = array() ) {
$post = get_post( $post_id );
if ( ! $post ) {
return new \WP_Error( 'wpdc_param_error', 'There is no WordPress post with the supplied id.' );
return new \WP_Error( 'wpdc_param_error', 'There is no WordPress post with the supplied id.' );
}
$email_notifier = new \WPDiscourse\EmailNotification\EmailNotification();
@ -425,5 +419,5 @@ class Utilities {
$publish->setup_options( $options );
$publish->setup_logger();
$publish->sync_to_discourse( $post_id, $post->post_title, $post->post_content );
}
}
}

143
lib/webhook-utilities.php Normal file
View file

@ -0,0 +1,143 @@
<?php
/**
* Shared Webhook methods.
*
* @package WPDiscourse
*/
namespace WPDiscourse\Shared;
use WPDiscourse\Logs\Logger;
/**
* Trait WebhookUtilities
*/
trait WebhookUtilities {
/**
* Supported Webhook events.
*
* @access protected
* @var mixed|void
*/
protected $supported_events = null;
/**
* Get webhook data for WPDiscourse classes.
*
* @param \WP_REST_Request $request The WP_REST_Request data object.
*
* @return object|\WP_Error
*/
protected function get_webhook_data( $request ) {
if ( ! $this->webhook_enabled() ) {
return new \WP_Error( 'discourse_webhook_error', __( 'The webhook is not enabled.' ) );
}
return $this->get_discourse_webhook_data( $request, $this->supported_events, $this->logger_context );
}
/**
* Get data from discourse webhook request.
*
* @param \WP_REST_Request $request The WP_REST_Request data object.
* @param array $supported_events An optional array of supported events.
* @param string $logger_context An optional logger context.
*
* @return object|\WP_Error
*/
protected function get_discourse_webhook_data( $request, $supported_events = null, $logger_context = 'discourse_webhook' ) {
$request = $this->verify_discourse_webhook_request( $request );
if ( is_wp_error( $request ) ) {
$this->logger->error( "$logger_context.webhook_verification_error", array( 'message', $request->get_error_message() ) );
return new \WP_Error( 'discourse_webhook_error', __( 'The webhook was not verified.' ) );
}
$event_type = $request->get_header( 'x_discourse_event_type' );
$event = $request->get_header( 'x_discourse_event' );
if ( $supported_events && ! in_array( $event, $supported_events, true ) ) {
return new \WP_Error( 'discourse_webhook_error', __( 'The webhook event is not supported.' ) );
}
$json = $request->get_json_params();
if ( is_wp_error( $json ) ) {
$this->logger->error( "$logger_context.response_body_error" );
return new \WP_Error( 'discourse_webhook_error', __( 'The webhook sent an invalid body.' ) );
}
return (object) array(
'json' => $json,
'event_type' => $event_type,
'event' => $event,
);
}
/**
* Verify that the request originated from a Discourse webhook and the the secret keys match.
*
* @param \WP_REST_Request $data The WP_REST_Request object.
*
* @return \WP_Error|\WP_REST_Request
*/
protected function verify_discourse_webhook_request( $data ) {
$options = isset( $this->options ) ? $this->options : $this->get_options();
// The X-Discourse-Event-Signature consists of 'sha256=' . hamc of raw payload.
// It is generated by computing `hash_hmac( 'sha256', $payload, $secret )`.
$sig = substr( $data->get_header( 'X-Discourse-Event-Signature' ), 7 );
if ( $sig ) {
$payload = $data->get_body();
// Key used for verifying the request - a matching key needs to be set on the Discourse webhook.
$secret = ! empty( $options['webhook-secret'] ) ? $options['webhook-secret'] : '';
if ( ! $secret ) {
return new \WP_Error( 'discourse_webhook_configuration_error', 'The webhook secret key has not been set.' );
}
if ( hash_hmac( 'sha256', $payload, $secret ) === $sig ) {
return $data;
} else {
return new \WP_Error( 'discourse_webhook_authentication_error', 'Discourse Webhook Request Error: signatures did not match.' );
}
}
return new \WP_Error( 'discourse_webhook_authentication_error', 'Discourse Webhook Request Error: the X-Discourse-Event-Signature was not set for the request.' );
}
/**
* Failed response to webhook request.
*
* @param string $message Message to add to the response.
*/
protected function failed_response( $message ) {
return wp_json_encode(
array(
'success' => false,
'message' => $message,
)
);
}
/**
* Success response to webhook request.
*
* @param string $message Message to add to the response.
*/
protected function success_response( $message = '' ) {
return wp_json_encode(
array(
'success' => true,
'message' => $message,
)
);
}
}

View file

@ -463,4 +463,3 @@ class WordPressEmailVerification {
exit;
}
}

View file

@ -23,7 +23,7 @@ require_once $_tests_dir . '/includes/functions.php';
* Manually load the plugin being tested.
*/
function _manually_load_plugin() {
require dirname( dirname( __FILE__ ) ) . '/../wp-discourse.php';
require dirname( __DIR__ ) . '/../wp-discourse.php';
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );

View file

@ -7,7 +7,7 @@
namespace WPDiscourse\Test;
use \WPDiscourse\Logs\FileManager;
use WPDiscourse\Logs\FileManager;
/**
* Logging methods for WPDiscourse unit tests

View file

@ -102,7 +102,7 @@ trait RemotePost {
protected function mock_remote_post( $first_request, $second_request = null ) {
add_filter(
'pre_http_request',
function( $prempt, $args, $url ) use ( $first_request, $second_request ) {
function ( $prempt, $args, $url ) use ( $first_request, $second_request ) {
$is_sr = ! empty( $second_request ) && ( strpos( $url, $second_request['url'] ) !== false );
$request = $is_sr ? $second_request : $first_request;

View file

@ -7,7 +7,7 @@
use WPDiscourse\Test\Multisite;
use WPDiscourse\Test\SyncDiscourseTopicTest;
use \WPDiscourse\DiscoursePublish\DiscoursePublish;
use WPDiscourse\DiscoursePublish\DiscoursePublish;
/**
* SyncDiscourseTopicMultisiteTest test case.

View file

@ -7,8 +7,8 @@
namespace WPDiscourse\Test;
use \WPDiscourse\DiscourseCommentFormatter\DiscourseCommentFormatter;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\DiscourseCommentFormatter\DiscourseCommentFormatter;
use WPDiscourse\Test\UnitTest;
/**
* DiscourseComment test case.

View file

@ -7,9 +7,9 @@
namespace WPDiscourse\Test;
use \WPDiscourse\DiscourseCommentFormatter\DiscourseCommentFormatter;
use \WPDiscourse\DiscourseComment\DiscourseComment;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\DiscourseCommentFormatter\DiscourseCommentFormatter;
use WPDiscourse\DiscourseComment\DiscourseComment;
use WPDiscourse\Test\UnitTest;
/**
* DiscourseComment test case.

View file

@ -7,8 +7,8 @@
namespace WPDiscourse\Test;
use \WPDiscourse\Test\UnitTest;
use \WPDiscourse\Admin\FormHelper;
use WPDiscourse\Test\UnitTest;
use WPDiscourse\Admin\FormHelper;
/**
* DiscourseComment test case.
@ -114,7 +114,7 @@ class DiscourseConnectionTest extends UnitTest {
$raw_body = $this->response_body_json( 'scopes' );
$body = json_decode( $raw_body );
$scopes = array_filter(
$body->scopes, function( $scope ) {
$body->scopes, function ( $scope ) {
return 'commenting' !== $scope->key;
}
);

View file

@ -7,10 +7,10 @@
namespace WPDiscourse\Test;
use \WPDiscourse\EmailNotification\EmailNotification;
use \WPDiscourse\DiscoursePublish\DiscoursePublish;
use \WPDiscourse\Logs\FileHandler;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\EmailNotification\EmailNotification;
use WPDiscourse\DiscoursePublish\DiscoursePublish;
use WPDiscourse\Logs\FileHandler;
use WPDiscourse\Test\UnitTest;
/**
* DiscoursePublish test case.
@ -40,7 +40,6 @@ class DiscoursePublishTest extends UnitTest {
public static function setUpBeforeClass(): void {
parent::setUpBeforeClass();
self::initialize_variables();
}
/**
@ -682,7 +681,7 @@ class DiscoursePublishTest extends UnitTest {
// Add filter.
add_filter(
'wpdc_publish_body',
function( $body, $remote_post_type ) use ( $tags ) {
function ( $body, $remote_post_type ) use ( $tags ) {
if ( 'create_post' === $remote_post_type ) {
$body['tags'] = $tags;
}
@ -700,7 +699,7 @@ class DiscoursePublishTest extends UnitTest {
// Check tags are in the body passed to request.
add_filter(
'pre_http_request',
function( $prempt, $args, $url ) use ( $tags, $response ) {
function ( $prempt, $args, $url ) use ( $tags, $response ) {
$body = json_decode( $args['body'] );
if ( ! isset( $body->tags ) || ! ( $tags === $body->tags ) ) {
@ -867,7 +866,7 @@ class DiscoursePublishTest extends UnitTest {
add_filter(
'pre_http_request',
function( $prempt, $args, $url ) use ( $response, $title_with_decoded_entities ) {
function ( $prempt, $args, $url ) use ( $response, $title_with_decoded_entities ) {
$body = json_decode( $args['body'] );
if ( $body->title !== $title_with_decoded_entities ) {

View file

@ -7,8 +7,8 @@
namespace WPDiscourse\Test;
use \WPDiscourse\DiscourseSSO\DiscourseSSO;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\DiscourseSSO\DiscourseSSO;
use WPDiscourse\Test\UnitTest;
/**
* DiscourseSSO test case.

View file

@ -7,11 +7,11 @@
namespace WPDiscourse\Test;
use \WPDiscourse\Logs\Logger;
use \WPDiscourse\Logs\FileManager;
use \WPDiscourse\Logs\FileHandler;
use \WPDiscourse\Logs\LineFormatter;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\Logs\Logger;
use WPDiscourse\Logs\FileManager;
use WPDiscourse\Logs\FileHandler;
use WPDiscourse\Logs\LineFormatter;
use WPDiscourse\Test\UnitTest;
/**
* FileHandler test case.
@ -21,193 +21,193 @@ class FileHandlerTest extends UnitTest {
* It creates an instance of FileHandler
*/
public function test_construct() {
$file_handler = new FileHandler( new FileManager() );
$this->assertInstanceOf( FileHandler::class, $file_handler );
$file_handler = new FileHandler( new FileManager() );
$this->assertInstanceOf( FileHandler::class, $file_handler );
}
/**
* It is enabled if the File Manager is ready
*/
public function test_enabled() {
$file_handler = new FileHandler( new FileManager() );
$this->assertTrue( $file_handler->enabled() );
$file_handler = new FileHandler( new FileManager() );
$this->assertTrue( $file_handler->enabled() );
}
/**
* It is not enabled if the File Manager is not ready
*/
public function test_not_enabled() {
$file_manager_double = \Mockery::mock( FileManager::class )->makePartial();
$file_manager_double->shouldReceive( 'ready' )->andReturn( false );
$file_handler = new FileHandler( $file_manager_double );
$this->assertFalse( $file_handler->enabled() );
$file_manager_double = \Mockery::mock( FileManager::class )->makePartial();
$file_manager_double->shouldReceive( 'ready' )->andReturn( false );
$file_handler = new FileHandler( $file_manager_double );
$this->assertFalse( $file_handler->enabled() );
}
/**
* It creates log files to write logs to
*/
public function test_log_file_create() {
$file_handler = new FileHandler( new FileManager() );
$logger = Logger::create( 'test', self::$plugin_options, $file_handler );
$logger->info( 'New Log' );
$file_handler = new FileHandler( new FileManager() );
$logger = Logger::create( 'test', self::$plugin_options, $file_handler );
$logger->info( 'New Log' );
$manager = new FileManager();
$log_files = glob( $manager->logs_dir . '/*.log' );
$this->assertCount( 1, $log_files );
$manager = new FileManager();
$log_files = glob( $manager->logs_dir . '/*.log' );
$this->assertCount( 1, $log_files );
$log_file = $log_files[0];
$this->assertFileExists( $log_file );
$log_file = $log_files[0];
$this->assertFileExists( $log_file );
}
/**
* It writes logs to a file it has created
*/
public function test_log_file_write() {
$file_handler = new FileHandler( new FileManager() );
$logger = Logger::create( 'test', self::$plugin_options, $file_handler );
$logger->info( 'New Log' );
$file_handler = new FileHandler( new FileManager() );
$logger = Logger::create( 'test', self::$plugin_options, $file_handler );
$logger->info( 'New Log' );
$manager = new FileManager();
$log_files = glob( $manager->logs_dir . '/*.log' );
$this->assertCount( 1, $log_files );
$manager = new FileManager();
$log_files = glob( $manager->logs_dir . '/*.log' );
$this->assertCount( 1, $log_files );
$log_file = $log_files[0];
$last_entry = shell_exec( "tail -n 1 $log_file" );
$this->assertMatchesRegularExpression( '/New Log/', $last_entry );
$log_file = $log_files[0];
$last_entry = shell_exec( "tail -n 1 $log_file" );
$this->assertMatchesRegularExpression( '/New Log/', $last_entry );
}
/**
* It writes multiple logs to the same file
*/
public function test_log_file_multiple() {
$file_manager = new FileManager();
$file_handler = new FileHandler( $file_manager );
$file_manager = new FileManager();
$file_handler = new FileHandler( $file_manager );
$logger = Logger::create( 'test', self::$plugin_options, $file_handler );
for ( $i = 1; $i <= 10; $i++ ) {
$logger->warning( "Multi Log $i" );
$logger = Logger::create( 'test', self::$plugin_options, $file_handler );
for ( $i = 1; $i <= 10; $i++ ) {
$logger->warning( "Multi Log $i" );
}
$log_files = glob( $file_manager->logs_dir . '/*.log' );
$this->assertCount( 1, $log_files );
$matching_line_count = 0;
$handle = fopen( $log_files[0], 'r' );
while ( ! feof( $handle ) ) {
$line = fgets( $handle );
if ( strpos( $line, 'Multi Log' ) !== false ) {
$matching_line_count++;
}
}
fclose( $handle );
$log_files = glob( $file_manager->logs_dir . '/*.log' );
$this->assertCount( 1, $log_files );
$matching_line_count = 0;
$handle = fopen( $log_files[0], 'r' );
while ( ! feof( $handle ) ) {
$line = fgets( $handle );
if ( strpos( $line, 'Multi Log' ) !== false ) {
$matching_line_count++;
}
}
fclose( $handle );
$this->assertEquals( 10, $matching_line_count );
$this->assertEquals( 10, $matching_line_count );
}
/**
* It rotates log files every day.
*/
public function test_log_file_date_rotation() {
$file_manager = new FileManager();
$file_handler = new FileHandler( $file_manager );
$file_manager = new FileManager();
$file_handler = new FileHandler( $file_manager );
$logger = Logger::create( 'test', self::$plugin_options, $file_handler );
$logger->warning( "Today's Log" );
$logger = Logger::create( 'test', self::$plugin_options, $file_handler );
$logger->warning( "Today's Log" );
$todays_datetime = new \DateTimeImmutable( 'now' );
$tomorrows_datetime = new \DateTimeImmutable( 'tomorrow' );
$todays_datetime = new \DateTimeImmutable( 'now' );
$tomorrows_datetime = new \DateTimeImmutable( 'tomorrow' );
// Make file handler think it's tomorrow.
$tomorrows_file_handler = new FileHandler( $file_manager, null, null, $tomorrows_datetime );
// Make file handler think it's tomorrow.
$tomorrows_file_handler = new FileHandler( $file_manager, null, null, $tomorrows_datetime );
// Make logger think it's tomorrow.
$tomorrows_logger = Logger::create( 'test', self::$plugin_options, $tomorrows_file_handler );
$tomorrows_logger->pushProcessor(
function ( $record ) use ( $tomorrows_datetime ) {
$record['datetime'] = $tomorrows_datetime;
return $record;
}
);
// Make logger think it's tomorrow.
$tomorrows_logger = Logger::create( 'test', self::$plugin_options, $tomorrows_file_handler );
$tomorrows_logger->pushProcessor(
function ( $record ) use ( $tomorrows_datetime ) {
$record['datetime'] = $tomorrows_datetime;
return $record;
}
);
$tomorrows_logger->warning( "Tomorrow's Log" );
$tomorrows_logger->warning( "Tomorrow's Log" );
$tomorrows_date = $tomorrows_datetime->format( FileHandler::DATE_FORMAT );
$todays_date = $todays_datetime->format( FileHandler::DATE_FORMAT );
$tomorrows_date = $tomorrows_datetime->format( FileHandler::DATE_FORMAT );
$todays_date = $todays_datetime->format( FileHandler::DATE_FORMAT );
$files = $file_handler->list_files();
$this->assertMatchesRegularExpression( '/' . $tomorrows_date . '/', $files[0] );
$this->assertMatchesRegularExpression( '/' . $todays_date . '/', $files[1] );
$files = $file_handler->list_files();
$this->assertMatchesRegularExpression( '/' . $tomorrows_date . '/', $files[0] );
$this->assertMatchesRegularExpression( '/' . $todays_date . '/', $files[1] );
}
/**
* It rotates logs when size limit is reached.
*/
public function test_log_file_size_limit_rotation() {
$file_manager = new FileManager();
$file_handler = new FileHandler( $file_manager );
$file_manager = new FileManager();
$file_handler = new FileHandler( $file_manager );
$logger = Logger::create( 'high-volume', self::$plugin_options, $file_handler );
$logger->warning( 'High volume log' );
$logger = Logger::create( 'high-volume', self::$plugin_options, $file_handler );
$logger->warning( 'High volume log' );
// It's inefficient to create a large file via individual logs, so we're
// stuffing the log file with filler data so it's almost up to the limit
// then taking it over the limit with normal logs.
// It's inefficient to create a large file via individual logs, so we're
// stuffing the log file with filler data so it's almost up to the limit
// then taking it over the limit with normal logs.
$handle = fopen( $file_handler->getUrl(), 'wb' );
$limit = $file_handler->get_file_size_limit();
$handle = fopen( $file_handler->getUrl(), 'wb' );
$limit = $file_handler->get_file_size_limit();
while ( fstat( $handle )['size'] < ( $limit - ( 1024 * 30 * 1 ) ) ) {
fwrite( $handle, str_repeat( "filler line taking up 30 bts\n", 1024 ) );
}
while ( fstat( $handle )['size'] < ( $limit - ( 1024 * 30 * 1 ) ) ) {
fwrite( $handle, str_repeat( "filler line taking up 30 bts\n", 1024 ) );
}
for ( $i = 1; $i <= 300; $i++ ) {
$logger->warning( 'High volume log' );
}
for ( $i = 1; $i <= 300; $i++ ) {
$logger->warning( 'High volume log' );
}
$this->assertLessThanOrEqual( $limit, fstat( $handle )['size'] );
$this->assertCount( 2, $file_handler->list_files() );
$this->assertLessThanOrEqual( $limit, fstat( $handle )['size'] );
$this->assertCount( 2, $file_handler->list_files() );
}
/**
* It increments file numbers on each rotation.
*/
public function test_log_file_number() {
$file_manager = new FileManager();
$file_manager = new FileManager();
// Size limit to restrict each file to a single line.
$low_limit_file_handler = new FileHandler( $file_manager, 200 );
// Size limit to restrict each file to a single line.
$low_limit_file_handler = new FileHandler( $file_manager, 200 );
$logger = Logger::create( 'one-log-per-file', self::$plugin_options, $low_limit_file_handler );
$logger = Logger::create( 'one-log-per-file', self::$plugin_options, $low_limit_file_handler );
for ( $i = 1; $i <= 7; $i++ ) {
$logger->warning( 'A line long enough to take it over 100 bytes with log metadata' );
}
for ( $i = 1; $i <= 7; $i++ ) {
$logger->warning( 'A line long enough to take it over 100 bytes with log metadata' );
}
$this->assertCount( 7, $low_limit_file_handler->list_files() );
$this->assertEquals( 7, $low_limit_file_handler->current_file_number() );
$this->assertCount( 7, $low_limit_file_handler->list_files() );
$this->assertEquals( 7, $low_limit_file_handler->current_file_number() );
}
/**
* It respects the max_files limit.
*/
public function test_log_max_files() {
$file_manager = new FileManager();
$file_manager = new FileManager();
// Size limit to restrict each file to a single line.
$handler = new FileHandler( $file_manager, 200 );
$logger = Logger::create( 'one-log-per-file', self::$plugin_options, $handler );
// Size limit to restrict each file to a single line.
$handler = new FileHandler( $file_manager, 200 );
$logger = Logger::create( 'one-log-per-file', self::$plugin_options, $handler );
for ( $i = 1; $i <= 15; $i++ ) {
$logger->warning( 'A line long enough to take it over 100 bytes with log metadata' );
}
for ( $i = 1; $i <= 15; $i++ ) {
$logger->warning( 'A line long enough to take it over 100 bytes with log metadata' );
}
$files = $handler->list_files();
$files = $handler->list_files();
$this->assertCount( 10, $files );
$this->assertCount( 10, $files );
// Ensure the right files have been removed.
$this->assertEquals( 15, $handler->get_number_from_url( $files[0] ) );
$this->assertEquals( 6, $handler->get_number_from_url( end( $files ) ) );
// Ensure the right files have been removed.
$this->assertEquals( 15, $handler->get_number_from_url( $files[0] ) );
$this->assertEquals( 6, $handler->get_number_from_url( end( $files ) ) );
}
}

View file

@ -7,8 +7,8 @@
namespace WPDiscourse\Test;
use \WPDiscourse\Logs\FileManager;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\Logs\FileManager;
use WPDiscourse\Test\UnitTest;
/**
* Logger test case.
@ -18,107 +18,107 @@ class FileManagerTest extends UnitTest {
* Setup test class.
*/
public function setUp(): void {
parent::setUp();
static::reset_permissions();
parent::setUp();
static::reset_permissions();
}
/**
* Teardown test class.
*/
public function tearDown(): void {
parent::tearDown();
static::reset_permissions();
parent::tearDown();
static::reset_permissions();
}
/**
* Validation creates discourse uploads folder and .htaccess file if they don't exist.
*/
public function test_validation_uploads_creation() {
$file_manager = new FileManager();
$file_manager = new FileManager();
$this->recursive_rmdir( $file_manager->upload_dir );
$this->assertDirectoryDoesNotExist( $file_manager->upload_dir );
$this->recursive_rmdir( $file_manager->upload_dir );
$this->assertDirectoryDoesNotExist( $file_manager->upload_dir );
$file_manager->validate();
$file_manager->validate();
$this->assertDirectoryExists( $file_manager->upload_dir );
$this->assertFileExists( $file_manager->upload_dir . '/.htaccess' );
$this->assertDirectoryExists( $file_manager->upload_dir );
$this->assertFileExists( $file_manager->upload_dir . '/.htaccess' );
}
/**
* Validation creates discourse logs folder and .htaccess file if they don't exist.
*/
public function test_validation_logs_creation() {
$file_manager = new FileManager();
$file_manager = new FileManager();
$this->recursive_rmdir( $file_manager->logs_dir );
$this->assertDirectoryDoesNotExist( $file_manager->logs_dir );
$this->recursive_rmdir( $file_manager->logs_dir );
$this->assertDirectoryDoesNotExist( $file_manager->logs_dir );
$file_manager->validate();
$file_manager->validate();
$this->assertDirectoryExists( $file_manager->logs_dir );
$this->assertFileExists( $file_manager->logs_dir . '/.htaccess' );
$this->assertDirectoryExists( $file_manager->logs_dir );
$this->assertFileExists( $file_manager->logs_dir . '/.htaccess' );
}
/**
* It is ready if validation passes.
*/
public function test_validation_ready() {
$file_manager = new FileManager();
$this->assertTrue( $file_manager->validate() );
$this->assertTrue( $file_manager->ready() );
$file_manager = new FileManager();
$this->assertTrue( $file_manager->validate() );
$this->assertTrue( $file_manager->ready() );
}
/**
* It is not ready if validation is not run
*/
public function test_validation_not_ready() {
$file_manager = new FileManager();
$this->assertFalse( $file_manager->ready() );
$file_manager = new FileManager();
$this->assertFalse( $file_manager->ready() );
}
/**
* Validation will not pass if wp uploads directory is not writable
*/
public function test_validation_when_wp_uploads_not_writable() {
$file_manager = new FileManager();
$file_manager = new FileManager();
chmod( wp_upload_dir()['basedir'], 0444 );
chmod( wp_upload_dir()['basedir'], 0444 );
$this->assertFalse( $file_manager->validate() );
$this->assertFalse( $file_manager->ready() );
$this->assertFalse( $file_manager->validate() );
$this->assertFalse( $file_manager->ready() );
}
/**
* Validation will not pass if all necessary folders and files are not present and writable.
*/
public function test_validation_when_folders_partially_restricted() {
$file_manager = new FileManager();
$file_manager = new FileManager();
$this->assertTrue( $file_manager->validate() );
$this->assertTrue( $file_manager->ready() );
$this->assertTrue( $file_manager->validate() );
$this->assertTrue( $file_manager->ready() );
chmod( $file_manager->logs_dir, 0444 );
chmod( $file_manager->logs_dir, 0444 );
$this->assertFalse( $file_manager->validate() );
$this->assertFalse( $file_manager->ready() );
$this->assertFalse( $file_manager->validate() );
$this->assertFalse( $file_manager->ready() );
}
/**
* Reset directory permissions.
*/
protected function reset_permissions() {
$file_manager = new FileManager();
$file_manager = new FileManager();
chmod( wp_upload_dir()['basedir'], 0744 );
chmod( wp_upload_dir()['basedir'], 0744 );
if ( is_dir( $file_manager->upload_dir ) ) {
chmod( $file_manager->upload_dir, 0744 );
}
if ( is_dir( $file_manager->upload_dir ) ) {
chmod( $file_manager->upload_dir, 0744 );
}
if ( is_dir( $file_manager->logs_dir ) ) {
chmod( $file_manager->logs_dir, 0744 );
}
if ( is_dir( $file_manager->logs_dir ) ) {
chmod( $file_manager->logs_dir, 0744 );
}
}
/**
@ -127,20 +127,20 @@ class FileManagerTest extends UnitTest {
* @param string $dir Path of directory to remove.
*/
protected function recursive_rmdir( $dir ) {
if ( is_dir( $dir ) ) {
$objects = scandir( $dir );
if ( is_dir( $dir ) ) {
$objects = scandir( $dir );
foreach ( $objects as $object ) {
if ( '.' !== $object && '..' !== $object ) {
if ( is_dir( $dir . DIRECTORY_SEPARATOR . $object ) ) {
$this->recursive_rmdir( $dir . DIRECTORY_SEPARATOR . $object );
} else {
unlink( $dir . DIRECTORY_SEPARATOR . $object );
}
}
}
foreach ( $objects as $object ) {
if ( '.' !== $object && '..' !== $object ) {
if ( is_dir( $dir . DIRECTORY_SEPARATOR . $object ) ) {
$this->recursive_rmdir( $dir . DIRECTORY_SEPARATOR . $object );
} else {
unlink( $dir . DIRECTORY_SEPARATOR . $object );
}
}
}
rmdir( $dir );
}
rmdir( $dir );
}
}
}

View file

@ -8,12 +8,12 @@
namespace WPDiscourse\Test;
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use \WPDiscourse\Logs\FileManager;
use \WPDiscourse\Logs\FileHandler;
use \WPDiscourse\Logs\Logger;
use \WPDiscourse\Admin\LogViewer;
use \WPDiscourse\Admin\FormHelper;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\Logs\FileManager;
use WPDiscourse\Logs\FileHandler;
use WPDiscourse\Logs\Logger;
use WPDiscourse\Admin\LogViewer;
use WPDiscourse\Admin\FormHelper;
use WPDiscourse\Test\UnitTest;
/**
* Logger test case.

View file

@ -7,11 +7,11 @@
namespace WPDiscourse\Test;
use \WPDiscourse\Logs\Logger;
use \WPDiscourse\Logs\NullHandler;
use \WPDiscourse\Logs\FileHandler;
use \WPDiscourse\Logs\LineFormatter;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\Logs\Logger;
use WPDiscourse\Logs\NullHandler;
use WPDiscourse\Logs\FileHandler;
use WPDiscourse\Logs\LineFormatter;
use WPDiscourse\Test\UnitTest;
/**
* Logger test case.
@ -22,19 +22,19 @@ class LoggerTest extends UnitTest {
* Teardown each test.
*/
public function tearDown(): void {
parent::tearDown();
parent::tearDown();
self::$plugin_options['logs-enabled'] = 1;
self::$plugin_options['logs-enabled'] = 1;
}
/**
* It creates an instance of Logger
*/
public function test_create() {
$logger = Logger::create( 'test', self::$plugin_options );
$this->assertInstanceOf( Logger::class, $logger );
$logger = Logger::create( 'test', self::$plugin_options );
$this->assertInstanceOf( Logger::class, $logger );
return $logger;
return $logger;
}
/**
@ -44,13 +44,13 @@ class LoggerTest extends UnitTest {
* @depends test_create
*/
public function test_create_handler( $logger ) {
$handlers = $logger->getHandlers();
$this->assertCount( 1, $handlers );
$handlers = $logger->getHandlers();
$this->assertCount( 1, $handlers );
$file_handler = reset( $handlers );
$this->assertInstanceOf( FileHandler::class, $file_handler );
$file_handler = reset( $handlers );
$this->assertInstanceOf( FileHandler::class, $file_handler );
return $file_handler;
return $file_handler;
}
/**
@ -60,32 +60,32 @@ class LoggerTest extends UnitTest {
* @depends test_create_handler
*/
public function test_create_handler_formatter( $file_handler ) {
$this->assertInstanceOf( LineFormatter::class, $file_handler->getFormatter() );
$this->assertInstanceOf( LineFormatter::class, $file_handler->getFormatter() );
}
/**
* It attaches NullHandler if FileHandler is not enabled
*/
public function test_create_file_handler_not_enabled() {
$file_handler_double = \Mockery::mock( FileHandler::class )->makePartial();
$file_handler_double->shouldReceive( 'enabled' )->andReturn( false );
$file_handler_double = \Mockery::mock( FileHandler::class )->makePartial();
$file_handler_double->shouldReceive( 'enabled' )->andReturn( false );
$logger = Logger::create( 'test', self::$plugin_options, $file_handler_double );
$handlers = $logger->getHandlers();
$logger = Logger::create( 'test', self::$plugin_options, $file_handler_double );
$handlers = $logger->getHandlers();
$this->assertCount( 1, $handlers );
$this->assertContainsOnlyInstancesOf( NullHandler::class, $handlers );
$this->assertCount( 1, $handlers );
$this->assertContainsOnlyInstancesOf( NullHandler::class, $handlers );
}
/**
* It attaches NullHandler if logs are not enabled
*/
public function test_create_logs_not_enabled() {
self::$plugin_options['logs-enabled'] = 0;
$logger = Logger::create( 'test', self::$plugin_options );
$handlers = $logger->getHandlers();
self::$plugin_options['logs-enabled'] = 0;
$logger = Logger::create( 'test', self::$plugin_options );
$handlers = $logger->getHandlers();
$this->assertCount( 1, $handlers );
$this->assertContainsOnlyInstancesOf( NullHandler::class, $handlers );
$this->assertCount( 1, $handlers );
$this->assertContainsOnlyInstancesOf( NullHandler::class, $handlers );
}
}

View file

@ -7,9 +7,9 @@
namespace WPDiscourse\Test;
use \WPDiscourse\SSOClient\Client;
use \WPDiscourse\SSOClient\Nonce;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\SSOClient\Client;
use WPDiscourse\SSOClient\Nonce;
use WPDiscourse\Test\UnitTest;
/**
* SSOClient test case.
@ -211,9 +211,9 @@ class SSOClientTest extends UnitTest {
self::$plugin_options['sso-client-disable-create-user'] = 1;
$this->sso_client->setup_options( self::$plugin_options );
$parse_result = $this->sso_client->parse_request();
$parse_result = $this->sso_client->parse_request();
$log = $this->get_last_log();
$log = $this->get_last_log();
$this->assertMatchesRegularExpression( '/sso_client.ERROR: parse_request.get_user_id/', $log );
$this->assertMatchesRegularExpression( '/"code":"no_matching_user"/', $log );
}

View file

@ -8,7 +8,7 @@
namespace WPDiscourse\Test;
use WPDiscourse\SyncDiscourseTopic\SyncDiscourseTopic;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\Test\UnitTest;
/**
* SyncDiscourseTopic test case.
@ -51,72 +51,80 @@ class SyncDiscourseTopicTest extends UnitTest {
* Setup each test.
*/
public function setUp(): void {
parent::setUp();
parent::setUp();
self::$plugin_options['webhook-secret'] = '1234567891011';
self::$plugin_options['use-discourse-webhook'] = 1;
self::$plugin_options['webhook-secret'] = '1234567891011';
self::$plugin_options['use-discourse-webhook'] = 1;
$this->sync_topic = new SyncDiscourseTopic();
$this->sync_topic->setup_options( self::$plugin_options );
$this->sync_topic->setup_logger();
$this->sync_topic = new SyncDiscourseTopic();
$this->sync_topic->setup_options( self::$plugin_options );
$this->sync_topic->setup_logger();
$this->payload = $this->response_body_file( 'webhook_post' );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->payload = $this->response_body_file( 'webhook_post' );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->request = new \WP_REST_Request();
$this->request->set_header( 'Content-Type', 'application/json' );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_body( $this->payload );
$this->request = new \WP_REST_Request();
$this->request->set_header( 'Content-Type', 'application/json' );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_header( 'X-Discourse-Event-Type', 'post' );
$this->request->set_header( 'X-Discourse-Event', 'post_created' );
$this->request->set_body( $this->payload );
}
/**
* update_topic_content handles webhook results correctly.
*/
public function test_update_topic_content() {
// Setup the posts
$post_id = wp_insert_post( self::$post_atts, false, false );
$discourse_post = json_decode( $this->payload )->post;
// Setup the posts
$post_id = wp_insert_post( self::$post_atts, false, false );
$discourse_post = json_decode( $this->payload )->post;
// Setup the post meta
$discourse_topic_id = $discourse_post->topic_id;
update_post_meta( $post_id, 'discourse_topic_id', $discourse_topic_id );
// Setup the post meta
$discourse_topic_id = $discourse_post->topic_id;
update_post_meta( $post_id, 'discourse_topic_id', $discourse_topic_id );
// Perform update
$this->sync_topic->update_topic_content( $this->request );
// Perform update
$response = $this->sync_topic->update_topic_content( $this->request );
// Ensure the post meta is updated correctly.
$this->assertEquals( get_post_meta( $post_id, 'wpdc_sync_post_comments', true ), 1 );
$this->assertEquals( get_post_meta( $post_id, 'discourse_comments_count', true ), '2' );
// Ensure the correct response is returned.
$this->assertEquals( json_decode( $response )->success, true );
$this->assertEquals( json_decode( $response )->message, 'The posts have been updated.' );
// Cleanup
wp_delete_post( $post_id );
// Ensure the post meta is updated correctly.
$this->assertEquals( get_post_meta( $post_id, 'wpdc_sync_post_comments', true ), 1 );
$this->assertEquals( get_post_meta( $post_id, 'discourse_comments_count', true ), '2' );
// Cleanup
wp_delete_post( $post_id );
}
/**
* update_topic_content throws an error when the webhook signature is invalid.
*/
public function test_update_topic_content_invalid_signature() {
// Setup invalid signature
self::$plugin_options['webhook-secret'] = '123456789101112';
$this->sync_topic->setup_options( self::$plugin_options );
// Setup invalid signature
self::$plugin_options['webhook-secret'] = '123456789101112';
$this->sync_topic->setup_options( self::$plugin_options );
// Setup the posts
$post_id = wp_insert_post( self::$post_atts, false, false );
$discourse_post = json_decode( $this->payload )->post;
// Setup the posts
$post_id = wp_insert_post( self::$post_atts, false, false );
$discourse_post = json_decode( $this->payload )->post;
// Setup the post meta
$discourse_topic_id = $discourse_post->topic_id;
update_post_meta( $post_id, 'discourse_topic_id', $discourse_topic_id );
// Setup the post meta
$discourse_topic_id = $discourse_post->topic_id;
update_post_meta( $post_id, 'discourse_topic_id', $discourse_topic_id );
// Perform update
$result = $this->sync_topic->update_topic_content( $this->request );
// Perform update
$response = $this->sync_topic->update_topic_content( $this->request );
// Ensure we got the right result
$this->assertTrue( is_wp_error( $result ) );
$this->assertEquals( get_post_meta( $post_id, 'wpdc_sync_post_comments', true ), null );
// Ensure the correct response is returned.
$this->assertEquals( json_decode( $response )->success, false );
$this->assertEquals( json_decode( $response )->message, 'The webhook was not verified.' );
// Cleanup
wp_delete_post( $post_id );
// Ensure the comment metadata is not changed
$this->assertEquals( get_post_meta( $post_id, 'wpdc_sync_post_comments', true ), null );
// Cleanup
wp_delete_post( $post_id );
}
}

View file

@ -8,7 +8,7 @@
namespace WPDiscourse\Test;
use WPDiscourse\SyncDiscourseUser\SyncDiscourseUser;
use \WPDiscourse\Test\UnitTest;
use WPDiscourse\Test\UnitTest;
/**
* SyncDiscourseUser test case.
@ -51,131 +51,172 @@ class SyncDiscourseUserTest extends UnitTest {
* Setup each test.
*/
public function setUp(): void {
parent::setUp();
parent::setUp();
self::$plugin_options['webhook-secret'] = '1234567891011';
self::$plugin_options['use-discourse-user-webhook'] = 1;
self::$plugin_options['enable-sso'] = 1;
self::$plugin_options['webhook-secret'] = '1234567891011';
self::$plugin_options['use-discourse-user-webhook'] = 1;
self::$plugin_options['enable-sso'] = 1;
$this->sync_user = new SyncDiscourseUser();
$this->sync_user->setup_options( self::$plugin_options );
$this->sync_user->setup_logger();
$this->sync_user = new SyncDiscourseUser();
$this->sync_user->setup_options( self::$plugin_options );
$this->sync_user->setup_logger();
$this->payload = $this->response_body_file( 'webhook_user' );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->payload = $this->response_body_file( 'webhook_user' );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->request = new \WP_REST_Request();
$this->request->set_header( 'Content-Type', 'application/json' );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_header( 'X-Discourse-Event-Type', 'user' );
$this->request->set_header( 'X-Discourse-Event', 'user_updated' );
$this->request->set_body( $this->payload );
$this->request = new \WP_REST_Request();
$this->request->set_header( 'Content-Type', 'application/json' );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_header( 'X-Discourse-Event-Type', 'user' );
$this->request->set_header( 'X-Discourse-Event', 'user_updated' );
$this->request->set_body( $this->payload );
}
public function tearDown(): void {
parent::tearDown();
parent::tearDown();
$payload = json_decode( $this->payload );
delete_metadata( 'user', null, 'discourse_username', null, true );
delete_metadata( 'user', null, 'discourse_sso_user_id', null, true );
$payload = json_decode( $this->payload );
delete_metadata( 'user', null, 'discourse_username', null, true );
delete_metadata( 'user', null, 'discourse_sso_user_id', null, true );
}
/**
* update_user handles webhook results correctly.
*/
public function test_update_user() {
// Setup the user
$payload = json_decode( $this->payload );
$discourse_user = $payload->user;
$user = wp_set_current_user( $discourse_user->external_id );
// Setup the user
$payload = json_decode( $this->payload );
$discourse_user = $payload->user;
$user = wp_set_current_user( $discourse_user->external_id );
// Perform update
$result = $this->sync_user->update_user( $this->request );
// Perform update
$result = $this->sync_user->update_user( $this->request );
// Ensure the post meta is updated correctly.
$this->assertEquals( get_user_meta( $user->ID, 'discourse_username', true ), $discourse_user->username );
$this->assertEquals( get_user_meta( $user->ID, 'discourse_sso_user_id', true ), $discourse_user->id );
// Ensure the post meta is updated correctly.
$this->assertEquals( get_user_meta( $user->ID, 'discourse_username', true ), $discourse_user->username );
$this->assertEquals( get_user_meta( $user->ID, 'discourse_sso_user_id', true ), $discourse_user->id );
}
/**
* update_user handles webhook results correctly when using discourse_sso_user_id.
*/
public function test_update_user_using_discourse_sso_user_id() {
// Setup request
$this->payload = $this->response_body_file( 'webhook_user' );
$payload = json_decode( $this->payload );
$payload->user->external_id = null;
$this->payload = json_encode( $payload );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_body( $this->payload );
// Setup request
$this->payload = $this->response_body_file( 'webhook_user' );
$payload = json_decode( $this->payload );
$payload->user->external_id = null;
$this->payload = json_encode( $payload );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_body( $this->payload );
// Setup the user
$user_id = self::factory()->user->create();
$discourse_user = $payload->user;
$user = wp_set_current_user( $user_id );
add_user_meta( $user->ID, 'discourse_sso_user_id', $discourse_user->id, true );
// Setup the user
$user_id = self::factory()->user->create();
$discourse_user = $payload->user;
$user = wp_set_current_user( $user_id );
add_user_meta( $user->ID, 'discourse_sso_user_id', $discourse_user->id, true );
// Perform update
$result = $this->sync_user->update_user( $this->request );
// Perform update
$result = $this->sync_user->update_user( $this->request );
// Ensure the post meta is updated correctly.
$this->assertEquals( get_user_meta( $user->ID, 'discourse_username', true ), $discourse_user->username );
// Ensure the post meta is updated correctly.
$this->assertEquals( get_user_meta( $user->ID, 'discourse_username', true ), $discourse_user->username );
}
/**
* update_user handles webhook results correctly when using email.
*/
public function test_update_user_using_email() {
// Setup options
self::$plugin_options['webhook-match-user-email'] = 1;
$this->sync_user->setup_options( self::$plugin_options );
// Setup options
self::$plugin_options['webhook-match-user-email'] = 1;
$this->sync_user->setup_options( self::$plugin_options );
// Setup request
$this->payload = $this->response_body_file( 'webhook_user' );
$payload = json_decode( $this->payload );
$payload->user->external_id = null;
$this->payload = json_encode( $payload );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_body( $this->payload );
// Setup request
$this->payload = $this->response_body_file( 'webhook_user' );
$payload = json_decode( $this->payload );
$payload->user->external_id = null;
$this->payload = json_encode( $payload );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_body( $this->payload );
// Setup the user
$user_id = self::factory()->user->create( array( 'user_email' => $payload->user->email ) );
$discourse_user = $payload->user;
$user = wp_set_current_user( $user_id );
// Setup the user
$user_id = self::factory()->user->create( array( 'user_email' => $payload->user->email ) );
$discourse_user = $payload->user;
$user = wp_set_current_user( $user_id );
// Perform update
$result = $this->sync_user->update_user( $this->request );
// Perform update
$result = $this->sync_user->update_user( $this->request );
// Ensure the post meta is updated correctly.
$this->assertEquals( get_user_meta( $user->ID, 'discourse_username', true ), $discourse_user->username );
// Ensure the post meta is updated correctly.
$this->assertEquals( get_user_meta( $user->ID, 'discourse_username', true ), $discourse_user->username );
// Destroy the user
if ( version_compare( get_bloginfo( 'version' ), '5.3', '<' ) ) {
wp_set_current_user( 0 );
}
wp_delete_user( $user_id );
}
/**
* update_user handles webhook results correctly without DiscourseConnect.
*/
public function test_update_user_using_email_without_discourse_connect() {
// Setup options
self::$plugin_options['enable-sso'] = 0;
self::$plugin_options['webhook-match-user-email'] = 1;
$this->sync_user->setup_options( self::$plugin_options );
// Setup request
$this->payload = $this->response_body_file( 'webhook_user' );
$payload = json_decode( $this->payload );
$payload->user->external_id = null;
$this->payload = json_encode( $payload );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_body( $this->payload );
// Setup the user
$user_id = self::factory()->user->create( array( 'user_email' => $payload->user->email ) );
$discourse_user = $payload->user;
$user = wp_set_current_user( $user_id );
// Perform update
$result = $this->sync_user->update_user( $this->request );
// Ensure the post meta is updated correctly.
$this->assertEquals( get_user_meta( $user->ID, 'discourse_username', true ), $discourse_user->username );
// Destroy the user
if ( version_compare( get_bloginfo( 'version' ), '5.3', '<' ) ) {
wp_set_current_user( 0 );
}
wp_delete_user( $user_id );
}
/**
* update_user creates the right logs if unable to find user.
*/
public function test_update_user_unable_to_find_user() {
// Setup request
$this->payload = $this->response_body_file( 'webhook_user' );
$payload = json_decode( $this->payload );
$payload->user->external_id = 7;
$this->payload = json_encode( $payload );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_body( $this->payload );
// Setup request
$this->payload = $this->response_body_file( 'webhook_user' );
$payload = json_decode( $this->payload );
$payload->user->external_id = 7;
$this->payload = json_encode( $payload );
$this->signature = hash_hmac( 'sha256', $this->payload, self::$plugin_options['webhook-secret'] );
$this->request->set_header( 'X-Discourse-Event-Signature', "sha256={$this->signature}" );
$this->request->set_body( $this->payload );
// Setup the user
$user_id = self::factory()->user->create();
$discourse_user = $payload->user;
$user = wp_set_current_user( $user_id );
// Setup the user
$user_id = self::factory()->user->create();
$discourse_user = $payload->user;
$user = wp_set_current_user( $user_id );
// Perform update
$result = $this->sync_user->update_user( $this->request );
// Perform update
$result = $this->sync_user->update_user( $this->request );
// Ensure the right log is created.
$log = $this->get_last_log();
$this->assertMatchesRegularExpression( '/webhook_user.WARNING: update_user.user_not_found/', $log );
// Ensure the right log is created.
$log = $this->get_last_log();
$this->assertMatchesRegularExpression( '/webhook_user.WARNING: update_user.user_not_found/', $log );
}
}

View file

@ -14,179 +14,201 @@ use WPDiscourse\Utilities\Utilities;
*/
class UtilitiesTest extends UnitTest {
/**
* User id
*
* @access protected
* @var int
*/
protected $user_id;
/**
* User id
*
* @access protected
* @var int
*/
protected $user_id;
public function setUp(): void {
$connection_options = get_option( 'discourse_connect' );
$connection_options = array_merge( $connection_options, self::$connection_options );
update_option( 'discourse_connect', $connection_options );
public function setUp(): void {
$connection_options = get_option( 'discourse_connect' );
$connection_options = array_merge( $connection_options, self::$connection_options );
update_option( 'discourse_connect', $connection_options );
$publish_options = get_option( 'discourse_publish' );
$publish_options = array_merge( $publish_options, self::$publish_options );
update_option( 'discourse_publish', $publish_options );
$publish_options = get_option( 'discourse_publish' );
$publish_options = array_merge( $publish_options, self::$publish_options );
update_option( 'discourse_publish', $publish_options );
$this->user_id = self::factory()->user->create();
}
$this->user_id = self::factory()->user->create();
}
/**
* get_options returns the plugin options.
*/
public function test_get_options() {
$options = Utilities::get_options();
$this->assertTrue( array_key_exists( 'api-key', $options ) );
}
/**
* get_options returns the plugin options.
*/
public function test_get_options() {
$options = Utilities::get_options();
$this->assertTrue( array_key_exists( 'api-key', $options ) );
}
/**
* validate validates http respsonses.
*/
public function test_validate() {
$this->assertEquals( Utilities::validate( null ), 0 );
$this->assertEquals( Utilities::validate( new \WP_Error( 'test_error', 'Test' ) ), 0 );
$this->assertEquals( Utilities::validate( $this->build_response( 'forbidden' ) ), 0 );
$this->assertEquals( Utilities::validate( $this->build_response( 'success' ) ), 1 );
}
/**
* validate validates http respsonses.
*/
public function test_validate() {
$this->assertEquals( Utilities::validate( null ), 0 );
$this->assertEquals( Utilities::validate( new \WP_Error( 'test_error', 'Test' ) ), 0 );
$this->assertEquals( Utilities::validate( $this->build_response( 'forbidden' ) ), 0 );
$this->assertEquals( Utilities::validate( $this->build_response( 'success' ) ), 1 );
}
/**
* get_discourse_categories gets the discourse categories.
*/
public function test_get_discourse_categories() {
$this->mock_remote_post_success( 'site' );
$categories = Utilities::get_discourse_categories();
$this->assertCount( 6, $categories );
}
/**
* get_discourse_categories gets the discourse categories.
*/
public function test_get_discourse_categories() {
$this->mock_remote_post_success( 'site' );
$categories = Utilities::get_discourse_categories();
$this->assertCount( 6, $categories );
}
/**
* get_discourse_user gets the discourse user.
*/
public function test_get_discourse_user() {
$this->mock_remote_post_success( 'user' );
$discourse_user = Utilities::get_discourse_user( 1 );
$this->assertEquals( $discourse_user->username, 'angus' );
}
/**
* get_discourse_user gets the discourse user.
*/
public function test_get_discourse_user() {
$this->mock_remote_post_success( 'user' );
$discourse_user = Utilities::get_discourse_user( 1 );
$this->assertEquals( $discourse_user->username, 'angus' );
}
/**
* get_discourse_user_by_email gets the discourse user by email.
*/
public function test_get_discourse_user_by_email() {
$this->mock_remote_post_success( 'users' );
$discourse_user = Utilities::get_discourse_user_by_email( 'angus@test.com' );
$this->assertEquals( $discourse_user->username, 'angus' );
}
/**
* get_discourse_user_by_email gets the discourse user by email.
*/
public function test_get_discourse_user_by_email() {
$this->mock_remote_post_success( 'users' );
$discourse_user = Utilities::get_discourse_user_by_email( 'angus@test.com' );
$this->assertEquals( $discourse_user->username, 'angus' );
}
/**
* sync_sso_record syncs the sso record.
*/
public function test_sync_sso_record() {
update_option( 'discourse_sso_common', array( 'sso-secret' => '12345678910' ) );
update_option( 'discourse_sso_provider', array( 'enable-sso' => 1 ) );
/**
* sync_sso_record syncs the sso record.
*/
public function test_sync_sso_record() {
update_option( 'discourse_sso_common', array( 'sso-secret' => '12345678910' ) );
update_option( 'discourse_sso_provider', array( 'enable-sso' => 1 ) );
$this->mock_remote_post_success( 'sync_sso', 'POST' );
$user = get_user_by( 'id', $this->user_id );
Utilities::sync_sso_record( Utilities::get_sso_params( $user ) );
$this->assertEquals( get_user_meta( $this->user_id, 'discourse_username', true ), 'angus' );
}
$this->mock_remote_post_success( 'sync_sso', 'POST' );
$user = get_user_by( 'id', $this->user_id );
Utilities::sync_sso_record( Utilities::get_sso_params( $user ) );
$this->assertEquals( get_user_meta( $this->user_id, 'discourse_username', true ), 'angus' );
}
/**
* get_sso_params gets the sso params.
*/
public function test_get_sso_params() {
$user = get_user_by( 'id', $this->user_id );
$sso_params = Utilities::get_sso_params( $user );
$this->assertTrue( array_key_exists( 'username', $sso_params ) );
}
/**
* get_sso_params gets the sso params.
*/
public function test_get_sso_params() {
$user = get_user_by( 'id', $this->user_id );
$sso_params = Utilities::get_sso_params( $user );
$this->assertTrue( array_key_exists( 'username', $sso_params ) );
}
/**
* verify_discourse_webhook_request verifies a webhook request.
*/
public function test_verify_discourse_webhook_request() {
$webhook_secret = '1234567891011';
update_option( 'discourse_webhook', array( 'webhook-secret' => $webhook_secret ) );
/**
* get_discourse_webhook_data resolves data from a webhook request.
*/
public function test_get_discourse_webhook_data() {
$webhook_secret = '1234567891011';
update_option( 'discourse_webhook', array( 'webhook-secret' => $webhook_secret ) );
$payload = $this->response_body_file( 'webhook_post' );
$signature = hash_hmac( 'sha256', $payload, $webhook_secret );
$request = new \WP_REST_Request();
$request->set_header( 'Content-Type', 'application/json' );
$request->set_header( 'X-Discourse-Event-Signature', "sha256={$signature}" );
$request->set_body( $payload );
$payload = $this->response_body_file( 'webhook_post' );
$signature = hash_hmac( 'sha256', $payload, $webhook_secret );
$request = new \WP_REST_Request();
$request->set_header( 'Content-Type', 'application/json' );
$request->set_header( 'X-Discourse-Event-Signature', "sha256={$signature}" );
$request->set_header( 'X-Discourse-Event-Type', 'post' );
$request->set_header( 'X-Discourse-Event', 'post_created' );
$request->set_body( $payload );
$response = Utilities::verify_discourse_webhook_request( $request );
$this->assertEquals( get_class( $response ), 'WP_REST_Request' );
}
$data = Utilities::get_discourse_webhook_data( $request );
$this->assertEquals( $data->json['post']['id'], 39 );
$this->assertEquals( $data->event_type, 'post' );
$this->assertEquals( $data->event, 'post_created' );
}
/**
* get_discourse_groups gets non-automatic discourse groups.
*/
public function test_get_discourse_groups() {
$this->mock_remote_post_success( 'groups' );
$groups = Utilities::get_discourse_groups();
$this->assertCount( 2, $groups );
}
/**
* verify_discourse_webhook_request verifies a webhook request.
*/
public function test_verify_discourse_webhook_request() {
$webhook_secret = '1234567891011';
update_option( 'discourse_webhook', array( 'webhook-secret' => $webhook_secret ) );
/**
* create_discourse_user creates a Discourse user.
*/
public function test_create_discourse_user() {
$this->mock_remote_post_success( 'user_create', 'POST' );
$user = get_user_by( 'id', $this->user_id );
$response = Utilities::create_discourse_user( $user );
$this->assertEquals( $response, 1 );
}
$payload = $this->response_body_file( 'webhook_post' );
$signature = hash_hmac( 'sha256', $payload, $webhook_secret );
$request = new \WP_REST_Request();
$request->set_header( 'Content-Type', 'application/json' );
$request->set_header( 'X-Discourse-Event-Signature', "sha256={$signature}" );
$request->set_body( $payload );
/**
* add_user_to_discourse_group adds a user to a Discourse group.
*/
public function test_add_user_to_discourse_group() {
update_option( 'discourse_sso_common', array( 'sso-secret' => '12345678910' ) );
update_option( 'discourse_sso_provider', array( 'enable-sso' => 1 ) );
$response = Utilities::verify_discourse_webhook_request( $request );
$this->assertEquals( get_class( $response ), 'WP_REST_Request' );
}
$this->mock_remote_post_success( 'sync_sso', 'POST' );
$response = Utilities::add_user_to_discourse_group( $this->user_id, 'test_group' );
$this->assertTrue( $response );
}
/**
* get_discourse_groups gets non-automatic discourse groups.
*/
public function test_get_discourse_groups() {
$this->mock_remote_post_success( 'groups' );
$groups = Utilities::get_discourse_groups();
$this->assertCount( 2, $groups );
}
/**
* remove_user_from_discourse_group removes a user from a Discourse group.
*/
public function test_remove_user_from_discourse_group() {
update_option( 'discourse_sso_common', array( 'sso-secret' => '12345678910' ) );
update_option( 'discourse_sso_provider', array( 'enable-sso' => 1 ) );
/**
* create_discourse_user creates a Discourse user.
*/
public function test_create_discourse_user() {
$this->mock_remote_post_success( 'user_create', 'POST' );
$user = get_user_by( 'id', $this->user_id );
$response = Utilities::create_discourse_user( $user );
$this->assertEquals( $response, 1 );
}
$this->mock_remote_post_success( 'sync_sso', 'POST' );
$response = Utilities::remove_user_from_discourse_group( $this->user_id, 'test_group' );
$this->assertTrue( $response );
}
/**
* add_user_to_discourse_group adds a user to a Discourse group.
*/
public function test_add_user_to_discourse_group() {
update_option( 'discourse_sso_common', array( 'sso-secret' => '12345678910' ) );
update_option( 'discourse_sso_provider', array( 'enable-sso' => 1 ) );
/**
* publish_to_discourse Publishes a post to Discourse.
*/
public function test_publish_to_discourse() {
// Set up a response body for creating a new post.
$body = $this->mock_remote_post_success( 'post_create', 'POST' );
$discourse_post_id = $body->id;
$discourse_topic_id = $body->topic_id;
$discourse_permalink = self::$discourse_url . '/t/' . $body->topic_slug . '/' . $body->topic_id;
$discourse_category = self::$post_atts['meta_input']['publish_post_category'];
$this->mock_remote_post_success( 'sync_sso', 'POST' );
$response = Utilities::add_user_to_discourse_group( $this->user_id, 'test_group' );
$this->assertTrue( $response );
}
// Add the post.
$post_id = wp_insert_post( self::$post_atts, false, false );
/**
* remove_user_from_discourse_group removes a user from a Discourse group.
*/
public function test_remove_user_from_discourse_group() {
update_option( 'discourse_sso_common', array( 'sso-secret' => '12345678910' ) );
update_option( 'discourse_sso_provider', array( 'enable-sso' => 1 ) );
// Run the publication.
$response = Utilities::publish_to_discourse( $post_id );
$this->mock_remote_post_success( 'sync_sso', 'POST' );
$response = Utilities::remove_user_from_discourse_group( $this->user_id, 'test_group' );
$this->assertTrue( $response );
}
// Ensure the right post meta is created.
$this->assertEquals( get_post_meta( $post_id, 'discourse_post_id', true ), $discourse_post_id );
$this->assertEquals( get_post_meta( $post_id, 'discourse_topic_id', true ), $discourse_topic_id );
$this->assertEquals( get_post_meta( $post_id, 'discourse_permalink', true ), $discourse_permalink );
$this->assertEquals( get_post_meta( $post_id, 'publish_post_category', true ), $discourse_category );
$this->assertEquals( get_post_meta( $post_id, 'wpdc_publishing_response', true ), 'success' );
/**
* publish_to_discourse Publishes a post to Discourse.
*/
public function test_publish_to_discourse() {
// Set up a response body for creating a new post.
$body = $this->mock_remote_post_success( 'post_create', 'POST' );
$discourse_post_id = $body->id;
$discourse_topic_id = $body->topic_id;
$discourse_permalink = self::$discourse_url . '/t/' . $body->topic_slug . '/' . $body->topic_id;
$discourse_category = self::$post_atts['meta_input']['publish_post_category'];
// Cleanup.
wp_delete_post( $post_id );
}
// Add the post.
$post_id = wp_insert_post( self::$post_atts, false, false );
// Run the publication.
$response = Utilities::publish_to_discourse( $post_id );
// Ensure the right post meta is created.
$this->assertEquals( get_post_meta( $post_id, 'discourse_post_id', true ), $discourse_post_id );
$this->assertEquals( get_post_meta( $post_id, 'discourse_topic_id', true ), $discourse_topic_id );
$this->assertEquals( get_post_meta( $post_id, 'discourse_permalink', true ), $discourse_permalink );
$this->assertEquals( get_post_meta( $post_id, 'publish_post_category', true ), $discourse_category );
$this->assertEquals( get_post_meta( $post_id, 'wpdc_publishing_response', true ), 'success' );
// Cleanup.
wp_delete_post( $post_id );
}
}

View file

@ -33,4 +33,3 @@ delete_site_option( 'wpdc_topic_blog_db_version' );
global $wpdb;
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}discourse_nonce" );
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}wpdc_topic_blog" );

View file

@ -42,8 +42,10 @@ $base64 = base64_encode( file_get_contents( WPDISCOURSE_LOGO_URL ) );
define( 'WPDISCOURSE_LOGO', "data:image/svg+xml;base64,$base64" );
require_once WPDISCOURSE_PATH . 'lib/plugin-utilities.php';
require_once WPDISCOURSE_PATH . 'lib/webhook-utilities.php';
require_once WPDISCOURSE_PATH . 'lib/template-functions.php';
require_once WPDISCOURSE_PATH . 'lib/utilities.php';
require_once WPDISCOURSE_PATH . 'lib/utilities/public-utilities.php';
require_once WPDISCOURSE_PATH . 'lib/utilities/utilities.php';
require_once WPDISCOURSE_PATH . 'lib/discourse.php';
require_once WPDISCOURSE_PATH . 'lib/discourse-base.php';
require_once WPDISCOURSE_PATH . 'lib/discourse-comment.php';