From de85cca8940fe184c36428f5414656192188513c Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Wed, 25 Sep 2024 10:22:51 -0500 Subject: [PATCH] Content warnings! (#900) * Allow content warning * build files * added missing namespace * fix tests * actors does not support sensitive (yet). * Update includes/functions.php * added `sanitize_callback` --------- Co-authored-by: Matthias Pfefferle --- build/editor-plugin/block.json | 8 ++++++ build/editor-plugin/plugin.asset.php | 1 + build/editor-plugin/plugin.js | 1 + includes/activity/class-actor.php | 11 ++++++++ includes/activity/class-base-object.php | 14 +++++++++- includes/class-blocks.php | 31 +++++++++++++++++++++ includes/functions.php | 21 +++++++++++++++ includes/transformer/class-post.php | 7 +++++ src/editor-plugin/block.json | 9 +++++++ src/editor-plugin/plugin.js | 33 +++++++++++++++++++++++ tests/test-class-activitypub-activity.php | 7 ++--- 11 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 build/editor-plugin/block.json create mode 100644 build/editor-plugin/plugin.asset.php create mode 100644 build/editor-plugin/plugin.js create mode 100644 src/editor-plugin/block.json create mode 100644 src/editor-plugin/plugin.js diff --git a/build/editor-plugin/block.json b/build/editor-plugin/block.json new file mode 100644 index 000000000..f41effc5f --- /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 000000000..67a3f1810 --- /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' => '88603987940fec29730d'); diff --git a/build/editor-plugin/plugin.js b/build/editor-plugin/plugin.js new file mode 100644 index 000000000..c16cd66e0 --- /dev/null +++ b/build/editor-plugin/plugin.js @@ -0,0 +1 @@ +(()=>{"use strict";const t=window.React,e=window.wp.editor,n=window.wp.plugins,i=window.wp.components,o=window.wp.data,a=window.wp.coreData,r=window.wp.i18n;(0,n.registerPlugin)("activitypub-editor-plugin",{render:()=>{const n=(0,o.useSelect)((t=>t("core/editor").getCurrentPostType()),[]),[w,c]=(0,a.useEntityProp)("postType",n,"meta");return(0,t.createElement)(e.PluginDocumentSettingPanel,{name:"activitypub",title:(0,r.__)("Fediverse","activitypub")},(0,t.createElement)(i.TextControl,{label:(0,r.__)("Content Warning","activitypub"),value:w?.activitypub_content_warning,onChange:t=>{c({...w,activitypub_content_warning:t})},placeholder:(0,r.__)("Optional content warning","activitypub")}))}})})(); \ No newline at end of file diff --git a/includes/activity/class-actor.php b/includes/activity/class-actor.php index 2d05539ac..80dc710b3 100644 --- a/includes/activity/class-actor.php +++ b/includes/activity/class-actor.php @@ -169,4 +169,15 @@ class Actor extends Base_Object { * @var boolean */ protected $manually_approves_followers = false; + + /** + * Used to mark an object as containing sensitive content. + * Mastodon displays a content warning, requiring users to click + * through to view the content. + * + * @see https://docs.joinmastodon.org/spec/activitypub/#sensitive + * + * @var boolean + */ + protected $sensitive = null; } diff --git a/includes/activity/class-base-object.php b/includes/activity/class-base-object.php index 9a5cd3f83..112ad4b4c 100644 --- a/includes/activity/class-base-object.php +++ b/includes/activity/class-base-object.php @@ -30,7 +30,8 @@ class Base_Object { const JSON_LD_CONTEXT = array( 'https://www.w3.org/ns/activitystreams', array( - 'Hashtag' => 'as:Hashtag', + 'Hashtag' => 'as:Hashtag', + 'sensitive' => 'as:sensitive', ), ); @@ -445,6 +446,17 @@ class Base_Object { */ protected $replies; + /** + * Used to mark an object as containing sensitive content. + * Mastodon displays a content warning, requiring users to click + * through to view the content. + * + * @see https://docs.joinmastodon.org/spec/activitypub/#sensitive + * + * @var boolean + */ + protected $sensitive = false; + /** * Magic function to implement getter and setter * diff --git a/includes/class-blocks.php b/includes/class-blocks.php index 5f2deb9c6..f2fea4089 100644 --- a/includes/class-blocks.php +++ b/includes/class-blocks.php @@ -14,6 +14,37 @@ public static function init() { \add_action( 'wp_enqueue_scripts', array( self::class, 'add_data' ) ); \add_action( 'enqueue_block_editor_assets', array( self::class, 'add_data' ) ); \add_action( 'load-post-new.php', array( self::class, 'handle_in_reply_to_get_param' ) ); + // Add editor plugin + \add_action( 'enqueue_block_editor_assets', array( self::class, 'enqueue_editor_assets' ) ); + \add_action( 'init', array( self::class, 'register_postmeta' ), 11 ); + } + + public static function register_postmeta() { + $ap_post_types = \get_post_types_by_support( 'activitypub' ); + foreach ( $ap_post_types as $post_type ) { + \register_post_meta( + $post_type, + 'activitypub_content_warning', + array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + ) + ); + } + } + + 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( 'activitypub' ); + if ( ! $current_screen || ! in_array( $current_screen->post_type, $ap_post_types, true ) ) { + return; + } + $asset_data = include ACTIVITYPUB_PLUGIN_DIR . 'build/editor-plugin/plugin.asset.php'; + $plugin_url = plugins_url( 'build/editor-plugin/plugin.js', ACTIVITYPUB_PLUGIN_FILE ); + wp_enqueue_script( 'activitypub-block-editor', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true ); } /** diff --git a/includes/functions.php b/includes/functions.php index 3d894c253..767bf7215 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1215,3 +1215,24 @@ function generate_post_summary( $post, $length = 500 ) { */ return $content; } + +/** + * Get the content warning of a post. + * + * @param int $post_id The post ID. + * + * @return string|false The content warning or false if not found. + */ +function get_content_warning( $post_id ) { + $post = get_post( $post_id ); + if ( ! $post ) { + return false; + } + + $warning = get_post_meta( $post->ID, 'activitypub_content_warning', true ); + if ( empty( $warning ) ) { + return false; + } + + return $warning; +} diff --git a/includes/transformer/class-post.php b/includes/transformer/class-post.php index 8fec1bfab..a80507f04 100644 --- a/includes/transformer/class-post.php +++ b/includes/transformer/class-post.php @@ -14,6 +14,7 @@ use function Activitypub\get_rest_url_by_path; use function Activitypub\is_user_type_disabled; use function Activitypub\generate_post_summary; +use function Activitypub\get_content_warning; /** * WordPress Post Transformer @@ -87,6 +88,12 @@ public function to_object() { ) ); + $content_warning = get_content_warning( $post ); + if ( ! empty( $content_warning ) ) { + $object->set_sensitive( true ); + $object->set_summary( $content_warning ); + } + return $object; } diff --git a/src/editor-plugin/block.json b/src/editor-plugin/block.json new file mode 100644 index 000000000..a8d510c7c --- /dev/null +++ b/src/editor-plugin/block.json @@ -0,0 +1,9 @@ +{ + "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/src/editor-plugin/plugin.js b/src/editor-plugin/plugin.js new file mode 100644 index 000000000..7e95dcda2 --- /dev/null +++ b/src/editor-plugin/plugin.js @@ -0,0 +1,33 @@ +import { PluginDocumentSettingPanel } from '@wordpress/editor'; +import { registerPlugin } from '@wordpress/plugins'; +import { TextControl } 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, activitypub_content_warning: value } ); + } } + placeholder={ __( 'Optional content warning', 'activitypub' ) } + /> + + ); +} + +registerPlugin( 'activitypub-editor-plugin', { render: EditorPlugin } ); \ No newline at end of file diff --git a/tests/test-class-activitypub-activity.php b/tests/test-class-activitypub-activity.php index f4a4fa9db..32a1f8304 100644 --- a/tests/test-class-activitypub-activity.php +++ b/tests/test-class-activitypub-activity.php @@ -34,9 +34,10 @@ function ( $mentions ) { public function test_object_transformation() { $test_array = array( - 'id' => 'https://example.com/post/123', - 'type' => 'Note', - 'content' => 'Hello world!', + 'id' => 'https://example.com/post/123', + 'type' => 'Note', + 'content' => 'Hello world!', + 'sensitive' => false, ); $object = \Activitypub\Activity\Base_Object::init_from_array( $test_array );