diff --git a/.distignore b/.distignore index d50d9f0..f883b76 100644 --- a/.distignore +++ b/.distignore @@ -4,6 +4,7 @@ /node_modules /bin /sass +/src /vendor /tests /config diff --git a/.editorconfig b/.editorconfig index 43b737f..ea6c38c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,3 @@ charset = utf-8 [*.php] indent_style = tab indent_size = 4 - -[*.{js,json}] -indent_style = space -indent_size = 2 diff --git a/build/editor-plugin/block.json b/build/editor-plugin/block.json new file mode 100644 index 0000000..f41effc --- /dev/null +++ b/build/editor-plugin/block.json @@ -0,0 +1,8 @@ +{ + "name": "editor-plugin", + "title": "Editor Plugin: not a block, but block.json is very useful.", + "category": "widgets", + "icon": "admin-comments", + "keywords": [], + "editorScript": "file:./plugin.js" +} \ No newline at end of file diff --git a/build/editor-plugin/plugin.asset.php b/build/editor-plugin/plugin.asset.php new file mode 100644 index 0000000..726932c --- /dev/null +++ b/build/editor-plugin/plugin.asset.php @@ -0,0 +1 @@ + array('react', 'wp-components', 'wp-core-data', 'wp-data', 'wp-editor', 'wp-i18n', 'wp-plugins'), 'version' => '03845d869ccb1b1eb23c'); diff --git a/build/editor-plugin/plugin.js b/build/editor-plugin/plugin.js new file mode 100644 index 0000000..b27222a --- /dev/null +++ b/build/editor-plugin/plugin.js @@ -0,0 +1 @@ +(()=>{"use strict";const e=window.React,n=window.wp.editor,t=window.wp.plugins,o=window.wp.components,i=window.wp.data,w=window.wp.coreData,s=window.wp.i18n;(0,t.registerPlugin)("webmention-editor-plugin",{render:()=>{const t=(0,i.useSelect)((e=>e("core/editor").getCurrentPostType()),[]),[r,a]=(0,w.useEntityProp)("postType",t,"meta");return(0,e.createElement)(n.PluginDocumentSettingPanel,{name:"webmention",title:(0,s.__)("Webmentions","webmention")},(0,e.createElement)(o.CheckboxControl,{__nextHasNoMarginBottom:!0,label:(0,s.__)("Disable Webmentions","webmention"),help:(0,s.__)("Do not accept incoming Webmentions for this post.","webmention"),checked:r.webmentions_disabled,onChange:e=>{a({...r,webmentions_disabled:e})}}))}})})(); \ No newline at end of file diff --git a/includes/class-block.php b/includes/class-block.php new file mode 100644 index 0000000..581d597 --- /dev/null +++ b/includes/class-block.php @@ -0,0 +1,47 @@ + true, + 'single' => true, + 'type' => 'boolean', + ) + ); + } + } + + /** + * Enqueue the block editor assets. + */ + public static function enqueue_editor_assets() { + // Check for our supported post types. + $current_screen = \get_current_screen(); + $ap_post_types = \get_post_types_by_support( 'webmentions' ); + if ( ! $current_screen || ! in_array( $current_screen->post_type, $ap_post_types, true ) ) { + return; + } + $asset_data = include WEBMENTION_PLUGIN_DIR . 'build/editor-plugin/plugin.asset.php'; + $plugin_url = plugins_url( 'build/editor-plugin/plugin.js', WEBMENTION_PLUGIN_FILE ); + wp_enqueue_script( 'webmention-block-editor', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true ); + } +} diff --git a/includes/class-receiver.php b/includes/class-receiver.php index f782d0e..f82305b 100755 --- a/includes/class-receiver.php +++ b/includes/class-receiver.php @@ -242,7 +242,7 @@ public static function post( $request ) { // check if webmentions are allowed if ( ! webmentions_open( $comment_post_id ) ) { - return new WP_Error( 'webmentions_closed', esc_html__( 'Webmentions are disabled for this post', 'webmention' ), array( 'status' => 400 ) ); + return new WP_Error( 'webmentions_disabled', esc_html__( 'Webmentions are disabled for this post', 'webmention' ), array( 'status' => 400 ) ); } $post = get_post( $comment_post_id ); diff --git a/includes/class-sender.php b/includes/class-sender.php index 3269f49..7063ca7 100755 --- a/includes/class-sender.php +++ b/includes/class-sender.php @@ -208,14 +208,15 @@ public static function send_webmentions( $post_id ) { $urls = webmention_extract_urls( $post->post_content, $support_media_urls ); // filter links - $targets = apply_filters( 'webmention_links', $urls, $post_id ); - $targets = array_unique( $targets ); - $pung = get_pung( $post ); + $targets = apply_filters( 'webmention_links', $urls, $post_id ); + $targets = array_unique( $targets ); + $mentioned = get_post_meta( $post->ID, '_webmentioned', true ); + $mentioned = empty( $mentioned ) ? array() : $mentioned; // Find previously sent Webmentions and send them one last time. - $deletes = array_diff( $pung, $targets ); + $deletes = array_diff( $mentioned, $targets ); - $ping = array(); + $mentions = array(); foreach ( $targets as $target ) { // send Webmention @@ -225,16 +226,13 @@ public static function send_webmentions( $post_id ) { continue; } - // check response - if ( - ! is_wp_error( $response ) && - wp_remote_retrieve_response_code( $response ) < 400 - ) { - $ping[] = $target; - } + $code = wp_remote_retrieve_response_code( $response ); - // reschedule if server responds with a http error 5xx - if ( wp_remote_retrieve_response_code( $response ) >= 500 ) { + // check response + if ( ! is_wp_error( $response ) && $code < 400 ) { + $mentions[] = $target; + } elseif ( $code >= 500 ) { + // reschedule if server responds with a http error 5xx self::reschedule( $post_id ); } } @@ -246,23 +244,23 @@ public static function send_webmentions( $post_id ) { // reschedule if server responds with a http error 5xx if ( wp_remote_retrieve_response_code( $response ) >= 500 ) { self::reschedule( $post_id ); - $ping[] = $deleted; + $mentions[] = $deleted; } } + if ( ! empty( $mentions ) ) { + update_post_meta( $post_id, '_webmentioned', $mentions ); + } + + $pung = get_pung( $post ); + if ( ! empty( $ping ) ) { - self::update_ping( $post, $ping ); + self::update_ping( $post, array_merge( $pung, $ping ) ); } - return $ping; + return $mentions; } - /* - * Update the Pinged List as Opposed to Adding to It. - * - * @param int|WP_Post $post_id Post. - * @param array $pinged Array of URLs - */ public static function update_ping( $post_id, $pinged ) { global $wpdb; $post = get_post( $post_id ); diff --git a/includes/functions.php b/includes/functions.php index 532c3ef..d2d0416 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -550,18 +550,22 @@ function ifset( &$var, $return = false ) { // phpcs:ignore Universal.NamingConve * @return boolean if webmentions are open */ function webmentions_open( $post = null ) { - $_post = get_post( $post ); - $post_id = $_post ? $_post->ID : 0; - - // If the post type does not support Webmentions do not even check further - if ( ! post_type_supports( get_post_type( $post_id ), 'webmentions' ) ) { - return false; + $post = get_post( $post ); + $post_id = $post ? $post->ID : 0; + $open = false; + if ( $post ) { + // Always consider the home mention link page to be open. + if ( get_option( 'webmention_home_mentions' ) === $post->ID ) { + $open = true; + } elseif ( ! post_type_supports( get_post_type( $post ), 'webmentions' ) ) { + // If the post type does not support Webmentions do not even check further. + $open = false; + } else { + // If the webmentions_disabled meta key exists then consider webmentions closed. Otherwise consider them open. + $open = ! ( metadata_exists( 'post', $post_id, 'webmentions_disabled' ) ); // Invert the result, as exists is closed and not exists is open. + } } - if ( get_option( 'webmention_home_mentions' ) === $post_id ) { - return true; - } - $open = ( $_post && ( pings_open( $post ) ) ); /** * Filters whether the current post is open for webmentions. * @@ -572,23 +576,6 @@ function webmentions_open( $post = null ) { return apply_filters( 'webmentions_open', $open, $post_id ); } -/** - * Return enabled status of Homepage Webmentions. - * - * @since 3.8.9 - * - * @param bool $open Whether the current post is open for pings. - * @param int $post_id The post ID. - * @return boolean if pings are open - */ -function webmention_pings_open( $open, $post_id ) { - if ( get_option( 'webmention_home_mentions' ) === $post_id ) { - return true; - } - - return $open; -} - /** * Retrieve the default comment status for a given post type. * @@ -602,16 +589,7 @@ function webmention_pings_open( $open, $post_id ) { * @return string */ function webmention_get_default_comment_status( $status, $post_type, $comment_type ) { - if ( 'webmention' === $comment_type ) { - return post_type_supports( $post_type, 'webmentions' ) ? 'open' : 'closed'; - } - - // Since support for the pingback comment type is used to keep pings open... - if ( ( 'pingback' === $comment_type ) ) { - return ( post_type_supports( $post_type, 'webmentions' ) ? 'open' : $status ); - } - - return $status; + return is_registered_webmention_comment_type( $comment_type ) ? 'open' : $status; } /** @@ -707,3 +685,31 @@ function is_html( $string ) { // phpcs:ignore Universal.NamingConventions.NoRese return ( wp_strip_all_tags( $string ) !== $string ); } } + +/** + * Check if a site supports the block editor. + * + * @return boolean True if the site supports the block editor, false otherwise. + */ +function site_supports_blocks() { + $return = true; + + if ( \version_compare( \get_bloginfo( 'version' ), '5.9', '<' ) ) { + $return = false; + } elseif ( \function_exists( 'classicpress_version' ) ) { + $return = false; + } elseif ( + ! \function_exists( 'register_block_type_from_metadata' ) || + ! \function_exists( 'do_blocks' ) + ) { + $return = false; + } + + /** + * Allow plugins to disable block editor support, + * thus disabling blocks registered by the Webmentions plugin. + * + * @param boolean $supports_blocks True if the site supports the block editor, false otherwise. + */ + return apply_filters( 'webmention_site_supports_blocks', $return ); +} diff --git a/package.json b/package.json index 64be192..5c1d17d 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,10 @@ "description": "Webmention support for WordPress posts", "main": "webmention.php", "devDependencies": { + "@wordpress/components": "^28.0.0", + "@wordpress/data": "^10.0.0", + "@wordpress/env": "^10.10.0", + "@wordpress/icons": "^10.10.0" }, "repository": { "type": "git", @@ -19,5 +23,12 @@ "bugs": { "url": "https://github.com/pfefferle/wordpress-webmention/issues" }, - "homepage": "https://github.com/pfefferle/wordpress-webmention" + "homepage": "https://github.com/pfefferle/wordpress-webmention", + "scripts": { + "dev": "wp-scripts start", + "build": "wp-scripts build", + "readme": "grunt wp_readme_to_markdown", + "env-start": "wp-env start", + "env-stop": "wp-env stop" + } } diff --git a/src/editor-plugin/block.json b/src/editor-plugin/block.json new file mode 100644 index 0000000..119a62d --- /dev/null +++ b/src/editor-plugin/block.json @@ -0,0 +1,8 @@ +{ + "name": "editor-plugin", + "title": "Editor Plugin: not a block, but block.json is very useful.", + "category": "widgets", + "icon": "admin-comments", + "keywords": [], + "editorScript": "file:./plugin.js" +} diff --git a/src/editor-plugin/plugin.js b/src/editor-plugin/plugin.js new file mode 100644 index 0000000..de82c7e --- /dev/null +++ b/src/editor-plugin/plugin.js @@ -0,0 +1,34 @@ +import { PluginDocumentSettingPanel } from '@wordpress/editor'; +import { registerPlugin } from '@wordpress/plugins'; +import { CheckboxControl } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { useEntityProp } from '@wordpress/core-data'; +import { __ } from '@wordpress/i18n'; + + +const EditorPlugin = () => { + const postType = useSelect( + ( select ) => select( 'core/editor' ).getCurrentPostType(), + [] + ); + const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' ); + + return ( + + { + setMeta( { ...meta, webmentions_disabled: value } ); + } } + /> + + ); +} + +registerPlugin( 'webmention-editor-plugin', { render: EditorPlugin } ); diff --git a/tests/test-sender.php b/tests/test-sender.php new file mode 100644 index 0000000..72d0887 --- /dev/null +++ b/tests/test-sender.php @@ -0,0 +1,101 @@ +post = self::factory()->post->create_and_get( array( + 'post_content' => 'Test post with a link to Example', + ) ); + } + + public function test_send_webmentions() { + // Mock der Webmention-Endpunkt-Entdeckung + add_filter( 'webmention_server_url', function( $url, $target ) { + return 'https://example.com/webmention'; + }, 10, 2 ); + + // Mock der HTTP-Anfrage + add_filter( 'pre_http_request', function( $preempt, $args, $url ) { + return array( + 'response' => array( 'code' => 200 ), + 'body' => 'Webmention received', + ); + }, 10, 3 ); + + $result = Sender::send_webmentions( $this->post->ID ); + + // Überprüfe, ob die Webmention gesendet wurde + $this->assertIsArray( $result ); + $this->assertContains( 'https://example.com', $result ); + + // Überprüfe, ob die URLs in den Post-Meta gespeichert wurden + $mentioned_urls = get_post_meta( $this->post->ID, '_webmentioned', true ); + $this->assertIsArray( $mentioned_urls ); + $this->assertContains( 'https://example.com', $mentioned_urls ); + } + + public function test_update_ping() { + $pinged = array( + 'https://example1.com', + 'https://example2.com', + ); + + $result = Sender::update_ping( $this->post->ID, $pinged ); + + // Überprüfe, ob die Pings aktualisiert wurden + $this->assertIsString( $result ); + $this->assertEquals( implode( "\n", $pinged ), $result ); + + // Überprüfe die Datenbank direkt + $updated_post = get_post( $this->post->ID ); + $this->assertEquals( $result, $updated_post->pinged ); + } + + public function test_update_ping_invalid_post() { + $result = Sender::update_ping( 999999, array( 'https://example.com' ) ); + $this->assertFalse( $result ); + } + + public function test_update_ping_invalid_pinged() { + $result = Sender::update_ping( $this->post->ID, 'not an array' ); + $this->assertFalse( $result ); + } + + public function test_send_webmentions_with_error_response() { + // Mock der Webmention-Endpunkt-Entdeckung + add_filter( 'webmention_server_url', function( $url, $target ) { + return 'https://example.com/webmention'; + }, 10, 2 ); + + // Mock der HTTP-Anfrage mit 500er Fehler + add_filter( 'pre_http_request', function( $preempt, $args, $url ) { + return array( + 'response' => array( + 'code' => 500, + 'message' => 'Internal Server Error', + ), + 'body' => 'Server Error', + 'headers' => array(), + 'cookies' => array(), + ); + }, 10, 3 ); + + $result = Sender::send_webmentions( $this->post->ID ); + + // Überprüfe, ob der Versuch neu geplant wurde + $this->assertTrue( metadata_exists( 'post', $this->post->ID, '_mentionme' ) ); + $this->assertEquals( '1', get_post_meta( $this->post->ID, '_mentionme_tries', true ) ); + } + + public function tear_down() { + parent::tear_down(); + remove_all_filters( 'webmention_server_url' ); + remove_all_filters( 'pre_http_request' ); + } +} diff --git a/tests/test-webmention.php b/tests/test-webmention.php index daa5145..9d8ff35 100644 --- a/tests/test-webmention.php +++ b/tests/test-webmention.php @@ -5,4 +5,4 @@ public function test_remove_sl() { $this->assertEquals( true, true ); } -} \ No newline at end of file +} diff --git a/webmention.php b/webmention.php index f6fa26a..2fe19fb 100755 --- a/webmention.php +++ b/webmention.php @@ -118,6 +118,12 @@ function init() { require_once __DIR__ . '/includes/class-discovery.php'; add_action( 'init', array( '\Webmention\Discovery', 'init' ) ); + if ( site_supports_blocks() ) { + // initialize Webmention Bloks. + require_once __DIR__ . '/includes/class-block.php'; + add_action( 'init', array( '\Webmention\Block', 'init' ) ); + } + // load local avatar store. if ( 1 === (int) get_option( 'webmention_avatar_store_enable', 0 ) ) { require_once __DIR__ . '/includes/class-avatar-store.php'; @@ -132,7 +138,6 @@ function init() { // Default Comment Status. add_filter( 'get_default_comment_status', 'webmention_get_default_comment_status', 11, 3 ); - add_filter( 'pings_open', 'webmention_pings_open', 10, 2 ); // Load language files. load_plugin_textdomain( 'webmention', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); @@ -149,6 +154,18 @@ function init() { remove_action( 'admin_init', array( 'Semantic_Linkbacks_Plugin', 'admin_init' ) ); add_action( 'wp_enqueue_scripts', '\Webmention\enqueue_scripts' ); + + // remove the "webmentions_disabled" meta value if the post is updated + \add_action( + 'updated_postmeta', + function ( $meta_id, $object_id, $meta_key, $meta_value ) { + if ( 'webmentions_disabled' === $meta_key && empty( $meta_value ) ) { + \delete_post_meta( $object_id, 'webmentions_disabled' ); + } + }, + 10, + 4 + ); } add_action( 'plugins_loaded', '\Webmention\init' );