From 9acd0732d4aac3b68c543df6ed578b7a6c9d15e4 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 14:03:10 +0100 Subject: [PATCH 01/94] hide users that can not publish posts fixes #230 --- includes/class-activitypub.php | 5 +++++ includes/rest/class-followers.php | 3 +++ includes/rest/class-following.php | 3 +++ includes/rest/class-inbox.php | 35 +++++++++++++++++++++++++++---- includes/rest/class-outbox.php | 3 +++ includes/rest/class-webfinger.php | 2 +- 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index c2a068d..01246a4 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -38,6 +38,11 @@ class Activitypub { return $template; } + // check if user can publish posts + if ( \is_author() && ! user_can( \get_the_author_meta( 'ID' ), 'publish_posts' ) ) { + return $template; + } + if ( \is_author() ) { $json_template = \dirname( __FILE__ ) . '/../templates/author-json.php'; } elseif ( \is_singular() ) { diff --git a/includes/rest/class-followers.php b/includes/rest/class-followers.php index 34392ce..2734c78 100644 --- a/includes/rest/class-followers.php +++ b/includes/rest/class-followers.php @@ -101,6 +101,9 @@ class Followers { $params['user_id'] = array( 'required' => true, 'type' => 'integer', + 'validate_callback' => function( $param, $request, $key ) { + return user_can( $param, 'publish_posts' ); + }, ); return $params; diff --git a/includes/rest/class-following.php b/includes/rest/class-following.php index 06d3f0b..d7caff4 100644 --- a/includes/rest/class-following.php +++ b/includes/rest/class-following.php @@ -99,6 +99,9 @@ class Following { $params['user_id'] = array( 'required' => true, 'type' => 'integer', + 'validate_callback' => function( $param, $request, $key ) { + return user_can( $param, 'publish_posts' ); + }, ); return $params; diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 3408950..258658a 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -33,7 +33,7 @@ class Inbox { array( 'methods' => \WP_REST_Server::EDITABLE, 'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox_post' ), - 'args' => self::shared_inbox_request_parameters(), + 'args' => self::shared_inbox_post_parameters(), 'permission_callback' => '__return_true', ), ) @@ -46,12 +46,13 @@ class Inbox { array( 'methods' => \WP_REST_Server::EDITABLE, 'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_post' ), - 'args' => self::user_inbox_request_parameters(), + 'args' => self::user_inbox_post_parameters(), 'permission_callback' => '__return_true', ), array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_get' ), + 'args' => self::user_inbox_get_parameters(), 'permission_callback' => '__return_true', ), ) @@ -195,7 +196,7 @@ class Inbox { * * @return array list of parameters */ - public static function user_inbox_request_parameters() { + public static function user_inbox_get_parameters() { $params = array(); $params['page'] = array( @@ -205,6 +206,32 @@ class Inbox { $params['user_id'] = array( 'required' => true, 'type' => 'integer', + 'validate_callback' => function( $param, $request, $key ) { + return user_can( $param, 'publish_posts' ); + }, + ); + + return $params; + } + + /** + * The supported parameters + * + * @return array list of parameters + */ + public static function user_inbox_post_parameters() { + $params = array(); + + $params['page'] = array( + 'type' => 'integer', + ); + + $params['user_id'] = array( + 'required' => true, + 'type' => 'integer', + 'validate_callback' => function( $param, $request, $key ) { + return user_can( $param, 'publish_posts' ); + }, ); $params['id'] = array( @@ -243,7 +270,7 @@ class Inbox { * * @return array list of parameters */ - public static function shared_inbox_request_parameters() { + public static function shared_inbox_post_parameters() { $params = array(); $params['page'] = array( diff --git a/includes/rest/class-outbox.php b/includes/rest/class-outbox.php index e43ab71..7eec5ac 100644 --- a/includes/rest/class-outbox.php +++ b/includes/rest/class-outbox.php @@ -138,6 +138,9 @@ class Outbox { $params['user_id'] = array( 'required' => true, 'type' => 'integer', + 'validate_callback' => function( $param, $request, $key ) { + return user_can( $param, 'publish_posts' ); + }, ); return $params; diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php index dc84da4..0c6d5f1 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger.php @@ -59,7 +59,7 @@ class Webfinger { $user = \get_user_by( 'login', \esc_sql( $resource_identifier ) ); - if ( ! $user ) { + if ( ! $user || ! user_can( $user, 'publish_posts' ) ) { return new \WP_Error( 'activitypub_user_not_found', \__( 'User not found', 'activitypub' ), array( 'status' => 404 ) ); } From a27a4fc234dcdf3ac1484ec712257afc4b112bfd Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 14:17:31 +0100 Subject: [PATCH 02/94] remove empty `trim` fix #136 --- activitypub.php | 2 +- readme.txt | 7 ++++++- templates/blog-json.php | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/activitypub.php b/activitypub.php index fe7809e..770a277 100644 --- a/activitypub.php +++ b/activitypub.php @@ -3,7 +3,7 @@ * Plugin Name: ActivityPub * Plugin URI: https://github.com/pfefferle/wordpress-activitypub/ * Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format. - * Version: 0.14.3 + * Version: 0.15.0 * Author: Matthias Pfefferle * Author URI: https://notiz.blog/ * License: MIT diff --git a/readme.txt b/readme.txt index e4b5e13..5dd16d3 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://notiz.blog/donate/ Tags: OStatus, fediverse, activitypub, activitystream Requires at least: 4.7 Tested up to: 6.1 -Stable tag: 0.14.3 +Stable tag: 0.15.0 Requires PHP: 5.6 License: MIT License URI: http://opensource.org/licenses/MIT @@ -88,6 +88,11 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub). += 0.15.0 = + +* Enable ActivityPub only for users that can `publish_posts` + + = 0.14.3 = * Better error handling. props [@akirk](https://github.com/akirk) diff --git a/templates/blog-json.php b/templates/blog-json.php index 18e65af..4a1efc8 100644 --- a/templates/blog-json.php +++ b/templates/blog-json.php @@ -38,7 +38,7 @@ $json->manuallyApprovesFollowers = \apply_filters( 'activitypub_json_manually_ap $json->publicKey = array( 'id' => \get_home_url( '/' ) . '#main-key', 'owner' => \get_home_url( '/' ), - 'publicKeyPem' => \trim(), + 'publicKeyPem' => '', ); $json->tag = array(); From bf0b51ceb3f28cb87e612fc7e8f0012d972d15a4 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 14:43:37 +0100 Subject: [PATCH 03/94] only save public activities first step to #72 --- includes/rest/class-inbox.php | 60 ++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 258658a..5c21f43 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -437,6 +437,12 @@ class Inbox { return; } + // check if Activity is public or not + if ( ! self::is_activity_public( $object ) ) { + // @todo maybe send email + return; + } + $comment_post_id = \url_to_postid( $object['object']['inReplyTo'] ); // save only replys and reactions @@ -473,21 +479,53 @@ class Inbox { \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 ); } + /** + * Extract recipient URLs from Activity object + * + * @param array $data + * + * @return array The list of user URLs + */ public static function extract_recipients( $data ) { - $recipients = array(); - $users = array(); + $recipient_items = array(); foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) { if ( array_key_exists( $i, $data ) ) { - $recipients = array_merge( $recipients, $data[ $i ] ); + $recipient_items = array_merge( $recipient_items, $data[ $i ] ); } if ( array_key_exists( $i, $data['object'] ) ) { - $recipients = array_merge( $recipients, $data[ $i ] ); + $recipient_items = array_merge( $recipient_items, $data[ $i ] ); } } - $recipients = array_unique( $recipients ); + $recipients = array(); + + // flatten array + foreach ( $recipient_items as $recipient ) { + if ( is_array( $recipient ) ) { + // check if recipient is an object + if ( array_key_exists( 'id', $recipient ) ) { + $recipients[] = $recipient['id']; + } + } else { + $recipients[] = $recipient; + } + } + + return array_unique( $recipients ); + } + + /** + * Get local user recipients + * + * @param array $data + * + * @return array The list of local users + */ + public static function get_recipients( $data ) { + $recipients = self::extract_recipients( $data ); + $users = array(); foreach ( $recipients as $recipient ) { $user_id = \Activitypub\url_to_authorid( $recipient ); @@ -501,4 +539,16 @@ class Inbox { return $users; } + + /** + * Check if passed Activity is Public + * + * @param array $data + * @return boolean + */ + public static function is_activity_public( $data ) { + $recipients = self::extract_recipients( $data ); + + return in_array( 'https://www.w3.org/ns/activitystreams#Public', $recipients, true ); + } } From c221daef8664310ea0b705cd5ddaec3c986f6f7e Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 15:48:14 +0100 Subject: [PATCH 04/94] store permalink in post meta for trashed posts this should quick fix #16 without changing the permalink structure --- includes/class-activitypub.php | 24 ++++++++++++++++++++++++ includes/model/class-post.php | 11 ++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index 01246a4..3ff7d13 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -24,6 +24,8 @@ class Activitypub { } \add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 10, 3 ); + \add_action( 'wp_trash_post', array( '\Activitypub\Activitypub', 'trash_post' ), 10 ); + \add_action( 'untrash_post', array( '\Activitypub\Activitypub', 'untrash_post' ), 10 ); } /** @@ -185,4 +187,26 @@ class Activitypub { } return \get_comment_meta( $comment->comment_ID, 'avatar_url', true ); } + + /** + * Store permalink in meta, to send delete Activity + * + * @param string $post_id The Post ID + * + * @return void + */ + public static function trash_post( $post_id ) { + \add_post_meta( $post_id, 'activitypub_canonical_url', \get_permalink( $post_id ) ); + } + + /** + * Delete permalink from meta + * + * @param string $post_id The Post ID + * + * @return void + */ + public static function untrash_post( $post_id ) { + \delete_post_meta( $post_id, 'activitypub_canonical_url' ); + } } diff --git a/includes/model/class-post.php b/includes/model/class-post.php index e885199..02b77fe 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -68,11 +68,16 @@ class Post { } public function generate_id() { - $post = $this->post; - $permalink = \get_permalink( $post ); + $post = $this->post; + + if ( 'trash' === get_post_status( $post ) ) { + $permalink = \get_post_meta( $post, 'activitypub_canonical_url', true ); + } else { + $permalink = \get_permalink( $post ); + } // replace 'trashed' for delete activity - return \str_replace( '__trashed', '', $permalink ); + return $permalink; } public function generate_attachments() { From 6878b86922299d44e063bc13c314cca7fc830fe8 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 15:56:46 +0100 Subject: [PATCH 05/94] fix test --- includes/model/class-post.php | 1 - tests/test-class-activitypub-post.php | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 02b77fe..412de24 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -76,7 +76,6 @@ class Post { $permalink = \get_permalink( $post ); } - // replace 'trashed' for delete activity return $permalink; } diff --git a/tests/test-class-activitypub-post.php b/tests/test-class-activitypub-post.php index 4f1c74e..08e0bfd 100644 --- a/tests/test-class-activitypub-post.php +++ b/tests/test-class-activitypub-post.php @@ -16,6 +16,8 @@ class Test_Activitypub_Post extends WP_UnitTestCase { \wp_trash_post( $post ); + $post = \get_post( $post ); + $activitypub_post = new \Activitypub\Model\Post( $post ); $this->assertEquals( $permalink, $activitypub_post->get_id() ); From 10a8a2de1dfcdd8754dcca6787f83442e2cc98dc Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 16:01:59 +0100 Subject: [PATCH 06/94] use unique meta --- includes/class-activitypub.php | 6 +++--- tests/test-class-activitypub-post.php | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index 3ff7d13..658f72c 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -24,8 +24,8 @@ class Activitypub { } \add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 10, 3 ); - \add_action( 'wp_trash_post', array( '\Activitypub\Activitypub', 'trash_post' ), 10 ); - \add_action( 'untrash_post', array( '\Activitypub\Activitypub', 'untrash_post' ), 10 ); + \add_action( 'wp_trash_post', array( '\Activitypub\Activitypub', 'trash_post' ), 1 ); + \add_action( 'untrash_post', array( '\Activitypub\Activitypub', 'untrash_post' ), 1 ); } /** @@ -196,7 +196,7 @@ class Activitypub { * @return void */ public static function trash_post( $post_id ) { - \add_post_meta( $post_id, 'activitypub_canonical_url', \get_permalink( $post_id ) ); + \add_post_meta( $post_id, 'activitypub_canonical_url', \get_permalink( $post_id ), true ); } /** diff --git a/tests/test-class-activitypub-post.php b/tests/test-class-activitypub-post.php index 08e0bfd..4f1c74e 100644 --- a/tests/test-class-activitypub-post.php +++ b/tests/test-class-activitypub-post.php @@ -16,8 +16,6 @@ class Test_Activitypub_Post extends WP_UnitTestCase { \wp_trash_post( $post ); - $post = \get_post( $post ); - $activitypub_post = new \Activitypub\Model\Post( $post ); $this->assertEquals( $permalink, $activitypub_post->get_id() ); From b984319f8f6be591eff705eb57186f6b9a554978 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 16:06:41 +0100 Subject: [PATCH 07/94] hooks will be ignored --- tests/test-class-activitypub-post.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test-class-activitypub-post.php b/tests/test-class-activitypub-post.php index 4f1c74e..9e872c4 100644 --- a/tests/test-class-activitypub-post.php +++ b/tests/test-class-activitypub-post.php @@ -16,6 +16,8 @@ class Test_Activitypub_Post extends WP_UnitTestCase { \wp_trash_post( $post ); + \sleep( 3 ); + $activitypub_post = new \Activitypub\Model\Post( $post ); $this->assertEquals( $permalink, $activitypub_post->get_id() ); From 4b97d412e0cb8dede7e233bc80f61b3f1eb5f3c0 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 16:14:19 +0100 Subject: [PATCH 08/94] be sure to register hooks --- tests/test-class-activitypub-post.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test-class-activitypub-post.php b/tests/test-class-activitypub-post.php index 9e872c4..2f49c07 100644 --- a/tests/test-class-activitypub-post.php +++ b/tests/test-class-activitypub-post.php @@ -1,6 +1,8 @@ 1, @@ -16,8 +18,6 @@ class Test_Activitypub_Post extends WP_UnitTestCase { \wp_trash_post( $post ); - \sleep( 3 ); - $activitypub_post = new \Activitypub\Model\Post( $post ); $this->assertEquals( $permalink, $activitypub_post->get_id() ); From eea3f582d649aa7b3d21c64c91be4332ac5ea866 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 16:16:22 +0100 Subject: [PATCH 09/94] add hooks to the test --- tests/test-class-activitypub-post.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test-class-activitypub-post.php b/tests/test-class-activitypub-post.php index 2f49c07..3dd5000 100644 --- a/tests/test-class-activitypub-post.php +++ b/tests/test-class-activitypub-post.php @@ -1,7 +1,8 @@ Date: Tue, 27 Dec 2022 16:58:49 +0100 Subject: [PATCH 10/94] get_post_meta need the post ID --- includes/model/class-post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 412de24..cd8f119 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -71,7 +71,7 @@ class Post { $post = $this->post; if ( 'trash' === get_post_status( $post ) ) { - $permalink = \get_post_meta( $post, 'activitypub_canonical_url', true ); + $permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true ); } else { $permalink = \get_permalink( $post ); } From 6f3b7427e0803dc5489ed721e493fa7e27456383 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 16:59:04 +0100 Subject: [PATCH 11/94] added local test env using docker --- Dockerfile | 27 +++++++++++++++++++++++++++ composer.json | 10 +++++----- docker-compose-test.yml | 17 +++++++++++++++++ phpunit.xml.dist | 2 +- 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose-test.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3b056fd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM php:7.4-alpine3.13 + +RUN mkdir /app + +WORKDIR /app + +# Install Git, NPM & needed libraries +RUN apk update \ + && apk add bash git nodejs npm gettext subversion mysql mysql-client zip \ + && rm -f /var/cache/apk/* + +RUN docker-php-ext-install mysqli + +# Install Composer +RUN EXPECTED_CHECKSUM=$(curl -s https://composer.github.io/installer.sig) \ + && curl https://getcomposer.org/installer -o composer-setup.php \ + && ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" \ + && if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then >&2 echo 'ERROR: Invalid installer checksum'; rm composer-setup.php; exit 1; fi \ + && php composer-setup.php --quiet \ + && php -r "unlink('composer-setup.php');" \ + && mv composer.phar /usr/local/bin/composer + +RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \ + chmod +x wp-cli.phar && \ + mv wp-cli.phar /usr/local/bin/wp + +RUN chmod +x -R ./ diff --git a/composer.json b/composer.json index 522d8da..cdb5a32 100644 --- a/composer.json +++ b/composer.json @@ -32,10 +32,10 @@ "installer-name": "activitypub" }, "scripts": { - "test": [ - "composer install", - "bin/install-wp-tests.sh wordpress wordpress wordpress", - "vendor/bin/phpunit" - ] + "test": [ + "composer install", + "bin/install-wp-tests.sh activitypub-test root activitypub-test test-db latest true", + "vendor/bin/phpunit" + ] } } diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 0000000..a293823 --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,17 @@ +version: '2' +services: + test-db: + image: mysql:5.7 + environment: + MYSQL_DATABASE: activitypub-test + MYSQL_ROOT_PASSWORD: activitypub-test + + test-php: + build: + context: . + dockerfile: Dockerfile + links: + - test-db + volumes: + - .:/app + command: ["composer", "run-script", "test"] diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 44f0fdb..d51b296 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,7 +7,7 @@ convertWarningsToExceptions="true" > - + ./tests/ From 6ecda2b8693533ee7afdd16e10f53d5131a26890 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 17:01:10 +0100 Subject: [PATCH 12/94] fix composer indents --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index cdb5a32..67bd2d3 100644 --- a/composer.json +++ b/composer.json @@ -32,10 +32,10 @@ "installer-name": "activitypub" }, "scripts": { - "test": [ - "composer install", - "bin/install-wp-tests.sh activitypub-test root activitypub-test test-db latest true", - "vendor/bin/phpunit" - ] + "test": [ + "composer install", + "bin/install-wp-tests.sh activitypub-test root activitypub-test test-db latest true", + "vendor/bin/phpunit" + ] } } From 8a5f5758037e23dfaf093b17d10795dabd549a64 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 17:26:33 +0100 Subject: [PATCH 13/94] update readme --- README.md | 7 ++++++- readme.txt | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 465949e..55f2996 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **Tags:** OStatus, fediverse, activitypub, activitystream **Requires at least:** 4.7 **Tested up to:** 6.1 -**Stable tag:** 0.14.3 +**Stable tag:** 0.15.0 **Requires PHP:** 5.6 **License:** MIT **License URI:** http://opensource.org/licenses/MIT @@ -88,6 +88,11 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub). +### 0.15.0 ### + +* Enable ActivityPub only for users that can `publish_posts` +* Fix remote-delete + ### 0.14.3 ### * Better error handling. props [@akirk](https://github.com/akirk) diff --git a/readme.txt b/readme.txt index 5dd16d3..cf729ba 100644 --- a/readme.txt +++ b/readme.txt @@ -91,7 +91,7 @@ Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github = 0.15.0 = * Enable ActivityPub only for users that can `publish_posts` - +* Fix remote-delete = 0.14.3 = From 7ee91f1ab1b117b0d27c71e13d832c8a2b0e849c Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 17:29:34 +0100 Subject: [PATCH 14/94] remove hooks --- tests/test-class-activitypub-post.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test-class-activitypub-post.php b/tests/test-class-activitypub-post.php index 3dd5000..4f1c74e 100644 --- a/tests/test-class-activitypub-post.php +++ b/tests/test-class-activitypub-post.php @@ -1,9 +1,6 @@ 1, From 195727bc78bd7319661773393f36f86cb2ef85f5 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 27 Dec 2022 20:44:36 +0100 Subject: [PATCH 15/94] update readme --- README.md | 1 + readme.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 55f2996..4579fc4 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github ### 0.15.0 ### * Enable ActivityPub only for users that can `publish_posts` +* Persist only public Activities * Fix remote-delete ### 0.14.3 ### diff --git a/readme.txt b/readme.txt index cf729ba..4a9d2dc 100644 --- a/readme.txt +++ b/readme.txt @@ -91,6 +91,7 @@ Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github = 0.15.0 = * Enable ActivityPub only for users that can `publish_posts` +* Persist only public Activities * Fix remote-delete = 0.14.3 = From 43f347bc7c3edd2d39936e6a8f3983842146baa9 Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 6 Jan 2023 20:04:05 -0500 Subject: [PATCH 16/94] Make the excerpt code actually crop the excerpt at 400 characters. The existing implementation crops at words and may return very short strings based upon filters, or very long strings based upon user inputted excerpts. Make sure we never return a excerpt longer than we expect. --- includes/model/class-post.php | 58 ++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index e885199..84ed4fd 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -286,12 +286,62 @@ class Post { $excerpt = \apply_filters( 'the_content', $excerpt ); $excerpt = \str_replace( ']]>', ']]>', $excerpt ); - $excerpt_length = \apply_filters( 'excerpt_length', $excerpt_length ); + } + } - /** This filter is documented in wp-includes/formatting.php */ - $excerpt_more = \apply_filters( 'excerpt_more', ' [...]' ); + // Strip out any remaining tags. + $excerpt = \wp_strip_all_tags( $excerpt ); - $excerpt = \wp_trim_words( $excerpt, $excerpt_length, $excerpt_more ); + /** This filter is documented in wp-includes/formatting.php */ + $excerpt_more = \apply_filters( 'excerpt_more', ' [...]' ); + $excerpt_more_len = strlen( $excerpt_more ); + + // We now have a excerpt, but we need to check it's length, it may be longer than we want for two reasons: + // + // * The user has entered a manual excerpt which is longer that what we want. + // * No manual excerpt exists so we've used the content which might be longer than we want. + // + // Either way, let's trim it up if we need too. Also, don't forget to take into account the more indicator + // as part of the total length. + // + + // Setup a variable to hold the current excerpts length. + $current_excerpt_length = strlen( $excerpt ); + + // Setup a variable to keep track of our target length. + $target_excerpt_length = $current_excerpt_length - $excerpt_more_len; + + // Setup a variable to keep track of the current max length. + $current_expcerpt_max = $target_excerpt_length; + + // This is a loop since we can't calculate word break the string after 'the_excpert' filter has run (we would break + // all kinds of html tags), so we have to cut the excerpt down a bit at a time until we hit our target length. + while( $current_excerpt_length > $target_excerpt_length && $current_expcerpt_max > 0 ) { + // Trim the excerpt based on wordwrap() positioning. + // Note: we're using
as the linebreak just in case there are any newlines existing in the excerpt from the user. + // There won't be any
left after we've run wp_strip_all_tags() in the code above, so they're + // safe to use here. It won't be included in the final excerpt as the substr() will trim it off. + $excerpt = substr( $excerpt, 0, strpos( wordwrap( $excerpt, $current_expcerpt_max, '
' ), '
' ) ); + + // If something went wrong, or we're in a language that wordwrap() doesn't understand, + // just chop it off and don't worry about breaking in the middle of a word. + if( strlen( $excerpt ) > $excerpt_length - $excerpt_more_len ) { + $excerpt = substr( $excerpt, 0, $current_expcerpt_max ); + } + + // Add in the more indicator. + $excerpt = $excerpt . $excerpt_more; + + // Run it through the excerpt filter which will add some html tags back in. + $excerpt_filtered = apply_filters( 'the_excerpt', $excerpt ); + + // Now set the current excerpt length to this new filtered length. + $current_excerpt_length = strlen( $excerpt_filtered ); + + // Check to see if we're over the target length. + if( $current_excerpt_length > $target_excerpt_length ) { + // If so, remove 20 characters from the current max and run the loop again. + $current_expcerpt_max = $current_expcerpt_max - 20; } } From d1765b56dda78dda40b8b926114b697328c1348e Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Sat, 7 Jan 2023 15:35:14 +0100 Subject: [PATCH 17/94] configuration item for number of images to attach --- activitypub.php | 1 + includes/class-admin.php | 9 +++++++++ includes/model/class-post.php | 32 +++++++++++++++++--------------- templates/settings.php | 20 ++++++++++++++++++++ 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/activitypub.php b/activitypub.php index fe7809e..263a5ae 100644 --- a/activitypub.php +++ b/activitypub.php @@ -19,6 +19,7 @@ namespace Activitypub; * Initialize plugin */ function init() { + \defined( 'ACTIVITYPUB_NUMBER_IMAGES' ) || \define( 'ACTIVITYPUB_NUMBER_IMAGES', 3 ); \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=

)|(?<=
)|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' ); \defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '

    1. ' );
       	\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "

      %title%

      \n\n%content%\n\n

      %hashtags%

      \n\n

      %shortlink%

      " ); diff --git a/includes/class-admin.php b/includes/class-admin.php index 3354df1..ecb7f73 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -98,6 +98,15 @@ class Admin { 'default' => ACTIVITYPUB_CUSTOM_POST_CONTENT, ) ); + \register_setting( + 'activitypub', + 'activitypub_number_images', + array( + 'type' => 'integer', + 'description' => \__( 'Number of images to attach to posts.', 'activitypub' ), + 'default' => ACTIVITYPUB_NUMBER_IMAGES, + ) + ); \register_setting( 'activitypub', 'activitypub_object_type', diff --git a/includes/model/class-post.php b/includes/model/class-post.php index e885199..832f742 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -76,7 +76,7 @@ class Post { } public function generate_attachments() { - $max_images = \apply_filters( 'activitypub_max_images', 3 ); + $max_images = intval( \apply_filters( 'activitypub_max_images', \get_option( 'activitypub_number_images', ACTIVITYPUB_NUMBER_IMAGES ) ) ); $images = array(); @@ -94,20 +94,22 @@ class Post { $max_images--; } // then list any image attachments - $query = new \WP_Query( - array( - 'post_parent' => $id, - 'post_status' => 'inherit', - 'post_type' => 'attachment', - 'post_mime_type' => 'image', - 'order' => 'ASC', - 'orderby' => 'menu_order ID', - 'posts_per_page' => $max_images, - ) - ); - foreach ( $query->get_posts() as $attachment ) { - if ( ! \in_array( $attachment->ID, $image_ids, true ) ) { - $image_ids[] = $attachment->ID; + if ( $max_images > 0 ) { + $query = new \WP_Query( + array( + 'post_parent' => $id, + 'post_status' => 'inherit', + 'post_type' => 'attachment', + 'post_mime_type' => 'image', + 'order' => 'ASC', + 'orderby' => 'menu_order ID', + 'posts_per_page' => $max_images, + ) + ); + foreach ( $query->get_posts() as $attachment ) { + if ( ! \in_array( $attachment->ID, $image_ids, true ) ) { + $image_ids[] = $attachment->ID; + } } } diff --git a/templates/settings.php b/templates/settings.php index 4bcb358..90a1503 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -74,6 +74,26 @@

      Let me know if you miss a template pattern.', 'activitypub' ), 'default' ); ?>

      + + + + + + +

      + %s', 'activitypub' ), + \esc_html( ACTIVITYPUB_NUMBER_IMAGES ) + ), + 'default' + ); + ?> +

      + + From 1e7e6bba28ddff13ac54625d26b825e98c6a7a04 Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Thu, 12 Jan 2023 21:29:21 +0100 Subject: [PATCH 18/94] standardise and improve name of attachment setting --- activitypub.php | 2 +- includes/class-admin.php | 4 ++-- includes/model/class-post.php | 2 +- templates/settings.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/activitypub.php b/activitypub.php index 263a5ae..bd91ab3 100644 --- a/activitypub.php +++ b/activitypub.php @@ -19,7 +19,7 @@ namespace Activitypub; * Initialize plugin */ function init() { - \defined( 'ACTIVITYPUB_NUMBER_IMAGES' ) || \define( 'ACTIVITYPUB_NUMBER_IMAGES', 3 ); + \defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 ); \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=

      )|(?<=
      )|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' ); \defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '

        1. ' );
           	\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "

          %title%

          \n\n%content%\n\n

          %hashtags%

          \n\n

          %shortlink%

          " ); diff --git a/includes/class-admin.php b/includes/class-admin.php index ecb7f73..0481cbb 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -100,11 +100,11 @@ class Admin { ); \register_setting( 'activitypub', - 'activitypub_number_images', + 'activitypub_max_image_attachments', array( 'type' => 'integer', 'description' => \__( 'Number of images to attach to posts.', 'activitypub' ), - 'default' => ACTIVITYPUB_NUMBER_IMAGES, + 'default' => ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS, ) ); \register_setting( diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 832f742..91ff03b 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -76,7 +76,7 @@ class Post { } public function generate_attachments() { - $max_images = intval( \apply_filters( 'activitypub_max_images', \get_option( 'activitypub_number_images', ACTIVITYPUB_NUMBER_IMAGES ) ) ); + $max_images = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) ); $images = array(); diff --git a/templates/settings.php b/templates/settings.php index 90a1503..1c453c8 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -79,14 +79,14 @@ - +

          %s', 'activitypub' ), - \esc_html( ACTIVITYPUB_NUMBER_IMAGES ) + \esc_html( ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ), 'default' ); From 6992fbbe226eb1753d34637cb50c10b9009d778c Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 12 Jan 2023 21:55:33 +0100 Subject: [PATCH 19/94] simplified `ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS` --- includes/model/class-post.php | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 91ff03b..74b2469 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -82,7 +82,7 @@ class Post { // max images can't be negative or zero if ( $max_images <= 0 ) { - $max_images = 1; + return $images; } $id = $this->post->ID; @@ -94,22 +94,20 @@ class Post { $max_images--; } // then list any image attachments - if ( $max_images > 0 ) { - $query = new \WP_Query( - array( - 'post_parent' => $id, - 'post_status' => 'inherit', - 'post_type' => 'attachment', - 'post_mime_type' => 'image', - 'order' => 'ASC', - 'orderby' => 'menu_order ID', - 'posts_per_page' => $max_images, - ) - ); - foreach ( $query->get_posts() as $attachment ) { - if ( ! \in_array( $attachment->ID, $image_ids, true ) ) { - $image_ids[] = $attachment->ID; - } + $query = new \WP_Query( + array( + 'post_parent' => $id, + 'post_status' => 'inherit', + 'post_type' => 'attachment', + 'post_mime_type' => 'image', + 'order' => 'ASC', + 'orderby' => 'menu_order ID', + 'posts_per_page' => $max_images, + ) + ); + foreach ( $query->get_posts() as $attachment ) { + if ( ! \in_array( $attachment->ID, $image_ids, true ) ) { + $image_ids[] = $attachment->ID; } } From c06a7d44cf72acc9c54922a59f6a47b8a8bf4d5f Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 12 Jan 2023 22:21:48 +0100 Subject: [PATCH 20/94] re-added max_images check props @mexon --- includes/model/class-post.php | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 74b2469..512a003 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -88,26 +88,30 @@ class Post { $id = $this->post->ID; $image_ids = array(); + // list post thumbnail first if this post has one if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) { $image_ids[] = \get_post_thumbnail_id( $id ); $max_images--; } - // then list any image attachments - $query = new \WP_Query( - array( - 'post_parent' => $id, - 'post_status' => 'inherit', - 'post_type' => 'attachment', - 'post_mime_type' => 'image', - 'order' => 'ASC', - 'orderby' => 'menu_order ID', - 'posts_per_page' => $max_images, - ) - ); - foreach ( $query->get_posts() as $attachment ) { - if ( ! \in_array( $attachment->ID, $image_ids, true ) ) { - $image_ids[] = $attachment->ID; + + if ( $max_images > 0 ) { + // then list any image attachments + $query = new \WP_Query( + array( + 'post_parent' => $id, + 'post_status' => 'inherit', + 'post_type' => 'attachment', + 'post_mime_type' => 'image', + 'order' => 'ASC', + 'orderby' => 'menu_order ID', + 'posts_per_page' => $max_images, + ) + ); + foreach ( $query->get_posts() as $attachment ) { + if ( ! \in_array( $attachment->ID, $image_ids, true ) ) { + $image_ids[] = $attachment->ID; + } } } From 006b3eef3eae5c0de479617d0890da39741e9e31 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 13 Jan 2023 08:56:38 +0100 Subject: [PATCH 21/94] use number input field instead of textarea --- templates/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/settings.php b/templates/settings.php index 1c453c8..9450195 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -79,7 +79,7 @@ - +

          Date: Fri, 13 Jan 2023 09:18:25 +0100 Subject: [PATCH 22/94] add changelog --- README.md | 4 ++++ readme.txt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 4579fc4..4f2d78d 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,10 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub). +### v.next ### + +* Add configuration item for number of images to attach. props [@mexon](https://github.com/mexon) + ### 0.15.0 ### * Enable ActivityPub only for users that can `publish_posts` diff --git a/readme.txt b/readme.txt index 4a9d2dc..7ef54de 100644 --- a/readme.txt +++ b/readme.txt @@ -88,6 +88,10 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub). += v.next = + +* Add configuration item for number of images to attach. props [@mexon](https://github.com/mexon) + = 0.15.0 = * Enable ActivityPub only for users that can `publish_posts` From 5dac683c4868cbc91510dc08076aabeec4c31409 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 13 Jan 2023 09:19:02 +0100 Subject: [PATCH 23/94] switch to constants to define pathes --- includes/class-activitypub.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index 658f72c..ea3532a 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -46,11 +46,11 @@ class Activitypub { } if ( \is_author() ) { - $json_template = \dirname( __FILE__ ) . '/../templates/author-json.php'; + $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/author-json.php'; } elseif ( \is_singular() ) { - $json_template = \dirname( __FILE__ ) . '/../templates/post-json.php'; + $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/post-json.php'; } elseif ( \is_home() ) { - $json_template = \dirname( __FILE__ ) . '/../templates/blog-json.php'; + $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/blog-json.php'; } global $wp_query; From 27aeaeb4e40893fed62380ef4f2391f36a8ddd6b Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 13 Jan 2023 11:02:16 -0500 Subject: [PATCH 24/94] Fix incorrect setting of target length and spelling mistake. --- includes/model/class-post.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 84ed4fd..7f9c9e9 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -309,24 +309,24 @@ class Post { $current_excerpt_length = strlen( $excerpt ); // Setup a variable to keep track of our target length. - $target_excerpt_length = $current_excerpt_length - $excerpt_more_len; + $target_excerpt_length = $excerpt_length - $excerpt_more_len; // Setup a variable to keep track of the current max length. - $current_expcerpt_max = $target_excerpt_length; + $current_excerpt_max = $target_excerpt_length; // This is a loop since we can't calculate word break the string after 'the_excpert' filter has run (we would break // all kinds of html tags), so we have to cut the excerpt down a bit at a time until we hit our target length. - while( $current_excerpt_length > $target_excerpt_length && $current_expcerpt_max > 0 ) { + while( $current_excerpt_length > $target_excerpt_length && $current_excerpt_max > 0 ) { // Trim the excerpt based on wordwrap() positioning. // Note: we're using
          as the linebreak just in case there are any newlines existing in the excerpt from the user. // There won't be any
          left after we've run wp_strip_all_tags() in the code above, so they're // safe to use here. It won't be included in the final excerpt as the substr() will trim it off. - $excerpt = substr( $excerpt, 0, strpos( wordwrap( $excerpt, $current_expcerpt_max, '
          ' ), '
          ' ) ); + $excerpt = substr( $excerpt, 0, strpos( wordwrap( $excerpt, $current_excerpt_max, '
          ' ), '
          ' ) ); // If something went wrong, or we're in a language that wordwrap() doesn't understand, // just chop it off and don't worry about breaking in the middle of a word. if( strlen( $excerpt ) > $excerpt_length - $excerpt_more_len ) { - $excerpt = substr( $excerpt, 0, $current_expcerpt_max ); + $excerpt = substr( $excerpt, 0, $current_excerpt_max ); } // Add in the more indicator. @@ -341,7 +341,7 @@ class Post { // Check to see if we're over the target length. if( $current_excerpt_length > $target_excerpt_length ) { // If so, remove 20 characters from the current max and run the loop again. - $current_expcerpt_max = $current_expcerpt_max - 20; + $current_excerpt_max = $current_excerpt_max - 20; } } From e7d3cf9d68cc01f65911f6a69a3b4644ece2f1ca Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 13 Jan 2023 15:47:13 -0500 Subject: [PATCH 25/94] Convert template codes to shortcodes. As well as add new shortcodes for: [ap_hashcats] - The post's categories as hashtags [ap_image] - The URL for the post's featured image, full size [ap_thumbnail] - The URL for the post's featured image thumbnail size [ap_author] - The author's name [ap_authorurl] - The URL to the author's profile page [ap_date] - The post's date [ap_time] - The post's time [ap_datetime] - The post's date/time formated as "date @ time" [ap_blogurl] - The URL to the site [ap_blogname] - The name of the site [ap_blogdesc] - The description of the site --- activitypub.php | 2 +- includes/model/class-post.php | 232 +++++++++++++++++++++++++++++++--- templates/settings.php | 30 ++++- 3 files changed, 239 insertions(+), 25 deletions(-) diff --git a/activitypub.php b/activitypub.php index d9f2a61..b347814 100644 --- a/activitypub.php +++ b/activitypub.php @@ -22,7 +22,7 @@ function init() { \defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 ); \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=

          )|(?<=
          )|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' ); \defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '

            1. ' );
              -	\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "

              %title%

              \n\n%content%\n\n

              %hashtags%

              \n\n

              %shortlink%

              " ); + \defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "

              [ap_title]

              \n\n[ap_content]\n\n

              [ap_hashtags]

              \n\n

              [ap_shortlink]

              " ); \define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); \define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); \define( 'ACTIVITYPUB_PLUGIN_FILE', plugin_dir_path( __FILE__ ) . '/' . basename( __FILE__ ) ); diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 80c4d25..a062829 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -26,6 +26,12 @@ class Post { $this->attachments = $this->generate_attachments(); $this->tags = $this->generate_tags(); $this->object_type = $this->generate_object_type(); + + $shortcodes = array( 'ap_title', 'ap_excerpt', 'ap_content', 'ap_permalink', 'ap_shortlink', 'ap_hashtags', 'ap_thumbnail', 'ap_image', 'ap_hashcats', 'ap_author', 'ap_authorurl', 'ap_blogurl', 'ap_blogname', 'ap_blogdesc', 'ap_date', 'ap_time', 'ap_datetime' ); + + foreach( $shortcodes as $tag ) { + add_shortcode( $tag, [ $this, 'shortcode_content' ] ); + } } public function __call( $method, $params ) { @@ -226,18 +232,88 @@ class Post { return $object_type; } + public function shortcode_content( $atts, $content, $tag ) { + $tag = strtolower( $tag ); + $post = $this->post; + + switch( $tag ) { + case 'ap_title': + echo \get_the_title( $post->ID ); + + break; + case 'ap_excerpt': + echo $this->get_the_post_excerpt(); + + break; + case 'ap_content': + echo $this->get_the_post_content(); + + break; + case 'ap_permalink': + echo $this->get_the_post_link( 'permalink' ); + + break; + case 'ap_shortlink': + echo $this->get_the_post_link( 'shortlink' ); + + break; + case 'ap_hashtags': + echo $this->get_the_post_hashtags(); + + break; + case 'ap_thumbnail': + echo $this->get_the_post_image( 'thumbnail' ); + + break; + case 'ap_image': + echo $this->get_the_post_image(); + + break; + case 'ap_hashcats': + echo $this->get_the_post_categories(); + + break; + case 'ap_author': + echo $this->get_the_post_author(); + + break; + case 'ap_authorurl': + echo $this->get_the_post_author_url(); + + break; + case 'ap_blogurl': + echo \get_bloginfo('url'); + + break; + case 'ap_blogname': + echo \get_bloginfo('name'); + + break; + case 'ap_blogdesc': + echo \get_bloginfo('description'); + + break; + case 'ap_date': + echo $this->get_the_post_date( 'time' ); + + break; + case 'ap_time': + echo $this->get_the_post_date( 'date' ); + + break; + case 'ap_datetime': + echo $this->get_the_post_date( 'both' ); + + break; + } + } + public function generate_the_content() { $post = $this->post; $content = $this->get_post_content_template(); - $content = \str_replace( '%title%', \get_the_title( $post->ID ), $content ); - $content = \str_replace( '%excerpt%', $this->get_the_post_excerpt(), $content ); - $content = \str_replace( '%content%', $this->get_the_post_content(), $content ); - $content = \str_replace( '%permalink%', $this->get_the_post_link( 'permalink' ), $content ); - $content = \str_replace( '%shortlink%', $this->get_the_post_link( 'shortlink' ), $content ); - $content = \str_replace( '%hashtags%', $this->get_the_post_hashtags(), $content ); - // backwards compatibility - $content = \str_replace( '%tags%', $this->get_the_post_hashtags(), $content ); + // Fill in the shortcodes. + $content = do_shortcode( $content ); $content = \trim( \preg_replace( '/[\r\n]{2,}/', '', $content ) ); @@ -255,18 +331,36 @@ class Post { public function get_post_content_template() { if ( 'excerpt' === \get_option( 'activitypub_post_content_type', 'content' ) ) { - return "%excerpt%\n\n

              %permalink%

              "; + return "[ap_excerpt]\n\n

              [ap_permalink]

              "; } if ( 'title' === \get_option( 'activitypub_post_content_type', 'content' ) ) { - return "

              %title%

              \n\n

              %permalink%

              "; + return "

              [ap_title]

              \n\n

              [ap_permalink]

              "; } if ( 'content' === \get_option( 'activitypub_post_content_type', 'content' ) ) { - return "%content%\n\n

              %hashtags%

              \n\n

              %permalink%

              "; + return "[ap_content]\n\n

              [ap_hashtags]

              \n\n

              [ap_permalink]

              "; } - return \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT ); + // Get the custom template. + $content = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT ); + $old_content = $content; + + // Backwards compatibility, templates are now deprecated convert to shortcodes instead. + $content = \str_replace( '%title%', '[ap_title]', $content ); + $content = \str_replace( '%excerpt%', '[ap_excerpt]', $content ); + $content = \str_replace( '%content%', '[ap_content]', $content ); + $content = \str_replace( '%permalink%', '[ap_permalink]', $content ); + $content = \str_replace( '%shortlink%', '[ap_shortlink]', $content ); + $content = \str_replace( '%hashtags%', '[ap_hashtags]', $content ); + $content = \str_replace( '%tags%', '[ap_hashtags]', $content ); + + // Store the new template if required. + if( $content != $old_content ) { + \update_option( 'activitypub_custom_post_content', $content ); + } + + return $content; } /** @@ -322,8 +416,7 @@ class Post { /** * Adds a backlink to the post/summary content * - * @param string $content - * @param WP_Post $post + * @param string $type * * @return string */ @@ -344,9 +437,6 @@ class Post { /** * Adds all tags as hashtags to the post/summary content * - * @param string $content - * @param WP_Post $post - * * @return string */ public function get_the_post_hashtags() { @@ -365,4 +455,112 @@ class Post { return \implode( ' ', $hash_tags ); } + + /** + * Adds the featured image url to the post/summary content + * + * @param string $size + * + * @return string + */ + public function get_the_post_image( $size = 'full' ) { + $post = $this->post; + + if( $size == '' ) { $size = 'full'; } + + $image = \get_the_post_thumbnail_url( $post->ID, $size ); + + if ( ! $image ) { + return ''; + } + + return $image; + } + + /** + * Adds all categories as hashtags to the post/summary content + * + * @return string + */ + public function get_the_post_categories() { + $post = $this->post; + $categories = \get_the_category( $post->ID ); + + if ( ! $categories ) { + return ''; + } + + $hash_tags = array(); + + foreach ( $categories as $category ) { + $hash_tags[] = \sprintf( '
              ', \get_category_link( $category ), $category->slug ); + } + + return \implode( ' ', $hash_tags ); + } + + /** + * Adds author to the post/summary content + * + * @return string + */ + public function get_the_post_author() { + $post = $this->post; + $name = \get_the_author_meta( 'display_name', $post->post_author ); + + if ( ! $name ) { + return ''; + } + + return $name; + } + + /** + * Adds author's url to the post/summary content + * + * @return string + */ + public function get_the_post_profile_url() { + $post = $this->post; + $url = \get_the_author_meta( 'user_url', $post->post_author ); + + if ( ! $url ) { + return ''; + } + + return $url; + } + + /** + * Adds the post date/time to the post/summary content + * + * @param string display + * + * @return string + */ + public function get_the_post_date( $display = 'both' ) { + $post = $this->post; + $datetime = \get_post_datetime( $post ); + $dateformat = \get_option( 'date_format' ); + $timeformat = \get_option( 'time_format' ); + + switch( $display ) { + case 'date': + $date = $datetime->format( $dateformat ); + break; + case 'time': + $date = $datetime->format( $timeformat ); + break; + default: + $date = $datetime->format( $dateformat . ' @ ' . $timeformat ); + break; + } + + if ( ! $date ) { + return ''; + } + + return $date; + } + } diff --git a/templates/settings.php b/templates/settings.php index 9450195..e8d6529 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -7,6 +7,10 @@ 'welcome' => '', ) ); + +$image_sizes = wp_get_registered_image_subsizes(); +$thumnail_size = $image_sizes['thumbnail']['width'] . 'x' . $image_sizes['thumbnail']['height'] . ' px'; + ?>
              @@ -56,17 +60,29 @@

              - +
                -
              • %title% -
              • -
              • %content% -
              • -
              • %excerpt% -
              • -
              • %permalink% -
              • +
              • [ap_title] -
              • +
              • [ap_content] -
              • +
              • [ap_excerpt] -
              • +
              • [ap_permalink] -
              • -
              • %shortlink% - Hum, to prettify the Shortlinks', 'activitypub' ), 'default' ); ?>
              • -
              • %hashtags% -
              • +
              • [ap_shortlink] - Hum, to prettify the Shortlinks', 'activitypub' ), 'default' ); ?>
              • +
              • [ap_hashtags] -
              • +
              • [ap_hashcats] -
              • +
              • [ap_image] -
              • +
              • [ap_thumbnail] -
              • +
              • [ap_author] -
              • +
              • [ap_authorurl] -
              • +
              • [ap_date] -
              • +
              • [ap_time] -
              • +
              • [ap_datetime] -
              • +
              • [ap_blogurl] -
              • +
              • [ap_blogname] -
              • +
              • [ap_blogdesc] -
              +

              From bf6cf24b17c00fdde493fdbf3b1cc3448b3c65d1 Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 13 Jan 2023 16:11:52 -0500 Subject: [PATCH 26/94] Add length to excerpt shortcode. --- activitypub.php | 1 + includes/model/class-post.php | 12 ++++++++++-- templates/settings.php | 14 ++++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/activitypub.php b/activitypub.php index b347814..6ae9dbc 100644 --- a/activitypub.php +++ b/activitypub.php @@ -19,6 +19,7 @@ namespace Activitypub; * Initialize plugin */ function init() { + \defined( 'ACTIVITYPUB_EXCERPT_LENGTH' ) || \define( 'ACTIVITYPUB_EXCERPT_LENGTH', 400 ); \defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 ); \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=

              )|(?<=
              )|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' ); \defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '

                1. ' );
                  diff --git a/includes/model/class-post.php b/includes/model/class-post.php
                  index a062829..f9bde0d 100644
                  --- a/includes/model/class-post.php
                  +++ b/includes/model/class-post.php
                  @@ -242,7 +242,15 @@ class Post {
                   
                   				break;
                   			case 'ap_excerpt':
                  -				echo $this->get_the_post_excerpt();
                  +				$length = ACTIVITYPUB_EXCERPT_LENGTH;
                  +
                  +				if( is_array( $atts ) && array_key_exists( 'length', $atts ) ) {
                  +					$length = intval( $atts['length'] );
                  +				}
                  +
                  +				if( $length == 0 ) { $length = ACTIVITYPUB_EXCERPT_LENGTH; }
                  +
                  +				echo $this->get_the_post_excerpt( $length );
                   
                   				break;
                   			case 'ap_content':
                  @@ -370,7 +378,7 @@ class Post {
                   	 *
                   	 * @return string The excerpt.
                   	 */
                  -	public function get_the_post_excerpt( $excerpt_length = 400 ) {
                  +	public function get_the_post_excerpt( $excerpt_length = ACTIVITYPUB_EXCERPT_LENGTH ) {
                   		$post = $this->post;
                   
                   		$excerpt = \get_post_field( 'post_excerpt', $post );
                  diff --git a/templates/settings.php b/templates/settings.php
                  index e8d6529..23242ae 100644
                  --- a/templates/settings.php
                  +++ b/templates/settings.php
                  @@ -62,14 +62,16 @@ $thumnail_size = $image_sizes['thumbnail']['width'] . 'x' . $image_sizes['thumbn
                   							
                  +

                  +

                    -
                  • [ap_title] -
                  • -
                  • [ap_content] -
                  • -
                  • [ap_excerpt] -
                  • -
                  • [ap_permalink] -
                  • +
                  • [ap_title] -
                  • +
                  • [ap_content] -
                  • +
                  • [ap_excerpt lenght=nnn] -
                  • +
                  • [ap_permalink] -
                  • -
                  • [ap_shortlink] - Hum, to prettify the Shortlinks', 'activitypub' ), 'default' ); ?>
                  • -
                  • [ap_hashtags] -
                  • +
                  • [ap_shortlink] - Hum, to prettify the Shortlinks', 'activitypub' ), 'default' ); ?>
                  • +
                  • [ap_hashtags] -
                  • [ap_hashcats] -
                  • [ap_image] -
                  • [ap_thumbnail] -
                  • From e4eda45e9f4e906af5026bda5cebdea52ccb3bf2 Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 13 Jan 2023 20:17:51 -0500 Subject: [PATCH 27/94] Give the notice boxes some margin so they have some space. --- assets/css/activitypub-admin.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/css/activitypub-admin.css b/assets/css/activitypub-admin.css index a6cc390..cd1808c 100644 --- a/assets/css/activitypub-admin.css +++ b/assets/css/activitypub-admin.css @@ -1,6 +1,7 @@ .settings_page_activitypub .notice { max-width: 800px; - margin: 0 auto; + margin: auto; + margin-top: 10px; } .activitypub-settings-header { From 2f0dbde2a41ac71240fb1f30521b761499b033d7 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 16 Jan 2023 15:28:10 +0100 Subject: [PATCH 28/94] fix phpcs issues --- includes/model/class-post.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index aa207e9..9140b07 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -302,7 +302,7 @@ class Post { /** This filter is documented in wp-includes/formatting.php */ $excerpt_more = \apply_filters( 'excerpt_more', ' [...]' ); - $excerpt_more_len = strlen( $excerpt_more ); + $excerpt_more_len = strlen( $excerpt_more ); // We now have a excerpt, but we need to check it's length, it may be longer than we want for two reasons: // @@ -324,7 +324,7 @@ class Post { // This is a loop since we can't calculate word break the string after 'the_excpert' filter has run (we would break // all kinds of html tags), so we have to cut the excerpt down a bit at a time until we hit our target length. - while( $current_excerpt_length > $target_excerpt_length && $current_excerpt_max > 0 ) { + while ( $current_excerpt_length > $target_excerpt_length && $current_excerpt_max > 0 ) { // Trim the excerpt based on wordwrap() positioning. // Note: we're using
                    as the linebreak just in case there are any newlines existing in the excerpt from the user. // There won't be any
                    left after we've run wp_strip_all_tags() in the code above, so they're @@ -333,7 +333,7 @@ class Post { // If something went wrong, or we're in a language that wordwrap() doesn't understand, // just chop it off and don't worry about breaking in the middle of a word. - if( strlen( $excerpt ) > $excerpt_length - $excerpt_more_len ) { + if ( strlen( $excerpt ) > $excerpt_length - $excerpt_more_len ) { $excerpt = substr( $excerpt, 0, $current_excerpt_max ); } @@ -347,7 +347,7 @@ class Post { $current_excerpt_length = strlen( $excerpt_filtered ); // Check to see if we're over the target length. - if( $current_excerpt_length > $target_excerpt_length ) { + if ( $current_excerpt_length > $target_excerpt_length ) { // If so, remove 20 characters from the current max and run the loop again. $current_excerpt_max = $current_excerpt_max - 20; } From caea1ecbed39f6d77144ebc26c6ff633f0e7cda6 Mon Sep 17 00:00:00 2001 From: Greg Date: Mon, 16 Jan 2023 10:26:38 -0500 Subject: [PATCH 29/94] Make sure we have a post before using it to set class variables with. --- includes/model/class-post.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index f9bde0d..ef998bd 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -17,15 +17,17 @@ class Post { private $object_type; public function __construct( $post = null ) { - $this->post = \get_post( $post ); + if( $post ) { + $this->post = \get_post( $post ); - $this->post_author = $this->post->post_author; - $this->id = $this->generate_id(); - $this->summary = $this->generate_the_title(); - $this->content = $this->generate_the_content(); - $this->attachments = $this->generate_attachments(); - $this->tags = $this->generate_tags(); - $this->object_type = $this->generate_object_type(); + $this->post_author = $this->post->post_author; + $this->id = $this->generate_id(); + $this->summary = $this->generate_the_title(); + $this->content = $this->generate_the_content(); + $this->attachments = $this->generate_attachments(); + $this->tags = $this->generate_tags(); + $this->object_type = $this->generate_object_type(); + } $shortcodes = array( 'ap_title', 'ap_excerpt', 'ap_content', 'ap_permalink', 'ap_shortlink', 'ap_hashtags', 'ap_thumbnail', 'ap_image', 'ap_hashcats', 'ap_author', 'ap_authorurl', 'ap_blogurl', 'ap_blogname', 'ap_blogdesc', 'ap_date', 'ap_time', 'ap_datetime' ); From 4a17bb4ea7eb891ea0e3d1e45c21fda7e11ea100 Mon Sep 17 00:00:00 2001 From: Greg Date: Mon, 16 Jan 2023 10:27:27 -0500 Subject: [PATCH 30/94] Separate the shortcode upgrade function and call it in the settings. --- includes/class-admin.php | 3 +++ includes/model/class-post.php | 49 +++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/includes/class-admin.php b/includes/class-admin.php index 0481cbb..df79d0b 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -50,6 +50,9 @@ class Admin { switch ( $tab ) { case 'settings': + $post_model = new \Activitypub\Model\Post(); + $post_model->upgrade_post_content_template(); + \load_template( \dirname( __FILE__ ) . '/../templates/settings.php' ); break; case 'welcome': diff --git a/includes/model/class-post.php b/includes/model/class-post.php index ef998bd..b2bf2d1 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -234,6 +234,14 @@ class Post { return $object_type; } + /** + * Outputs the shortcode content. + * + * @param array $atts the attributes of the shortcode + * @param string $content the content between opening and closing shortcodes + * @param string $tag the name of the shortcode being processed + * + */ public function shortcode_content( $atts, $content, $tag ) { $tag = strtolower( $tag ); $post = $this->post; @@ -318,6 +326,11 @@ class Post { } } + /** + * Generates the content for the activitypub item. + * + * @return string the content + */ public function generate_the_content() { $post = $this->post; $content = $this->get_post_content_template(); @@ -339,6 +352,11 @@ class Post { return $decoded_content; } + /** + * Gets the template to use to generate the content of the activitypub item. + * + * @return string the template + */ public function get_post_content_template() { if ( 'excerpt' === \get_option( 'activitypub_post_content_type', 'content' ) ) { return "[ap_excerpt]\n\n

                    [ap_permalink]

                    "; @@ -352,11 +370,32 @@ class Post { return "[ap_content]\n\n

                    [ap_hashtags]

                    \n\n

                    [ap_permalink]

                    "; } - // Get the custom template. - $content = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT ); - $old_content = $content; + // Upgrade from old template codes to shortcodes. + $content = $this->upgrade_post_content_template(); - // Backwards compatibility, templates are now deprecated convert to shortcodes instead. + return $content; + } + + /** + * Updates the custom template to use shortcodes instead of the deprecated templates. + * + * @return string the updated template content + */ + public function upgrade_post_content_template() { + // Get the custom template. + $old_content = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT ); + + // If the old content exists but is a blank string, we're going to need a flag to updated it even + // after setting it to the default contents. + $need_update = false; + + // If the old contents is blank, use the defaults. + if( $old_content == "" ) { $old_content = ACTIVITYPUB_CUSTOM_POST_CONTENT; $need_update = true; } + + // Set the new content to be the old content. + $content = $old_content; + + // Convert old templates to shortcodes. $content = \str_replace( '%title%', '[ap_title]', $content ); $content = \str_replace( '%excerpt%', '[ap_excerpt]', $content ); $content = \str_replace( '%content%', '[ap_content]', $content ); @@ -366,7 +405,7 @@ class Post { $content = \str_replace( '%tags%', '[ap_hashtags]', $content ); // Store the new template if required. - if( $content != $old_content ) { + if( $content != $old_content || $need_update ) { \update_option( 'activitypub_custom_post_content', $content ); } From bc8e46e1215929b932dbf20fffb157b316f67d79 Mon Sep 17 00:00:00 2001 From: Greg Date: Mon, 16 Jan 2023 12:51:18 -0500 Subject: [PATCH 31/94] Fix shortcode output. --- includes/model/class-post.php | 49 +++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index b2bf2d1..919b511 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -29,11 +29,6 @@ class Post { $this->object_type = $this->generate_object_type(); } - $shortcodes = array( 'ap_title', 'ap_excerpt', 'ap_content', 'ap_permalink', 'ap_shortlink', 'ap_hashtags', 'ap_thumbnail', 'ap_image', 'ap_hashcats', 'ap_author', 'ap_authorurl', 'ap_blogurl', 'ap_blogname', 'ap_blogdesc', 'ap_date', 'ap_time', 'ap_datetime' ); - - foreach( $shortcodes as $tag ) { - add_shortcode( $tag, [ $this, 'shortcode_content' ] ); - } } public function __call( $method, $params ) { @@ -246,9 +241,11 @@ class Post { $tag = strtolower( $tag ); $post = $this->post; + $text = ''; + switch( $tag ) { case 'ap_title': - echo \get_the_title( $post->ID ); + $text = \get_the_title( $post->ID ); break; case 'ap_excerpt': @@ -260,70 +257,72 @@ class Post { if( $length == 0 ) { $length = ACTIVITYPUB_EXCERPT_LENGTH; } - echo $this->get_the_post_excerpt( $length ); + $text = $this->get_the_post_excerpt( $length ); break; case 'ap_content': - echo $this->get_the_post_content(); + $text = $this->get_the_post_content(); break; case 'ap_permalink': - echo $this->get_the_post_link( 'permalink' ); + $text = $this->get_the_post_link( 'permalink' ); break; case 'ap_shortlink': - echo $this->get_the_post_link( 'shortlink' ); + $text = $this->get_the_post_link( 'shortlink' ); break; case 'ap_hashtags': - echo $this->get_the_post_hashtags(); + $text = $this->get_the_post_hashtags(); break; case 'ap_thumbnail': - echo $this->get_the_post_image( 'thumbnail' ); + $text = $this->get_the_post_image( 'thumbnail' ); break; case 'ap_image': - echo $this->get_the_post_image(); + $text = $this->get_the_post_image(); break; case 'ap_hashcats': - echo $this->get_the_post_categories(); + $text = $this->get_the_post_categories(); break; case 'ap_author': - echo $this->get_the_post_author(); + $text = $this->get_the_post_author(); break; case 'ap_authorurl': - echo $this->get_the_post_author_url(); + $text = $this->get_the_post_author_url(); break; case 'ap_blogurl': - echo \get_bloginfo('url'); + $text = \get_bloginfo('url'); break; case 'ap_blogname': - echo \get_bloginfo('name'); + $text = \get_bloginfo('name'); break; case 'ap_blogdesc': - echo \get_bloginfo('description'); + $text = \get_bloginfo('description'); break; case 'ap_date': - echo $this->get_the_post_date( 'time' ); + $text = $this->get_the_post_date( 'time' ); break; case 'ap_time': - echo $this->get_the_post_date( 'date' ); + $text = $this->get_the_post_date( 'date' ); break; case 'ap_datetime': - echo $this->get_the_post_date( 'both' ); + $text = $this->get_the_post_date( 'both' ); break; } + + return $text; } /** @@ -335,6 +334,12 @@ class Post { $post = $this->post; $content = $this->get_post_content_template(); + $shortcodes = array( 'ap_title', 'ap_excerpt', 'ap_content', 'ap_permalink', 'ap_shortlink', 'ap_hashtags', 'ap_thumbnail', 'ap_image', 'ap_hashcats', 'ap_author', 'ap_authorurl', 'ap_blogurl', 'ap_blogname', 'ap_blogdesc', 'ap_date', 'ap_time', 'ap_datetime' ); + + foreach( $shortcodes as $tag ) { + add_shortcode( $tag, [ $this, 'shortcode_content' ] ); + } + // Fill in the shortcodes. $content = do_shortcode( $content ); From 47bd6eb3b4a1ebda67cae83f299f2014a2058617 Mon Sep 17 00:00:00 2001 From: Greg Date: Mon, 16 Jan 2023 13:19:26 -0500 Subject: [PATCH 32/94] Move the activitypub endpoint rule to the main rewrite addition function. This is for two reasons: - No need to add the endpoint every time the plugin loads. - The old code didn't flush the rewrite rules, making the endpoint non-functional until something did (like the user saving the permalink settings) --- activitypub.php | 2 ++ includes/class-activitypub.php | 8 -------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/activitypub.php b/activitypub.php index d9f2a61..9dd8f82 100644 --- a/activitypub.php +++ b/activitypub.php @@ -116,6 +116,8 @@ function add_rewrite_rules() { \add_rewrite_rule( '^.well-known/nodeinfo', 'index.php?rest_route=/activitypub/1.0/nodeinfo/discovery', 'top' ); \add_rewrite_rule( '^.well-known/x-nodeinfo2', 'index.php?rest_route=/activitypub/1.0/nodeinfo2', 'top' ); } + + \add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES ); } \add_action( 'init', '\Activitypub\add_rewrite_rules', 1 ); diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index ea3532a..00b413f 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -13,7 +13,6 @@ class Activitypub { public static function init() { \add_filter( 'template_include', array( '\Activitypub\Activitypub', 'render_json_template' ), 99 ); \add_filter( 'query_vars', array( '\Activitypub\Activitypub', 'add_query_vars' ) ); - \add_action( 'init', array( '\Activitypub\Activitypub', 'add_rewrite_endpoint' ) ); \add_filter( 'pre_get_avatar_data', array( '\Activitypub\Activitypub', 'pre_get_avatar_data' ), 11, 2 ); // Add support for ActivityPub to custom post types @@ -96,13 +95,6 @@ class Activitypub { return $vars; } - /** - * Add our rewrite endpoint to permalinks and pages. - */ - public static function add_rewrite_endpoint() { - \add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES ); - } - /** * Schedule Activities. * From 3dfdf2ac0a94e55e73b9e010abf940d40b791788 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 16 Jan 2023 20:12:14 +0100 Subject: [PATCH 33/94] Use a single page to explain all topics (glossar) --- includes/help.php | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/includes/help.php b/includes/help.php index 16ffbea..f84ccaf 100644 --- a/includes/help.php +++ b/includes/help.php @@ -2,45 +2,21 @@ \get_current_screen()->add_help_tab( array( - 'id' => 'fediverse', - 'title' => \__( 'Fediverse', 'activitypub' ), + 'id' => 'glossar', + 'title' => \__( 'Glossar', 'activitypub' ), 'content' => - '

                    ' . \__( 'What is the Fediverse?', 'activitypub' ) . '

                    ' . + '

                    ' . \__( 'Fediverse', 'activitypub' ) . '

                    ' . '

                    ' . \__( 'The Fediverse is a new word made of two words: "federation" + "universe"', 'activitypub' ) . '

                    ' . '

                    ' . \__( 'It is a federated social network running on free open software on a myriad of computers across the globe. Many independent servers are interconnected and allow people to interact with one another. There\'s no one central site: you choose a server to register. This ensures some decentralization and sovereignty of data. Fediverse (also called Fedi) has no built-in advertisements, no tricky algorithms, no one big corporation dictating the rules. Instead we have small cozy communities of like-minded people. Welcome!', 'activitypub' ) . '

                    ' . - '

                    ' . \__( 'For more informations please visit fediverse.party', 'activitypub' ) . '

                    ', - ) -); - -\get_current_screen()->add_help_tab( - array( - 'id' => 'activitypub', - 'title' => \__( 'ActivityPub', 'activitypub' ), - 'content' => - '

                    ' . \__( 'What is ActivityPub?', 'activitypub' ) . '

                    ' . - '

                    ' . \__( 'ActivityPub is a decentralized social networking protocol based on the ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended standard published by the W3C Social Web Working Group. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and subscribing to content.', 'activitypub' ) . '

                    ', - ) -); - -\get_current_screen()->add_help_tab( - array( - 'id' => 'webfinger', - 'title' => \__( 'WebFinger', 'activitypub' ), - 'content' => - '

                    ' . \__( 'What is WebFinger?', 'activitypub' ) . '

                    ' . + '

                    ' . \__( 'For more informations please visit fediverse.party', 'activitypub' ) . '

                    ' . + '

                    ' . \__( 'ActivityPub', 'activitypub' ) . '

                    ' . + '

                    ' . \__( 'ActivityPub is a decentralized social networking protocol based on the ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended standard published by the W3C Social Web Working Group. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and subscribing to content.', 'activitypub' ) . '

                    ' . + '

                    ' . \__( 'WebFinger', 'activitypub' ) . '

                    ' . '

                    ' . \__( 'WebFinger is used to discover information about people or other entities on the Internet that are identified by a URI using standard Hypertext Transfer Protocol (HTTP) methods over a secure transport. A WebFinger resource returns a JavaScript Object Notation (JSON) object describing the entity that is queried. The JSON object is referred to as the JSON Resource Descriptor (JRD).', 'activitypub' ) . '

                    ' . '

                    ' . \__( 'For a person, the type of information that might be discoverable via WebFinger includes a personal profile address, identity service, telephone number, or preferred avatar. For other entities on the Internet, a WebFinger resource might return JRDs containing link relations that enable a client to discover, for example, that a printer can print in color on A4 paper, the physical location of a server, or other static information.', 'activitypub' ) . '

                    ' . '

                    ' . \__( 'On Mastodon [and other Plattforms], user profiles can be hosted either locally on the same website as yours, or remotely on a completely different website. The same username may be used on a different domain. Therefore, a Mastodon user\'s full mention consists of both the username and the domain, in the form @username@domain. In practical terms, @user@example.com is not the same as @user@example.org. If the domain is not included, Mastodon will try to find a local user named @username. However, in order to deliver to someone over ActivityPub, the @username@domain mention is not enough – mentions must be translated to an HTTPS URI first, so that the remote actor\'s inbox and outbox can be found. (This paragraph is copied from the Mastodon Documentation)', 'activitypub' ) . '

                    ' . - '

                    ' . \__( 'For more informations please visit webfinger.net', 'activitypub' ) . '

                    ', - ) -); - -\get_current_screen()->add_help_tab( - array( - 'id' => 'nodeinfo', - 'title' => \__( 'NodeInfo', 'activitypub' ), - 'content' => - '

                    ' . \__( 'What is NodeInfo?', 'activitypub' ) . '

                    ' . + '

                    ' . \__( 'For more informations please visit webfinger.net', 'activitypub' ) . '

                    ' . + '

                    ' . \__( 'NodeInfo', 'activitypub' ) . '

                    ' . '

                    ' . \__( 'NodeInfo is an effort to create a standardized way of exposing metadata about a server running one of the distributed social networks. The two key goals are being able to get better insights into the user base of distributed social networking and the ability to build tools that allow users to choose the best fitting software and server for their needs.', 'activitypub' ) . '

                    ' . '

                    ' . \__( 'For more informations please visit nodeinfo.diaspora.software', 'activitypub' ) . '

                    ', ) From f412e83f0f7c1732b32d6aacda7970b6ac973067 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 16 Jan 2023 20:23:05 +0100 Subject: [PATCH 34/94] hashtag support is experimental --- templates/settings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/settings.php b/templates/settings.php index 9450195..809ab15 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -132,11 +132,11 @@ - +

                    - with the tag-link.', 'activitypub' ), 'default' ); ?> +

                    From 0d255d219b72c5c5fd43cd34f2f605b19769ca64 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 16 Jan 2023 20:28:45 +0100 Subject: [PATCH 35/94] change priority because of #182 --- includes/class-activitypub.php | 2 +- includes/class-hashtag.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index 00b413f..c04aedc 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -22,7 +22,7 @@ class Activitypub { \add_post_type_support( $post_type, 'activitypub' ); } - \add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 10, 3 ); + \add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 33, 3 ); \add_action( 'wp_trash_post', array( '\Activitypub\Activitypub', 'trash_post' ), 1 ); \add_action( 'untrash_post', array( '\Activitypub\Activitypub', 'untrash_post' ), 1 ); } diff --git a/includes/class-hashtag.php b/includes/class-hashtag.php index fbe2f3d..356cdb9 100644 --- a/includes/class-hashtag.php +++ b/includes/class-hashtag.php @@ -12,8 +12,8 @@ class Hashtag { */ public static function init() { if ( '1' === \get_option( 'activitypub_use_hashtags', '1' ) ) { - \add_filter( 'wp_insert_post', array( '\Activitypub\Hashtag', 'insert_post' ), 99, 2 ); - \add_filter( 'the_content', array( '\Activitypub\Hashtag', 'the_content' ), 99, 2 ); + \add_filter( 'wp_insert_post', array( '\Activitypub\Hashtag', 'insert_post' ), 10, 2 ); + \add_filter( 'the_content', array( '\Activitypub\Hashtag', 'the_content' ), 10, 2 ); } } From b5fa16b464d238111db0323bd060b187b2f1db86 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 22 Jan 2023 01:13:46 -0500 Subject: [PATCH 36/94] Move the shortcodes to their own class. --- activitypub.php | 1 + includes/class-shortcodes.php | 464 ++++++++++++++++++++++++++++++++++ includes/model/class-post.php | 352 +------------------------- 3 files changed, 466 insertions(+), 351 deletions(-) create mode 100644 includes/class-shortcodes.php diff --git a/activitypub.php b/activitypub.php index f02c054..f590c34 100644 --- a/activitypub.php +++ b/activitypub.php @@ -28,6 +28,7 @@ function init() { \define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); \define( 'ACTIVITYPUB_PLUGIN_FILE', plugin_dir_path( __FILE__ ) . '/' . basename( __FILE__ ) ); + require_once \dirname( __FILE__ ) . '/includes/class-shortcodes.php'; require_once \dirname( __FILE__ ) . '/includes/table/followers-list.php'; require_once \dirname( __FILE__ ) . '/includes/class-signature.php'; require_once \dirname( __FILE__ ) . '/includes/class-webfinger.php'; diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php new file mode 100644 index 0000000..7b25128 --- /dev/null +++ b/includes/class-shortcodes.php @@ -0,0 +1,464 @@ +post = $post; + } else { + $this->post = false; + } + + foreach( get_class_methods( $this ) as $shortcode ) { + if( $shortcode != 'init' && strpos( $shortcode, '__' ) !== 0 ) { + add_shortcode( 'ap_' . $shortcode, array( $this, $shortcode ) ); + } + } + } + + /** + * Generates output for the ap_hashtags shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function hashtags( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $tags = \get_the_tags( $this->post->ID ); + + if ( ! $tags ) { + return ''; + } + + $hash_tags = array(); + + foreach ( $tags as $tag ) { + $hash_tags[] = \sprintf( '', \get_tag_link( $tag ), $tag->slug ); + } + + return \implode( ' ', $hash_tags ); + } + + /** + * Generates output for the ap_title shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function title( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + return \get_the_title( $this->post->ID );; + } + + /** + * Generates output for the ap_excerpt shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function excerpt( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $length = ACTIVITYPUB_EXCERPT_LENGTH; + + if( is_array( $atts ) && array_key_exists( 'length', $atts ) ) { + $length = intval( $atts['length'] ); + } + + if( $length == 0 ) { $length = ACTIVITYPUB_EXCERPT_LENGTH; } + + $excerpt = \get_post_field( 'post_excerpt', $this->post ); + + if ( '' === $excerpt ) { + + $content = \get_post_field( 'post_content', $this->post ); + + // An empty string will make wp_trim_excerpt do stuff we do not want. + if ( '' !== $content ) { + + $excerpt = \strip_shortcodes( $content ); + + /** This filter is documented in wp-includes/post-template.php */ + $excerpt = \apply_filters( 'the_content', $excerpt ); + $excerpt = \str_replace( ']]>', ']]>', $excerpt ); + + } + } + + // Strip out any remaining tags. + $excerpt = \wp_strip_all_tags( $excerpt ); + + /** This filter is documented in wp-includes/formatting.php */ + $excerpt_more = \apply_filters( 'excerpt_more', ' [...]' ); + $excerpt_more_len = strlen( $excerpt_more ); + + // We now have a excerpt, but we need to check it's length, it may be longer than we want for two reasons: + // + // * The user has entered a manual excerpt which is longer that what we want. + // * No manual excerpt exists so we've used the content which might be longer than we want. + // + // Either way, let's trim it up if we need too. Also, don't forget to take into account the more indicator + // as part of the total length. + // + + // Setup a variable to hold the current excerpts length. + $current_excerpt_length = strlen( $excerpt ); + + // Setup a variable to keep track of our target length. + $target_excerpt_length = $excerpt_length - $excerpt_more_len; + + // Setup a variable to keep track of the current max length. + $current_excerpt_max = $target_excerpt_length; + + // This is a loop since we can't calculate word break the string after 'the_excpert' filter has run (we would break + // all kinds of html tags), so we have to cut the excerpt down a bit at a time until we hit our target length. + while ( $current_excerpt_length > $target_excerpt_length && $current_excerpt_max > 0 ) { + // Trim the excerpt based on wordwrap() positioning. + // Note: we're using
                    as the linebreak just in case there are any newlines existing in the excerpt from the user. + // There won't be any
                    left after we've run wp_strip_all_tags() in the code above, so they're + // safe to use here. It won't be included in the final excerpt as the substr() will trim it off. + $excerpt = substr( $excerpt, 0, strpos( wordwrap( $excerpt, $current_excerpt_max, '
                    ' ), '
                    ' ) ); + + // If something went wrong, or we're in a language that wordwrap() doesn't understand, + // just chop it off and don't worry about breaking in the middle of a word. + if ( strlen( $excerpt ) > $excerpt_length - $excerpt_more_len ) { + $excerpt = substr( $excerpt, 0, $current_excerpt_max ); + } + + // Add in the more indicator. + $excerpt = $excerpt . $excerpt_more; + + // Run it through the excerpt filter which will add some html tags back in. + $excerpt_filtered = apply_filters( 'the_excerpt', $excerpt ); + + // Now set the current excerpt length to this new filtered length. + $current_excerpt_length = strlen( $excerpt_filtered ); + + // Check to see if we're over the target length. + if ( $current_excerpt_length > $target_excerpt_length ) { + // If so, remove 20 characters from the current max and run the loop again. + $current_excerpt_max = $current_excerpt_max - 20; + } + } + + return \apply_filters( 'the_excerpt', $excerpt ); + } + + /** + * Generates output for the ap_content shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function content( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $content = \get_post_field( 'post_content', $this->post ); + + return \apply_filters( 'the_content', $content ); + } + + /** + * Generates output for the ap_permalink shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function permalink( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + return \sprintf( '%1$s', \esc_url( \get_permalink( $this->post->ID ) ) ); + } + + /** + * Generates output for the ap_shortlink shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function shortlink( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $this->post->ID ) ) ); + } + + /** + * Generates output for the ap_thumbnail shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function thumbnail( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $image = \get_the_post_thumbnail_url( $this->post->ID, 'thumbnail' ); + + if ( ! $image ) { + return ''; + } + + return $image; + } + + /** + * Generates output for the ap_image shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function image( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $image = \get_the_post_thumbnail_url( $this->post->ID, 'full' ); + + if ( ! $image ) { + return ''; + } + + return $image; + } + + /** + * Generates output for the ap_hashcats shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function hashcats( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $categories = \get_the_category( $this->post->ID ); + + if ( ! $categories ) { + return ''; + } + + $hash_tags = array(); + + foreach ( $categories as $category ) { + $hash_tags[] = \sprintf( '', \get_category_link( $category ), $category->slug ); + } + + return \implode( ' ', $hash_tags ); + } + + /** + * Generates output for the ap_author shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function author( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $name = \get_the_author_meta( 'display_name', $this->post->post_author ); + + if ( ! $name ) { + return ''; + } + + return $name; + } + + /** + * Generates output for the ap_authorurl shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function authorurl( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $url = \get_the_author_meta( 'user_url', $this->post->post_author ); + + if ( ! $url ) { + return ''; + } + + return $url; + } + + /** + * Generates output for the ap_blogurl shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function blogurl( $atts, $content, $tag ) { + return \get_bloginfo('url'); + } + + /** + * Generates output for the ap_blogname shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function blogname( $atts, $content, $tag ) { + return \get_bloginfo('name'); + } + + /** + * Generates output for the ap_blogdesc shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function blogdesc( $atts, $content, $tag ) { + return \get_bloginfo('description'); + } + + /** + * Generates output for the ap_date shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function date( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $datetime = \get_post_datetime( $this->post ); + $dateformat = \get_option( 'date_format' ); + $timeformat = \get_option( 'time_format' ); + + $date = $datetime->format( $dateformat ); + + if ( ! $date ) { + return ''; + } + + return $date; + } + + /** + * Generates output for the ap_time shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function time( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $datetime = \get_post_datetime( $this->post ); + $dateformat = \get_option( 'date_format' ); + $timeformat = \get_option( 'time_format' ); + + $date = $datetime->format( $timeformat ); + + if ( ! $date ) { + return ''; + } + + return $date; + } + + /** + * Generates output for the ap_datetime shortcode + * + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name + * + * @return string + */ + public function datetime( $atts, $content, $tag ) { + if( $this->post === false ) { + return ''; + } + + $datetime = \get_post_datetime( $this->post ); + $dateformat = \get_option( 'date_format' ); + $timeformat = \get_option( 'time_format' ); + + $date = $datetime->format( $dateformat . ' @ ' . $timeformat ); + + if ( ! $date ) { + return ''; + } + + return $date; + } + +} \ No newline at end of file diff --git a/includes/model/class-post.php b/includes/model/class-post.php index c881e75..d528be6 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -229,102 +229,6 @@ class Post { return $object_type; } - /** - * Outputs the shortcode content. - * - * @param array $atts the attributes of the shortcode - * @param string $content the content between opening and closing shortcodes - * @param string $tag the name of the shortcode being processed - * - */ - public function shortcode_content( $atts, $content, $tag ) { - $tag = strtolower( $tag ); - $post = $this->post; - - $text = ''; - - switch( $tag ) { - case 'ap_title': - $text = \get_the_title( $post->ID ); - - break; - case 'ap_excerpt': - $length = ACTIVITYPUB_EXCERPT_LENGTH; - - if( is_array( $atts ) && array_key_exists( 'length', $atts ) ) { - $length = intval( $atts['length'] ); - } - - if( $length == 0 ) { $length = ACTIVITYPUB_EXCERPT_LENGTH; } - - $text = $this->get_the_post_excerpt( $length ); - - break; - case 'ap_content': - $text = $this->get_the_post_content(); - - break; - case 'ap_permalink': - $text = $this->get_the_post_link( 'permalink' ); - - break; - case 'ap_shortlink': - $text = $this->get_the_post_link( 'shortlink' ); - - break; - case 'ap_hashtags': - $text = $this->get_the_post_hashtags(); - - break; - case 'ap_thumbnail': - $text = $this->get_the_post_image( 'thumbnail' ); - - break; - case 'ap_image': - $text = $this->get_the_post_image(); - - break; - case 'ap_hashcats': - $text = $this->get_the_post_categories(); - - break; - case 'ap_author': - $text = $this->get_the_post_author(); - - break; - case 'ap_authorurl': - $text = $this->get_the_post_author_url(); - - break; - case 'ap_blogurl': - $text = \get_bloginfo('url'); - - break; - case 'ap_blogname': - $text = \get_bloginfo('name'); - - break; - case 'ap_blogdesc': - $text = \get_bloginfo('description'); - - break; - case 'ap_date': - $text = $this->get_the_post_date( 'time' ); - - break; - case 'ap_time': - $text = $this->get_the_post_date( 'date' ); - - break; - case 'ap_datetime': - $text = $this->get_the_post_date( 'both' ); - - break; - } - - return $text; - } - /** * Generates the content for the activitypub item. * @@ -334,11 +238,7 @@ class Post { $post = $this->post; $content = $this->get_post_content_template(); - $shortcodes = array( 'ap_title', 'ap_excerpt', 'ap_content', 'ap_permalink', 'ap_shortlink', 'ap_hashtags', 'ap_thumbnail', 'ap_image', 'ap_hashcats', 'ap_author', 'ap_authorurl', 'ap_blogurl', 'ap_blogname', 'ap_blogdesc', 'ap_date', 'ap_time', 'ap_datetime' ); - - foreach( $shortcodes as $tag ) { - add_shortcode( $tag, [ $this, 'shortcode_content' ] ); - } + $shortcodes = new \Activitypub\Shortcodes( $post ); // Fill in the shortcodes. $content = do_shortcode( $content ); @@ -417,254 +317,4 @@ class Post { return $content; } - /** - * Get the excerpt for a post for use outside of the loop. - * - * @param int Optional excerpt length. - * - * @return string The excerpt. - */ - public function get_the_post_excerpt( $excerpt_length = ACTIVITYPUB_EXCERPT_LENGTH ) { - $post = $this->post; - - $excerpt = \get_post_field( 'post_excerpt', $post ); - - if ( '' === $excerpt ) { - - $content = \get_post_field( 'post_content', $post ); - - // An empty string will make wp_trim_excerpt do stuff we do not want. - if ( '' !== $content ) { - - $excerpt = \strip_shortcodes( $content ); - - /** This filter is documented in wp-includes/post-template.php */ - $excerpt = \apply_filters( 'the_content', $excerpt ); - $excerpt = \str_replace( ']]>', ']]>', $excerpt ); - - } - } - - // Strip out any remaining tags. - $excerpt = \wp_strip_all_tags( $excerpt ); - - /** This filter is documented in wp-includes/formatting.php */ - $excerpt_more = \apply_filters( 'excerpt_more', ' [...]' ); - $excerpt_more_len = strlen( $excerpt_more ); - - // We now have a excerpt, but we need to check it's length, it may be longer than we want for two reasons: - // - // * The user has entered a manual excerpt which is longer that what we want. - // * No manual excerpt exists so we've used the content which might be longer than we want. - // - // Either way, let's trim it up if we need too. Also, don't forget to take into account the more indicator - // as part of the total length. - // - - // Setup a variable to hold the current excerpts length. - $current_excerpt_length = strlen( $excerpt ); - - // Setup a variable to keep track of our target length. - $target_excerpt_length = $excerpt_length - $excerpt_more_len; - - // Setup a variable to keep track of the current max length. - $current_excerpt_max = $target_excerpt_length; - - // This is a loop since we can't calculate word break the string after 'the_excpert' filter has run (we would break - // all kinds of html tags), so we have to cut the excerpt down a bit at a time until we hit our target length. - while ( $current_excerpt_length > $target_excerpt_length && $current_excerpt_max > 0 ) { - // Trim the excerpt based on wordwrap() positioning. - // Note: we're using
                    as the linebreak just in case there are any newlines existing in the excerpt from the user. - // There won't be any
                    left after we've run wp_strip_all_tags() in the code above, so they're - // safe to use here. It won't be included in the final excerpt as the substr() will trim it off. - $excerpt = substr( $excerpt, 0, strpos( wordwrap( $excerpt, $current_excerpt_max, '
                    ' ), '
                    ' ) ); - - // If something went wrong, or we're in a language that wordwrap() doesn't understand, - // just chop it off and don't worry about breaking in the middle of a word. - if ( strlen( $excerpt ) > $excerpt_length - $excerpt_more_len ) { - $excerpt = substr( $excerpt, 0, $current_excerpt_max ); - } - - // Add in the more indicator. - $excerpt = $excerpt . $excerpt_more; - - // Run it through the excerpt filter which will add some html tags back in. - $excerpt_filtered = apply_filters( 'the_excerpt', $excerpt ); - - // Now set the current excerpt length to this new filtered length. - $current_excerpt_length = strlen( $excerpt_filtered ); - - // Check to see if we're over the target length. - if ( $current_excerpt_length > $target_excerpt_length ) { - // If so, remove 20 characters from the current max and run the loop again. - $current_excerpt_max = $current_excerpt_max - 20; - } - } - - return \apply_filters( 'the_excerpt', $excerpt ); - } - - /** - * Get the content for a post for use outside of the loop. - * - * @return string The content. - */ - public function get_the_post_content() { - $post = $this->post; - - $content = \get_post_field( 'post_content', $post ); - - return \apply_filters( 'the_content', $content ); - } - - /** - * Adds a backlink to the post/summary content - * - * @param string $type - * - * @return string - */ - public function get_the_post_link( $type = 'permalink' ) { - $post = $this->post; - - if ( 'shortlink' === $type ) { - $link = \esc_url( \wp_get_shortlink( $post->ID ) ); - } elseif ( 'permalink' === $type ) { - $link = \esc_url( \get_permalink( $post->ID ) ); - } else { - return ''; - } - - return \sprintf( '%1$s', $link ); - } - - /** - * Adds all tags as hashtags to the post/summary content - * - * @return string - */ - public function get_the_post_hashtags() { - $post = $this->post; - $tags = \get_the_tags( $post->ID ); - - if ( ! $tags ) { - return ''; - } - - $hash_tags = array(); - - foreach ( $tags as $tag ) { - $hash_tags[] = \sprintf( '', \get_tag_link( $tag ), $tag->slug ); - } - - return \implode( ' ', $hash_tags ); - } - - /** - * Adds the featured image url to the post/summary content - * - * @param string $size - * - * @return string - */ - public function get_the_post_image( $size = 'full' ) { - $post = $this->post; - - if( $size == '' ) { $size = 'full'; } - - $image = \get_the_post_thumbnail_url( $post->ID, $size ); - - if ( ! $image ) { - return ''; - } - - return $image; - } - - /** - * Adds all categories as hashtags to the post/summary content - * - * @return string - */ - public function get_the_post_categories() { - $post = $this->post; - $categories = \get_the_category( $post->ID ); - - if ( ! $categories ) { - return ''; - } - - $hash_tags = array(); - - foreach ( $categories as $category ) { - $hash_tags[] = \sprintf( '', \get_category_link( $category ), $category->slug ); - } - - return \implode( ' ', $hash_tags ); - } - - /** - * Adds author to the post/summary content - * - * @return string - */ - public function get_the_post_author() { - $post = $this->post; - $name = \get_the_author_meta( 'display_name', $post->post_author ); - - if ( ! $name ) { - return ''; - } - - return $name; - } - - /** - * Adds author's url to the post/summary content - * - * @return string - */ - public function get_the_post_profile_url() { - $post = $this->post; - $url = \get_the_author_meta( 'user_url', $post->post_author ); - - if ( ! $url ) { - return ''; - } - - return $url; - } - - /** - * Adds the post date/time to the post/summary content - * - * @param string display - * - * @return string - */ - public function get_the_post_date( $display = 'both' ) { - $post = $this->post; - $datetime = \get_post_datetime( $post ); - $dateformat = \get_option( 'date_format' ); - $timeformat = \get_option( 'time_format' ); - - switch( $display ) { - case 'date': - $date = $datetime->format( $dateformat ); - break; - case 'time': - $date = $datetime->format( $timeformat ); - break; - default: - $date = $datetime->format( $dateformat . ' @ ' . $timeformat ); - break; - } - - if ( ! $date ) { - return ''; - } - - return $date; - } - } From 740a73b00fd806eba0167a97142cd1ff99541552 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 22 Jan 2023 01:25:50 -0500 Subject: [PATCH 37/94] Add size attribute to the image shortcode. --- includes/class-shortcodes.php | 14 +++++++++++++- templates/settings.php | 14 +++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 7b25128..7091106 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -257,7 +257,19 @@ class Shortcodes { return ''; } - $image = \get_the_post_thumbnail_url( $this->post->ID, 'full' ); + $size = 'full'; + + if( is_array( $atts ) && array_key_exists( 'size', $atts ) ) { + $registered_sizes = wp_get_registered_image_subsizes(); + + if( array_key_exists( $atts['size'], $registered_sizes ) ) { + $size = intval( $atts['size'] ); + } + } + + if( ! $size ) { $size = 'full'; } + + $image = \get_the_post_thumbnail_url( $this->post->ID, $size ); if ( ! $image ) { return ''; diff --git a/templates/settings.php b/templates/settings.php index 498c2ac..6e81b94 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -9,7 +9,15 @@ ); $image_sizes = wp_get_registered_image_subsizes(); -$thumnail_size = $image_sizes['thumbnail']['width'] . 'x' . $image_sizes['thumbnail']['height'] . ' px'; +$thumbnail_size = $image_sizes['thumbnail']['width'] . 'x' . $image_sizes['thumbnail']['height'] . ' px'; +$registered_sizes = ''; + +foreach( $image_sizes as $name => $size ) { + $registered_sizes .= $name . ', '; +} + +$registered_sizes = trim( $registered_sizes, ', ' ); +$registered_sizes .= '.'; ?> @@ -73,8 +81,8 @@ $thumnail_size = $image_sizes['thumbnail']['width'] . 'x' . $image_sizes['thumbn
                  • [ap_shortlink] - Hum, to prettify the Shortlinks', 'activitypub' ), 'default' ); ?>
                  • [ap_hashtags] -
                  • [ap_hashcats] -
                  • -
                  • [ap_image] -
                  • -
                  • [ap_thumbnail] -
                  • +
                  • [ap_image size=xxx] -
                  • +
                  • [ap_thumbnail] -
                  • [ap_author] -
                  • [ap_authorurl] -
                  • [ap_date] -
                  • From 3a8289194845302814e74016cb7b2bd13089a575 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 22 Jan 2023 11:27:13 -0500 Subject: [PATCH 38/94] Minor cleanups. --- includes/class-shortcodes.php | 12 +++++++++++- includes/model/class-post.php | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 7091106..093adde 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -2,8 +2,18 @@ namespace Activitypub; class Shortcodes { + /** + * The post object we're currently working on + * + * @var WP_Post $post A WordPress Post Object + */ private $post; + /** + * Class constructor, registering WordPress then shortcodes + * + * @param WP_Post $post A WordPress Post Object + */ public function __construct( $post = null ) { if( $post == null ) { $post = \get_post(); @@ -16,7 +26,7 @@ class Shortcodes { } foreach( get_class_methods( $this ) as $shortcode ) { - if( $shortcode != 'init' && strpos( $shortcode, '__' ) !== 0 ) { + if( strpos( $shortcode, '__' ) !== 0 ) { add_shortcode( 'ap_' . $shortcode, array( $this, $shortcode ) ); } } diff --git a/includes/model/class-post.php b/includes/model/class-post.php index d528be6..bf9c65c 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -238,6 +238,7 @@ class Post { $post = $this->post; $content = $this->get_post_content_template(); + // Register the shortcodes. $shortcodes = new \Activitypub\Shortcodes( $post ); // Fill in the shortcodes. From efa62ac4cbaeed66a5c8aa7002091fcabdc0512e Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 09:37:40 +0100 Subject: [PATCH 39/94] Add missing text domain --- templates/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/settings.php b/templates/settings.php index 6e81b94..eb7a176 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -92,7 +92,7 @@ $registered_sizes .= '.';
                  • [ap_blogname] -
                  • [ap_blogdesc] -
                  -

                  +

                  From 71f3a475895142cf69ccbce36345611f3dbc746e Mon Sep 17 00:00:00 2001 From: Greg Date: Mon, 23 Jan 2023 11:59:13 -0500 Subject: [PATCH 40/94] Converted shortcode class to static. And added options for shortlink/permalink type. --- activitypub.php | 4 +- includes/class-shortcodes.php | 185 +++++++++++++++++++++------------- includes/model/class-post.php | 2 + templates/settings.php | 4 +- 4 files changed, 120 insertions(+), 75 deletions(-) diff --git a/activitypub.php b/activitypub.php index f590c34..d855d9f 100644 --- a/activitypub.php +++ b/activitypub.php @@ -28,7 +28,6 @@ function init() { \define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); \define( 'ACTIVITYPUB_PLUGIN_FILE', plugin_dir_path( __FILE__ ) . '/' . basename( __FILE__ ) ); - require_once \dirname( __FILE__ ) . '/includes/class-shortcodes.php'; require_once \dirname( __FILE__ ) . '/includes/table/followers-list.php'; require_once \dirname( __FILE__ ) . '/includes/class-signature.php'; require_once \dirname( __FILE__ ) . '/includes/class-webfinger.php'; @@ -72,6 +71,9 @@ function init() { require_once \dirname( __FILE__ ) . '/includes/class-hashtag.php'; \Activitypub\Hashtag::init(); + require_once \dirname( __FILE__ ) . '/includes/class-shortcodes.php'; + \Activitypub\Shortcodes::init(); + require_once \dirname( __FILE__ ) . '/includes/class-debug.php'; \Activitypub\Debug::init(); diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 093adde..a36f99c 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -2,32 +2,15 @@ namespace Activitypub; class Shortcodes { - /** - * The post object we're currently working on - * - * @var WP_Post $post A WordPress Post Object - */ - private $post; - /** * Class constructor, registering WordPress then shortcodes * * @param WP_Post $post A WordPress Post Object */ - public function __construct( $post = null ) { - if( $post == null ) { - $post = \get_post(); - } - - if( \is_object( $post ) && ! $post instanceof WP_POST ) { - $this->post = $post; - } else { - $this->post = false; - } - - foreach( get_class_methods( $this ) as $shortcode ) { - if( strpos( $shortcode, '__' ) !== 0 ) { - add_shortcode( 'ap_' . $shortcode, array( $this, $shortcode ) ); + public static function init() { + foreach( get_class_methods( 'Activitypub\Shortcodes' ) as $shortcode ) { + if( $shortcode != 'init' ) { + add_shortcode( 'ap_' . $shortcode, array( 'Activitypub\Shortcodes', $shortcode ) ); } } } @@ -41,12 +24,14 @@ class Shortcodes { * * @return string */ - public function hashtags( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function hashtags( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $tags = \get_the_tags( $this->post->ID ); + $tags = \get_the_tags( $post->ID ); if ( ! $tags ) { return ''; @@ -70,12 +55,14 @@ class Shortcodes { * * @return string */ - public function title( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function title( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - return \get_the_title( $this->post->ID );; + return \get_the_title( $post->ID );; } /** @@ -87,24 +74,24 @@ class Shortcodes { * * @return string */ - public function excerpt( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function excerpt( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $length = ACTIVITYPUB_EXCERPT_LENGTH; + $atts = shortcode_atts( array( 'length' => ACTIVITYPUB_EXCERPT_LENGTH ), $atts, $tag ); - if( is_array( $atts ) && array_key_exists( 'length', $atts ) ) { - $length = intval( $atts['length'] ); - } + $length = intval( $atts['length'] ); if( $length == 0 ) { $length = ACTIVITYPUB_EXCERPT_LENGTH; } - $excerpt = \get_post_field( 'post_excerpt', $this->post ); + $excerpt = \get_post_field( 'post_excerpt', $post ); if ( '' === $excerpt ) { - $content = \get_post_field( 'post_content', $this->post ); + $content = \get_post_field( 'post_content', $post ); // An empty string will make wp_trim_excerpt do stuff we do not want. if ( '' !== $content ) { @@ -186,12 +173,14 @@ class Shortcodes { * * @return string */ - public function content( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function content( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $content = \get_post_field( 'post_content', $this->post ); + $content = \get_post_field( 'post_content', $post ); return \apply_filters( 'the_content', $content ); } @@ -205,12 +194,30 @@ class Shortcodes { * * @return string */ - public function permalink( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function permalink( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - return \sprintf( '%1$s', \esc_url( \get_permalink( $this->post->ID ) ) ); + $atts = shortcode_atts( array( 'type' => 'html' ), $atts, $tag ); + + if( is_array( $atts ) && array_key_exists( 'type', $atts ) ) { + if( $atts['type'] == 'raw' ) { + return \get_permalink( $post->ID ); + } + + if( $atts['type'] == 'esc' ) { + return \esc_url( \get_permalink( $post->ID ) ); + } + + if( $atts['type'] == 'blank' ) { + return \sprintf( '%1$s', \esc_url( \get_permalink( $post->ID ) ) ); + } + } + + return \sprintf( '%1$s', \esc_url( \get_permalink( $post->ID ) ) ); } /** @@ -222,12 +229,30 @@ class Shortcodes { * * @return string */ - public function shortlink( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function shortlink( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $this->post->ID ) ) ); + $atts = shortcode_atts( array( 'type' => 'html' ), $atts, $tag ); + + if( is_array( $atts ) && array_key_exists( 'type', $atts ) ) { + if( $atts['type'] == 'raw' ) { + return \wp_get_shortlink( $post->ID ); + } + + if( $atts['type'] == 'esc' ) { + return \esc_url( \wp_get_shortlink( $post->ID ) ); + } + + if( $atts['type'] == 'blank' ) { + return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $post->ID ) ) ); + } + } + + return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $post->ID ) ) ); } /** @@ -239,12 +264,14 @@ class Shortcodes { * * @return string */ - public function thumbnail( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function thumbnail( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $image = \get_the_post_thumbnail_url( $this->post->ID, 'thumbnail' ); + $image = \get_the_post_thumbnail_url( $post->ID, 'thumbnail' ); if ( ! $image ) { return ''; @@ -262,12 +289,14 @@ class Shortcodes { * * @return string */ - public function image( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function image( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $size = 'full'; + $atts = shortcode_atts( array( 'size' => 'full' ), $atts, $tag ); if( is_array( $atts ) && array_key_exists( 'size', $atts ) ) { $registered_sizes = wp_get_registered_image_subsizes(); @@ -279,7 +308,7 @@ class Shortcodes { if( ! $size ) { $size = 'full'; } - $image = \get_the_post_thumbnail_url( $this->post->ID, $size ); + $image = \get_the_post_thumbnail_url( $post->ID, $size ); if ( ! $image ) { return ''; @@ -297,12 +326,14 @@ class Shortcodes { * * @return string */ - public function hashcats( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function hashcats( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $categories = \get_the_category( $this->post->ID ); + $categories = \get_the_category( $post->ID ); if ( ! $categories ) { return ''; @@ -326,12 +357,14 @@ class Shortcodes { * * @return string */ - public function author( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function author( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $name = \get_the_author_meta( 'display_name', $this->post->post_author ); + $name = \get_the_author_meta( 'display_name', $post->post_author ); if ( ! $name ) { return ''; @@ -349,12 +382,14 @@ class Shortcodes { * * @return string */ - public function authorurl( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function authorurl( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $url = \get_the_author_meta( 'user_url', $this->post->post_author ); + $url = \get_the_author_meta( 'user_url', $post->post_author ); if ( ! $url ) { return ''; @@ -372,7 +407,7 @@ class Shortcodes { * * @return string */ - public function blogurl( $atts, $content, $tag ) { + public static function blogurl( $atts, $content, $tag ) { return \get_bloginfo('url'); } @@ -385,7 +420,7 @@ class Shortcodes { * * @return string */ - public function blogname( $atts, $content, $tag ) { + public static function blogname( $atts, $content, $tag ) { return \get_bloginfo('name'); } @@ -398,7 +433,7 @@ class Shortcodes { * * @return string */ - public function blogdesc( $atts, $content, $tag ) { + public static function blogdesc( $atts, $content, $tag ) { return \get_bloginfo('description'); } @@ -411,12 +446,14 @@ class Shortcodes { * * @return string */ - public function date( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function date( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $datetime = \get_post_datetime( $this->post ); + $datetime = \get_post_datetime( $post ); $dateformat = \get_option( 'date_format' ); $timeformat = \get_option( 'time_format' ); @@ -438,12 +475,14 @@ class Shortcodes { * * @return string */ - public function time( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function time( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $datetime = \get_post_datetime( $this->post ); + $datetime = \get_post_datetime( $post ); $dateformat = \get_option( 'date_format' ); $timeformat = \get_option( 'time_format' ); @@ -465,12 +504,14 @@ class Shortcodes { * * @return string */ - public function datetime( $atts, $content, $tag ) { - if( $this->post === false ) { + public static function datetime( $atts, $content, $tag ) { + $post = get_post(); + + if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - $datetime = \get_post_datetime( $this->post ); + $datetime = \get_post_datetime( $post ); $dateformat = \get_option( 'date_format' ); $timeformat = \get_option( 'time_format' ); diff --git a/includes/model/class-post.php b/includes/model/class-post.php index bf9c65c..7ca2b6b 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -242,7 +242,9 @@ class Post { $shortcodes = new \Activitypub\Shortcodes( $post ); // Fill in the shortcodes. + setup_postdata( $post ); $content = do_shortcode( $content ); + wp_reset_postdata(); $content = \trim( \preg_replace( '/[\r\n]{2,}/', '', $content ) ); diff --git a/templates/settings.php b/templates/settings.php index eb7a176..cebae11 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -76,9 +76,9 @@ $registered_sizes .= '.';
                2. [ap_title] -
                3. [ap_content] -
                4. [ap_excerpt lenght=nnn] -
                5. -
                6. [ap_permalink] -
                7. +
                8. [ap_permalink type=xxx] -
                9. -
                10. [ap_shortlink] - Hum, to prettify the Shortlinks', 'activitypub' ), 'default' ); ?>
                11. +
                12. [ap_shortlink type=xxx] - Hum, to prettify the Shortlinks. Type can be either: raw (the raw url, no escaping), esc (the html escaped url), html (default, an a tag to the url), blank (an a tag to the url that opens in a new window).', 'activitypub' ), 'default' ); ?>
                13. [ap_hashtags] -
                14. [ap_hashcats] -
                15. [ap_image size=xxx] -
                16. From 5eac4c725e6571808632c7a967d0d0d473929ad0 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 19:38:00 +0100 Subject: [PATCH 41/94] run on pull request --- .github/workflows/phpcs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml index 870b0ee..8834947 100644 --- a/.github/workflows/phpcs.yml +++ b/.github/workflows/phpcs.yml @@ -1,5 +1,7 @@ name: PHP_CodeSniffer -on: push +on: + push: + pull_request: jobs: phpcs: runs-on: ubuntu-latest From 16b52c0940e72ed0e0ed5d01568a1c36069ceb4f Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 19:41:30 +0100 Subject: [PATCH 42/94] run also on PR --- .github/workflows/phpcs.yml | 6 ++++-- .github/workflows/phpunit.yml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml index 870b0ee..b7392e1 100644 --- a/.github/workflows/phpcs.yml +++ b/.github/workflows/phpcs.yml @@ -1,5 +1,7 @@ name: PHP_CodeSniffer -on: push +on: + push: + pull_request: jobs: phpcs: runs-on: ubuntu-latest @@ -19,7 +21,7 @@ jobs: uses: pat-s/always-upload-cache@v1.1.4 with: path: ${{ steps.composer-cache.outputs.dir }} - # Use the hash of composer.json as the key for your cache if you do not commit composer.lock. + # Use the hash of composer.json as the key for your cache if you do not commit composer.lock. key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} #key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 0756e1f..4c70a1e 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -1,7 +1,7 @@ name: Unit Testing on: - push: - pull_request: + push: + pull_request: jobs: phpunit: runs-on: ubuntu-latest From aec21a489ccee23de8fd304c7dab24ce110e5bce Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 19:43:34 +0100 Subject: [PATCH 43/94] coding standards --- includes/class-shortcodes.php | 171 +++++++++++++++++----------------- includes/model/class-post.php | 8 +- 2 files changed, 92 insertions(+), 87 deletions(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index a36f99c..8255dbf 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -8,8 +8,8 @@ class Shortcodes { * @param WP_Post $post A WordPress Post Object */ public static function init() { - foreach( get_class_methods( 'Activitypub\Shortcodes' ) as $shortcode ) { - if( $shortcode != 'init' ) { + foreach ( get_class_methods( 'Activitypub\Shortcodes' ) as $shortcode ) { + if ( $shortcode != 'init' ) { add_shortcode( 'ap_' . $shortcode, array( 'Activitypub\Shortcodes', $shortcode ) ); } } @@ -18,16 +18,16 @@ class Shortcodes { /** * Generates output for the ap_hashtags shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function hashtags( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -49,35 +49,36 @@ class Shortcodes { /** * Generates output for the ap_title shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function title( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } - return \get_the_title( $post->ID );; + return \get_the_title( $post->ID ); + } /** * Generates output for the ap_excerpt shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function excerpt( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -85,7 +86,8 @@ class Shortcodes { $length = intval( $atts['length'] ); - if( $length == 0 ) { $length = ACTIVITYPUB_EXCERPT_LENGTH; } + if ( $length == 0 ) { + $length = ACTIVITYPUB_EXCERPT_LENGTH; } $excerpt = \get_post_field( 'post_excerpt', $post ); @@ -167,16 +169,16 @@ class Shortcodes { /** * Generates output for the ap_content shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function content( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -188,31 +190,31 @@ class Shortcodes { /** * Generates output for the ap_permalink shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function permalink( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } $atts = shortcode_atts( array( 'type' => 'html' ), $atts, $tag ); - if( is_array( $atts ) && array_key_exists( 'type', $atts ) ) { - if( $atts['type'] == 'raw' ) { + if ( is_array( $atts ) && array_key_exists( 'type', $atts ) ) { + if ( $atts['type'] == 'raw' ) { return \get_permalink( $post->ID ); } - if( $atts['type'] == 'esc' ) { + if ( $atts['type'] == 'esc' ) { return \esc_url( \get_permalink( $post->ID ) ); } - if( $atts['type'] == 'blank' ) { + if ( $atts['type'] == 'blank' ) { return \sprintf( '%1$s', \esc_url( \get_permalink( $post->ID ) ) ); } } @@ -223,31 +225,31 @@ class Shortcodes { /** * Generates output for the ap_shortlink shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function shortlink( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } $atts = shortcode_atts( array( 'type' => 'html' ), $atts, $tag ); - if( is_array( $atts ) && array_key_exists( 'type', $atts ) ) { - if( $atts['type'] == 'raw' ) { + if ( is_array( $atts ) && array_key_exists( 'type', $atts ) ) { + if ( $atts['type'] == 'raw' ) { return \wp_get_shortlink( $post->ID ); } - if( $atts['type'] == 'esc' ) { + if ( $atts['type'] == 'esc' ) { return \esc_url( \wp_get_shortlink( $post->ID ) ); } - if( $atts['type'] == 'blank' ) { + if ( $atts['type'] == 'blank' ) { return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $post->ID ) ) ); } } @@ -258,16 +260,16 @@ class Shortcodes { /** * Generates output for the ap_thumbnail shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function thumbnail( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -283,30 +285,31 @@ class Shortcodes { /** * Generates output for the ap_image shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function image( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } $atts = shortcode_atts( array( 'size' => 'full' ), $atts, $tag ); - if( is_array( $atts ) && array_key_exists( 'size', $atts ) ) { + if ( is_array( $atts ) && array_key_exists( 'size', $atts ) ) { $registered_sizes = wp_get_registered_image_subsizes(); - if( array_key_exists( $atts['size'], $registered_sizes ) ) { + if ( array_key_exists( $atts['size'], $registered_sizes ) ) { $size = intval( $atts['size'] ); } } - if( ! $size ) { $size = 'full'; } + if ( ! $size ) { + $size = 'full'; } $image = \get_the_post_thumbnail_url( $post->ID, $size ); @@ -320,16 +323,16 @@ class Shortcodes { /** * Generates output for the ap_hashcats shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function hashcats( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -351,16 +354,16 @@ class Shortcodes { /** * Generates output for the ap_author shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function author( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -376,16 +379,16 @@ class Shortcodes { /** * Generates output for the ap_authorurl shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function authorurl( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -401,55 +404,55 @@ class Shortcodes { /** * Generates output for the ap_blogurl shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function blogurl( $atts, $content, $tag ) { - return \get_bloginfo('url'); + return \get_bloginfo( 'url' ); } /** * Generates output for the ap_blogname shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function blogname( $atts, $content, $tag ) { - return \get_bloginfo('name'); + return \get_bloginfo( 'name' ); } /** * Generates output for the ap_blogdesc shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function blogdesc( $atts, $content, $tag ) { - return \get_bloginfo('description'); + return \get_bloginfo( 'description' ); } /** * Generates output for the ap_date shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function date( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -469,16 +472,16 @@ class Shortcodes { /** * Generates output for the ap_time shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function time( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -498,16 +501,16 @@ class Shortcodes { /** * Generates output for the ap_datetime shortcode * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name + * @param array $atts shortcode attributes + * @param string $content shortcode content + * @param string $tag shortcode tag name * * @return string */ public static function datetime( $atts, $content, $tag ) { $post = get_post(); - if( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { return ''; } @@ -524,4 +527,4 @@ class Shortcodes { return $date; } -} \ No newline at end of file +} diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 7ca2b6b..6006315 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -17,7 +17,7 @@ class Post { private $object_type; public function __construct( $post = null ) { - if( $post ) { + if ( $post ) { $this->post = \get_post( $post ); $this->post_author = $this->post->post_author; @@ -298,7 +298,9 @@ class Post { $need_update = false; // If the old contents is blank, use the defaults. - if( $old_content == "" ) { $old_content = ACTIVITYPUB_CUSTOM_POST_CONTENT; $need_update = true; } + if ( $old_content == '' ) { + $old_content = ACTIVITYPUB_CUSTOM_POST_CONTENT; + $need_update = true; } // Set the new content to be the old content. $content = $old_content; @@ -313,7 +315,7 @@ class Post { $content = \str_replace( '%tags%', '[ap_hashtags]', $content ); // Store the new template if required. - if( $content != $old_content || $need_update ) { + if ( $content != $old_content || $need_update ) { \update_option( 'activitypub_custom_post_content', $content ); } From 75cc35c66ec8042a27085daf2544794302bfb2a8 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 20:08:06 +0100 Subject: [PATCH 44/94] I think it is enough to check if $post or $post_id is set --- includes/class-shortcodes.php | 59 ++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 8255dbf..96c6caf 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -9,7 +9,7 @@ class Shortcodes { */ public static function init() { foreach ( get_class_methods( 'Activitypub\Shortcodes' ) as $shortcode ) { - if ( $shortcode != 'init' ) { + if ( 'init' !== $shortcode ) { add_shortcode( 'ap_' . $shortcode, array( 'Activitypub\Shortcodes', $shortcode ) ); } } @@ -25,13 +25,13 @@ class Shortcodes { * @return string */ public static function hashtags( $atts, $content, $tag ) { - $post = get_post(); + $post_id = get_the_ID(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post_id ) { return ''; } - $tags = \get_the_tags( $post->ID ); + $tags = \get_the_tags( $post_id ); if ( ! $tags ) { return ''; @@ -56,13 +56,13 @@ class Shortcodes { * @return string */ public static function title( $atts, $content, $tag ) { - $post = get_post(); + $post_id = get_the_ID(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post_id ) { return ''; } - return \get_the_title( $post->ID ); + return \get_the_title( $post_id ); } @@ -78,7 +78,7 @@ class Shortcodes { public static function excerpt( $atts, $content, $tag ) { $post = get_post(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post ) { return ''; } @@ -86,7 +86,7 @@ class Shortcodes { $length = intval( $atts['length'] ); - if ( $length == 0 ) { + if ( 0 === $length ) { $length = ACTIVITYPUB_EXCERPT_LENGTH; } $excerpt = \get_post_field( 'post_excerpt', $post ); @@ -178,7 +178,7 @@ class Shortcodes { public static function content( $atts, $content, $tag ) { $post = get_post(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post ) { return ''; } @@ -199,11 +199,12 @@ class Shortcodes { public static function permalink( $atts, $content, $tag ) { $post = get_post(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post ) { return ''; } - $atts = shortcode_atts( array( 'type' => 'html' ), $atts, $tag ); + $atts = shortcode_atts( + array( 'type' => 'html' ), $atts, $tag ); if ( is_array( $atts ) && array_key_exists( 'type', $atts ) ) { if ( $atts['type'] == 'raw' ) { @@ -234,7 +235,7 @@ class Shortcodes { public static function shortlink( $atts, $content, $tag ) { $post = get_post(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post ) { return ''; } @@ -267,13 +268,13 @@ class Shortcodes { * @return string */ public static function thumbnail( $atts, $content, $tag ) { - $post = get_post(); + $post_id = get_the_ID(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post_id ) { return ''; } - $image = \get_the_post_thumbnail_url( $post->ID, 'thumbnail' ); + $image = \get_the_post_thumbnail_url( $post_id, 'thumbnail' ); if ( ! $image ) { return ''; @@ -292,9 +293,9 @@ class Shortcodes { * @return string */ public static function image( $atts, $content, $tag ) { - $post = get_post(); + $post_id = get_the_ID(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post_id ) { return ''; } @@ -309,9 +310,10 @@ class Shortcodes { } if ( ! $size ) { - $size = 'full'; } + $size = 'full'; + } - $image = \get_the_post_thumbnail_url( $post->ID, $size ); + $image = \get_the_post_thumbnail_url( $post_id, $size ); if ( ! $image ) { return ''; @@ -330,13 +332,13 @@ class Shortcodes { * @return string */ public static function hashcats( $atts, $content, $tag ) { - $post = get_post(); + $post_id = get_the_ID(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post_id ) { return ''; } - $categories = \get_the_category( $post->ID ); + $categories = \get_the_category( $post_id ); if ( ! $categories ) { return ''; @@ -363,7 +365,7 @@ class Shortcodes { public static function author( $atts, $content, $tag ) { $post = get_post(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post ) { return ''; } @@ -388,7 +390,7 @@ class Shortcodes { public static function authorurl( $atts, $content, $tag ) { $post = get_post(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post ) { return ''; } @@ -452,7 +454,7 @@ class Shortcodes { public static function date( $atts, $content, $tag ) { $post = get_post(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post ) { return ''; } @@ -481,7 +483,7 @@ class Shortcodes { public static function time( $atts, $content, $tag ) { $post = get_post(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post ) { return ''; } @@ -510,7 +512,7 @@ class Shortcodes { public static function datetime( $atts, $content, $tag ) { $post = get_post(); - if ( ! \is_object( $post ) || ( \is_object( $post ) && get_class( $post ) != 'WP_Post' ) ) { + if ( ! $post ) { return ''; } @@ -526,5 +528,4 @@ class Shortcodes { return $date; } - } From 3666f89f6ec9b55e9f66127c9dc8f945dc0c87a4 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 20:11:18 +0100 Subject: [PATCH 45/94] with `shortcode_atts` there is no need to check if attr is set --- includes/class-shortcodes.php | 51 ++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 96c6caf..8c7377d 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -204,20 +204,23 @@ class Shortcodes { } $atts = shortcode_atts( - array( 'type' => 'html' ), $atts, $tag ); + array( + 'type' => 'html', + ), + $atts, + $tag + ); - if ( is_array( $atts ) && array_key_exists( 'type', $atts ) ) { - if ( $atts['type'] == 'raw' ) { - return \get_permalink( $post->ID ); - } + if ( 'raw' === $atts['type'] ) { + return \get_permalink( $post->ID ); + } - if ( $atts['type'] == 'esc' ) { - return \esc_url( \get_permalink( $post->ID ) ); - } + if ( 'esc' === $atts['type'] ) { + return \esc_url( \get_permalink( $post->ID ) ); + } - if ( $atts['type'] == 'blank' ) { - return \sprintf( '%1$s', \esc_url( \get_permalink( $post->ID ) ) ); - } + if ( 'blank' === $atts['type'] ) { + return \sprintf( '%1$s', \esc_url( \get_permalink( $post->ID ) ) ); } return \sprintf( '%1$s', \esc_url( \get_permalink( $post->ID ) ) ); @@ -239,20 +242,24 @@ class Shortcodes { return ''; } - $atts = shortcode_atts( array( 'type' => 'html' ), $atts, $tag ); + $atts = shortcode_atts( + array( + 'type' => 'html', + ), + $atts, + $tag + ); - if ( is_array( $atts ) && array_key_exists( 'type', $atts ) ) { - if ( $atts['type'] == 'raw' ) { - return \wp_get_shortlink( $post->ID ); - } + if ( 'raw' === $atts['type'] ) { + return \wp_get_shortlink( $post->ID ); + } - if ( $atts['type'] == 'esc' ) { - return \esc_url( \wp_get_shortlink( $post->ID ) ); - } + if ( 'esc' === $atts['type'] ) { + return \esc_url( \wp_get_shortlink( $post->ID ) ); + } - if ( $atts['type'] == 'blank' ) { - return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $post->ID ) ) ); - } + if ( 'blank' === $atts['type'] ) { + return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $post->ID ) ) ); } return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $post->ID ) ) ); From b458cc6b88ec73eed5815c4db48d473a3a692556 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 20:13:56 +0100 Subject: [PATCH 46/94] coding standard --- includes/model/class-post.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 6006315..c796035 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -298,7 +298,7 @@ class Post { $need_update = false; // If the old contents is blank, use the defaults. - if ( $old_content == '' ) { + if ( '' === $old_content ) { $old_content = ACTIVITYPUB_CUSTOM_POST_CONTENT; $need_update = true; } @@ -315,7 +315,7 @@ class Post { $content = \str_replace( '%tags%', '[ap_hashtags]', $content ); // Store the new template if required. - if ( $content != $old_content || $need_update ) { + if ( $content !== $old_content || $need_update ) { \update_option( 'activitypub_custom_post_content', $content ); } From d4b88f228dd8107b5ff3507cd9e62c67337ced44 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 20:24:03 +0100 Subject: [PATCH 47/94] mastodon sadly does not support target on links See https://github.com/mastodon/mastodon/blob/main/lib/sanitize_ext/sanitize_config.rb#L77 --- includes/class-shortcodes.php | 23 ++++++++++++----------- templates/settings.php | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 8c7377d..280c10a 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -40,7 +40,11 @@ class Shortcodes { $hash_tags = array(); foreach ( $tags as $tag ) { - $hash_tags[] = \sprintf( '', \get_tag_link( $tag ), $tag->slug ); + $hash_tags[] = \sprintf( + '', + \get_tag_link( $tag ), + $tag->slug + ); } return \implode( ' ', $hash_tags ); @@ -82,12 +86,17 @@ class Shortcodes { return ''; } - $atts = shortcode_atts( array( 'length' => ACTIVITYPUB_EXCERPT_LENGTH ), $atts, $tag ); + $atts = shortcode_atts( + array( 'length' => ACTIVITYPUB_EXCERPT_LENGTH ), + $atts, + $tag + ); $length = intval( $atts['length'] ); if ( 0 === $length ) { - $length = ACTIVITYPUB_EXCERPT_LENGTH; } + $length = ACTIVITYPUB_EXCERPT_LENGTH; + } $excerpt = \get_post_field( 'post_excerpt', $post ); @@ -219,10 +228,6 @@ class Shortcodes { return \esc_url( \get_permalink( $post->ID ) ); } - if ( 'blank' === $atts['type'] ) { - return \sprintf( '%1$s', \esc_url( \get_permalink( $post->ID ) ) ); - } - return \sprintf( '%1$s', \esc_url( \get_permalink( $post->ID ) ) ); } @@ -258,10 +263,6 @@ class Shortcodes { return \esc_url( \wp_get_shortlink( $post->ID ) ); } - if ( 'blank' === $atts['type'] ) { - return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $post->ID ) ) ); - } - return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $post->ID ) ) ); } diff --git a/templates/settings.php b/templates/settings.php index cebae11..d563cc5 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -76,9 +76,9 @@ $registered_sizes .= '.';
                17. [ap_title] -
                18. [ap_content] -
                19. [ap_excerpt lenght=nnn] -
                20. -
                21. [ap_permalink type=xxx] -
                22. +
                23. [ap_permalink type=xxx] -
                24. -
                25. [ap_shortlink type=xxx] - Hum, to prettify the Shortlinks. Type can be either: raw (the raw url, no escaping), esc (the html escaped url), html (default, an a tag to the url), blank (an a tag to the url that opens in a new window).', 'activitypub' ), 'default' ); ?>
                26. +
                27. [ap_shortlink type=xxx] - Hum, to prettify the Shortlinks. Type can be either: raw (the raw url, no escaping), esc (the html escaped url), html (default, an a tag to the url).', 'activitypub' ), 'default' ); ?>
                28. [ap_hashtags] -
                29. [ap_hashcats] -
                30. [ap_image size=xxx] -
                31. From cb1c26a365d371746fe6341a1e989f70e066aa76 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 20:31:14 +0100 Subject: [PATCH 48/94] use static method to upgrade post content to shortcodes --- includes/class-admin.php | 3 +-- includes/model/class-post.php | 23 ++++++++++------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/includes/class-admin.php b/includes/class-admin.php index df79d0b..872ceea 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -50,8 +50,7 @@ class Admin { switch ( $tab ) { case 'settings': - $post_model = new \Activitypub\Model\Post(); - $post_model->upgrade_post_content_template(); + \Activitypub\Model\Post::upgrade_post_content_template(); \load_template( \dirname( __FILE__ ) . '/../templates/settings.php' ); break; diff --git a/includes/model/class-post.php b/includes/model/class-post.php index c796035..1eccf4d 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -16,19 +16,16 @@ class Post { private $tags; private $object_type; - public function __construct( $post = null ) { - if ( $post ) { - $this->post = \get_post( $post ); - - $this->post_author = $this->post->post_author; - $this->id = $this->generate_id(); - $this->summary = $this->generate_the_title(); - $this->content = $this->generate_the_content(); - $this->attachments = $this->generate_attachments(); - $this->tags = $this->generate_tags(); - $this->object_type = $this->generate_object_type(); - } + public function __construct( $post ) { + $this->post = \get_post( $post ); + $this->post_author = $this->post->post_author; + $this->id = $this->generate_id(); + $this->summary = $this->generate_the_title(); + $this->content = $this->generate_the_content(); + $this->attachments = $this->generate_attachments(); + $this->tags = $this->generate_tags(); + $this->object_type = $this->generate_object_type(); } public function __call( $method, $params ) { @@ -289,7 +286,7 @@ class Post { * * @return string the updated template content */ - public function upgrade_post_content_template() { + public static function upgrade_post_content_template() { // Get the custom template. $old_content = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT ); From 092a6bd3ca7968260d78e111447d56d8c6e0e8f6 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 20:31:38 +0100 Subject: [PATCH 49/94] coding standards --- templates/settings.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/settings.php b/templates/settings.php index d563cc5..8163808 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -12,7 +12,7 @@ $image_sizes = wp_get_registered_image_subsizes(); $thumbnail_size = $image_sizes['thumbnail']['width'] . 'x' . $image_sizes['thumbnail']['height'] . ' px'; $registered_sizes = ''; -foreach( $image_sizes as $name => $size ) { +foreach ( $image_sizes as $name => $size ) { $registered_sizes .= $name . ', '; } @@ -70,18 +70,18 @@ $registered_sizes .= '.';
                  -

                  -

                  +

                  +

                  • [ap_title] -
                  • [ap_content] -
                  • -
                  • [ap_excerpt lenght=nnn] -
                  • +
                  • [ap_excerpt lenght=400] -
                  • [ap_permalink type=xxx] -
                  • [ap_shortlink type=xxx] - Hum, to prettify the Shortlinks. Type can be either: raw (the raw url, no escaping), esc (the html escaped url), html (default, an a tag to the url).', 'activitypub' ), 'default' ); ?>
                  • [ap_hashtags] -
                  • [ap_hashcats] -
                  • -
                  • [ap_image size=xxx] -
                  • +
                  • [ap_image size=xxx] -
                  • [ap_thumbnail] -
                  • [ap_author] -
                  • [ap_authorurl] -
                  • @@ -97,7 +97,7 @@ $registered_sizes .= '.';

                  -

                  Let me know if you miss a template pattern.', 'activitypub' ), 'default' ); ?>

                  +

                  Let me know if you miss a template pattern.', 'activitypub' ), 'activitypub' ); ?>

                  From fe4e0961c82f996d63fa180e78c55d9f453691f2 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 20:47:02 +0100 Subject: [PATCH 50/94] I would keep it simple for now --- includes/class-shortcodes.php | 49 ++++++++++------------------------- templates/settings.php | 15 +---------- 2 files changed, 15 insertions(+), 49 deletions(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 280c10a..f1dd53d 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -266,31 +266,6 @@ class Shortcodes { return \sprintf( '%1$s', \esc_url( \wp_get_shortlink( $post->ID ) ) ); } - /** - * Generates output for the ap_thumbnail shortcode - * - * @param array $atts shortcode attributes - * @param string $content shortcode content - * @param string $tag shortcode tag name - * - * @return string - */ - public static function thumbnail( $atts, $content, $tag ) { - $post_id = get_the_ID(); - - if ( ! $post_id ) { - return ''; - } - - $image = \get_the_post_thumbnail_url( $post_id, 'thumbnail' ); - - if ( ! $image ) { - return ''; - } - - return $image; - } - /** * Generates output for the ap_image shortcode * @@ -307,18 +282,22 @@ class Shortcodes { return ''; } - $atts = shortcode_atts( array( 'size' => 'full' ), $atts, $tag ); + $atts = shortcode_atts( + array( + 'type' => 'full', + ), + $atts, + $tag + ); - if ( is_array( $atts ) && array_key_exists( 'size', $atts ) ) { - $registered_sizes = wp_get_registered_image_subsizes(); + $size = 'full'; - if ( array_key_exists( $atts['size'], $registered_sizes ) ) { - $size = intval( $atts['size'] ); - } - } - - if ( ! $size ) { - $size = 'full'; + if ( in_array( + $atts['type'], + array( 'thumbnail', 'medium', 'large', 'full' ), + true + ) ) { + $size = $atts['type']; } $image = \get_the_post_thumbnail_url( $post_id, $size ); diff --git a/templates/settings.php b/templates/settings.php index 8163808..8761179 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -7,18 +7,6 @@ 'welcome' => '', ) ); - -$image_sizes = wp_get_registered_image_subsizes(); -$thumbnail_size = $image_sizes['thumbnail']['width'] . 'x' . $image_sizes['thumbnail']['height'] . ' px'; -$registered_sizes = ''; - -foreach ( $image_sizes as $name => $size ) { - $registered_sizes .= $name . ', '; -} - -$registered_sizes = trim( $registered_sizes, ', ' ); -$registered_sizes .= '.'; - ?>
                  @@ -81,8 +69,7 @@ $registered_sizes .= '.';
                32. [ap_shortlink type=xxx] - Hum, to prettify the Shortlinks. Type can be either: raw (the raw url, no escaping), esc (the html escaped url), html (default, an a tag to the url).', 'activitypub' ), 'default' ); ?>
                33. [ap_hashtags] -
                34. [ap_hashcats] -
                35. -
                36. [ap_image size=xxx] -
                37. -
                38. [ap_thumbnail] -
                39. +
                40. [ap_image type=full] -
                41. [ap_author] -
                42. [ap_authorurl] -
                43. [ap_date] -
                44. From c93f02615d7e68e4f9fcf5cb6881e65b94db4d47 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 20:59:39 +0100 Subject: [PATCH 51/94] always escape output --- includes/class-shortcodes.php | 22 +++++++--------------- includes/model/class-post.php | 4 ++-- readme.txt | 1 + templates/settings.php | 4 ++-- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index f1dd53d..ea803f8 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -214,17 +214,13 @@ class Shortcodes { $atts = shortcode_atts( array( - 'type' => 'html', + 'type' => 'url', ), $atts, $tag ); - if ( 'raw' === $atts['type'] ) { - return \get_permalink( $post->ID ); - } - - if ( 'esc' === $atts['type'] ) { + if ( 'url' === $atts['type'] ) { return \esc_url( \get_permalink( $post->ID ) ); } @@ -249,17 +245,13 @@ class Shortcodes { $atts = shortcode_atts( array( - 'type' => 'html', + 'type' => 'url', ), $atts, $tag ); - if ( 'raw' === $atts['type'] ) { - return \wp_get_shortlink( $post->ID ); - } - - if ( 'esc' === $atts['type'] ) { + if ( 'url' === $atts['type'] ) { return \esc_url( \wp_get_shortlink( $post->ID ) ); } @@ -306,7 +298,7 @@ class Shortcodes { return ''; } - return $image; + return \esc_url( $image ); } /** @@ -387,7 +379,7 @@ class Shortcodes { return ''; } - return $url; + return \esc_url( $url ); } /** @@ -400,7 +392,7 @@ class Shortcodes { * @return string */ public static function blogurl( $atts, $content, $tag ) { - return \get_bloginfo( 'url' ); + return \esc_url( \get_bloginfo( 'url' ) ); } /** diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 1eccf4d..cdf5d51 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -306,8 +306,8 @@ class Post { $content = \str_replace( '%title%', '[ap_title]', $content ); $content = \str_replace( '%excerpt%', '[ap_excerpt]', $content ); $content = \str_replace( '%content%', '[ap_content]', $content ); - $content = \str_replace( '%permalink%', '[ap_permalink]', $content ); - $content = \str_replace( '%shortlink%', '[ap_shortlink]', $content ); + $content = \str_replace( '%permalink%', '[ap_permalink type="html"]', $content ); + $content = \str_replace( '%shortlink%', '[ap_shortlink type="html"]', $content ); $content = \str_replace( '%hashtags%', '[ap_hashtags]', $content ); $content = \str_replace( '%tags%', '[ap_hashtags]', $content ); diff --git a/readme.txt b/readme.txt index 7ef54de..bf786ca 100644 --- a/readme.txt +++ b/readme.txt @@ -91,6 +91,7 @@ Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github = v.next = * Add configuration item for number of images to attach. props [@mexon](https://github.com/mexon) +* Use shortcodes instead of custom templates, to setup the Activity Post-Content. props [@toolstack](https://github.com/toolstack) = 0.15.0 = diff --git a/templates/settings.php b/templates/settings.php index 8761179..d50a68a 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -64,9 +64,9 @@
                45. [ap_title] -
                46. [ap_content] -
                47. [ap_excerpt lenght=400] -
                48. -
                49. [ap_permalink type=xxx] -
                50. +
                51. [ap_permalink type=url] -
                52. -
                53. [ap_shortlink type=xxx] - Hum, to prettify the Shortlinks. Type can be either: raw (the raw url, no escaping), esc (the html escaped url), html (default, an a tag to the url).', 'activitypub' ), 'default' ); ?>
                54. +
                55. [ap_shortlink type=url] - Hum, to prettify the Shortlinks. Type can be either: url (default, the escaped url), html (an a tag to the url).', 'activitypub' ), 'default' ); ?>
                56. [ap_hashtags] -
                57. [ap_hashcats] -
                58. [ap_image type=full] -
                59. From 4d75ade22b57d43aece91bf4bbc50eb262d1b2f7 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 21:08:59 +0100 Subject: [PATCH 52/94] strong is not supported --- includes/model/class-post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index cdf5d51..6586388 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -268,7 +268,7 @@ class Post { } if ( 'title' === \get_option( 'activitypub_post_content_type', 'content' ) ) { - return "

                  [ap_title]

                  \n\n

                  [ap_permalink]

                  "; + return "

                  [ap_title]

                  \n\n

                  [ap_permalink]

                  "; } if ( 'content' === \get_option( 'activitypub_post_content_type', 'content' ) ) { From 718bd78cf4f57ee4e76a2a285a8bd21f0b3c0260 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 21:09:25 +0100 Subject: [PATCH 53/94] typos --- templates/settings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/settings.php b/templates/settings.php index d50a68a..eb01128 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -56,10 +56,10 @@

                  - +

                  -

                  +

                  • [ap_title] -
                  • [ap_content] -
                  • From a55dc903794d13d5cb2b59928b651259c02784f3 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 21:13:50 +0100 Subject: [PATCH 54/94] fix length --- includes/class-shortcodes.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index ea803f8..b7243d5 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -92,10 +92,10 @@ class Shortcodes { $tag ); - $length = intval( $atts['length'] ); + $excerpt_length = intval( $atts['length'] ); - if ( 0 === $length ) { - $length = ACTIVITYPUB_EXCERPT_LENGTH; + if ( 0 === $excerpt_length ) { + $excerpt_length = ACTIVITYPUB_EXCERPT_LENGTH; } $excerpt = \get_post_field( 'post_excerpt', $post ); From 7be74c18378f75fd94f1f9a97f5a4ffb2028aa5f Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 21:24:54 +0100 Subject: [PATCH 55/94] fix upgrade call --- includes/model/class-post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 6586388..9eaab30 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -276,7 +276,7 @@ class Post { } // Upgrade from old template codes to shortcodes. - $content = $this->upgrade_post_content_template(); + $content = self::upgrade_post_content_template(); return $content; } From e1df1293551002436cbba6589c58105bacedeb88 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 22:22:22 +0100 Subject: [PATCH 56/94] simplify inline help a bit --- includes/help.php | 46 ++++++++++++++++++++++++++++++++++++++++++ templates/settings.php | 25 ++++++----------------- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/includes/help.php b/includes/help.php index f84ccaf..1f29119 100644 --- a/includes/help.php +++ b/includes/help.php @@ -1,5 +1,51 @@ add_help_tab( + array( + 'id' => 'template-tags', + 'title' => \__( 'Template Tags', 'activitypub' ), + 'content' => + '

                    ' . __( 'The following Template Tags are available:', 'activitypub' ) . '

                    ' . + '
                    +
                    [ap_title]
                    +
                    ' . \wp_kses( __( 'The post\'s title.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_content]
                    +
                    ' . \wp_kses( __( 'The post\'s content.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_excerpt lenght="400"]
                    +
                    ' . \wp_kses( __( 'The post\'s excerpt (default 400 chars). length parameter is optional.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_permalink type="url"]
                    +
                    ' . \wp_kses( __( 'The post\'s permalink. Type can be either: url or html (an <a /> tag to the url).', 'activitypub' ), 'default' ) . '
                    +
                    [ap_shortlink type="url"]
                    +
                    ' . \wp_kses( __( 'The post\'s shortlink. Type can be either url or html (an <a /> tag to the url). I can recommend Hum, to prettify the Shortlinks.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_hashtags]
                    +
                    ' . \wp_kses( __( 'The post\'s tags as hashtags.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_hashcats]
                    +
                    ' . \wp_kses( __( 'The post\'s categories as hashtags.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_image type=full]
                    +
                    ' . \wp_kses( __( 'The URL for the post\'s featured image, defaults to full size. The type attribute can be any of the following: thumbnail, medium, large, full', 'activitypub' ), 'default' ) . '
                    +
                    [ap_author]
                    +
                    ' . \wp_kses( __( 'The author\'s name.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_authorurl]
                    +
                    ' . \wp_kses( __( 'The URL to the author\'s profile page.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_date]
                    +
                    ' . \wp_kses( __( 'The post\'s date.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_time]
                    +
                    ' . \wp_kses( __( 'The post\'s time.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_datetime]
                    +
                    ' . \wp_kses( __( 'The post\'s date/time formated as "date @ time".', 'activitypub' ), 'default' ) . '
                    +
                    [ap_blogurl]
                    +
                    ' . \wp_kses( __( 'The URL to the site.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_blogname]
                    +
                    ' . \wp_kses( __( 'The name of the site.', 'activitypub' ), 'default' ) . '
                    +
                    [ap_blogdesc]
                    +
                    ' . \wp_kses( __( 'The description of the site.', 'activitypub' ), 'default' ) . '
                    +
                    ' . + '

                    ' . __( 'You may also use any Shortcode normally available to you on your site, however be aware that Shortcodes may significantly increase the size of your content depending on what they do.', 'activitypub' ) . '

                    ' . + '

                    ' . __( 'Note: the old Template Tags are now deprecated and automatically converted to the new ones.', 'activitypub' ) . '

                    ' . + '

                    ' . \wp_kses( \__( 'Let me know if you miss a template pattern.', 'activitypub' ), 'activitypub' ) . '

                    ', + ) +); + \get_current_screen()->add_help_tab( array( 'id' => 'glossar', diff --git a/templates/settings.php b/templates/settings.php index eb01128..24cd5c4 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -56,35 +56,22 @@

                    - +
                    -

                    -

                    • [ap_title] -
                    • [ap_content] -
                    • -
                    • [ap_excerpt lenght=400] -
                    • -
                    • [ap_permalink type=url] -
                    • - -
                    • [ap_shortlink type=url] - Hum, to prettify the Shortlinks. Type can be either: url (default, the escaped url), html (an a tag to the url).', 'activitypub' ), 'default' ); ?>
                    • +
                    • [ap_excerpt] -
                    • +
                    • [ap_permalink] -
                    • +
                    • [ap_shortlink] - Hum.', 'activitypub' ), 'default' ); ?>
                    • [ap_hashtags] -
                    • [ap_hashcats] -
                    • -
                    • [ap_image type=full] -
                    • -
                    • [ap_author] -
                    • -
                    • [ap_authorurl] -
                    • -
                    • [ap_date] -
                    • -
                    • [ap_time] -
                    • -
                    • [ap_datetime] -
                    • -
                    • [ap_blogurl] -
                    • -
                    • [ap_blogname] -
                    • -
                    • [ap_blogdesc] -
                    • +
                    • [ap_image] -
                    -

                    +

                    - -

                    Let me know if you miss a template pattern.', 'activitypub' ), 'activitypub' ); ?>

                    From 281ed2a8c21ec37a7c7404bfb7fb25e5fc72a43c Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Jan 2023 23:51:27 +0100 Subject: [PATCH 57/94] remove old shortcode code --- includes/model/class-post.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 9eaab30..a052715 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -235,9 +235,6 @@ class Post { $post = $this->post; $content = $this->get_post_content_template(); - // Register the shortcodes. - $shortcodes = new \Activitypub\Shortcodes( $post ); - // Fill in the shortcodes. setup_postdata( $post ); $content = do_shortcode( $content ); From 68955b92db067b1a2f60cce8f640803393acebbb Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 24 Jan 2023 11:45:17 +0100 Subject: [PATCH 58/94] optimized HTML and texts --- includes/help.php | 70 +++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/includes/help.php b/includes/help.php index 1f29119..6039b9f 100644 --- a/includes/help.php +++ b/includes/help.php @@ -6,43 +6,43 @@ 'title' => \__( 'Template Tags', 'activitypub' ), 'content' => '

                    ' . __( 'The following Template Tags are available:', 'activitypub' ) . '

                    ' . - '
                    -
                    [ap_title]
                    -
                    ' . \wp_kses( __( 'The post\'s title.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_content]
                    -
                    ' . \wp_kses( __( 'The post\'s content.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_excerpt lenght="400"]
                    -
                    ' . \wp_kses( __( 'The post\'s excerpt (default 400 chars). length parameter is optional.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_permalink type="url"]
                    -
                    ' . \wp_kses( __( 'The post\'s permalink. Type can be either: url or html (an <a /> tag to the url).', 'activitypub' ), 'default' ) . '
                    -
                    [ap_shortlink type="url"]
                    -
                    ' . \wp_kses( __( 'The post\'s shortlink. Type can be either url or html (an <a /> tag to the url). I can recommend Hum, to prettify the Shortlinks.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_hashtags]
                    -
                    ' . \wp_kses( __( 'The post\'s tags as hashtags.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_hashcats]
                    -
                    ' . \wp_kses( __( 'The post\'s categories as hashtags.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_image type=full]
                    -
                    ' . \wp_kses( __( 'The URL for the post\'s featured image, defaults to full size. The type attribute can be any of the following: thumbnail, medium, large, full', 'activitypub' ), 'default' ) . '
                    -
                    [ap_author]
                    -
                    ' . \wp_kses( __( 'The author\'s name.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_authorurl]
                    -
                    ' . \wp_kses( __( 'The URL to the author\'s profile page.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_date]
                    -
                    ' . \wp_kses( __( 'The post\'s date.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_time]
                    -
                    ' . \wp_kses( __( 'The post\'s time.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_datetime]
                    -
                    ' . \wp_kses( __( 'The post\'s date/time formated as "date @ time".', 'activitypub' ), 'default' ) . '
                    -
                    [ap_blogurl]
                    -
                    ' . \wp_kses( __( 'The URL to the site.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_blogname]
                    -
                    ' . \wp_kses( __( 'The name of the site.', 'activitypub' ), 'default' ) . '
                    -
                    [ap_blogdesc]
                    -
                    ' . \wp_kses( __( 'The description of the site.', 'activitypub' ), 'default' ) . '
                    -
                    ' . + '
                    ' . + '
                    [ap_title]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s title.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_content]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s content.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_excerpt lenght="400"]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s excerpt (default 400 chars). length attribute is optional.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_permalink type="url"]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s permalink. Type can be either: url or html (an <a /> tag). type attribute is optional.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_shortlink type="url"]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s shortlink. Type can be either url or html (an <a /> tag). I can recommend Hum, to prettify the Shortlinks. type attribute is optional.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_hashtags]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s tags as hashtags.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_hashcats]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s categories as hashtags.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_image type=full]
                    ' . + '
                    ' . \wp_kses( __( 'The URL for the post\'s featured image, defaults to full size. The type attribute can be any of the following: thumbnail, medium, large, full. type attribute is optional.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_author]
                    ' . + '
                    ' . \wp_kses( __( 'The author\'s name.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_authorurl]
                    ' . + '
                    ' . \wp_kses( __( 'The URL to the author\'s profile page.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_date]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s date.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_time]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s time.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_datetime]
                    ' . + '
                    ' . \wp_kses( __( 'The post\'s date/time formated as "date @ time".', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_blogurl]
                    ' . + '
                    ' . \wp_kses( __( 'The URL to the site.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_blogname]
                    ' . + '
                    ' . \wp_kses( __( 'The name of the site.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    [ap_blogdesc]
                    ' . + '
                    ' . \wp_kses( __( 'The description of the site.', 'activitypub' ), 'default' ) . '
                    ' . + '
                    ' . '

                    ' . __( 'You may also use any Shortcode normally available to you on your site, however be aware that Shortcodes may significantly increase the size of your content depending on what they do.', 'activitypub' ) . '

                    ' . '

                    ' . __( 'Note: the old Template Tags are now deprecated and automatically converted to the new ones.', 'activitypub' ) . '

                    ' . - '

                    ' . \wp_kses( \__( 'Let me know if you miss a template pattern.', 'activitypub' ), 'activitypub' ) . '

                    ', + '

                    ' . \wp_kses( \__( 'Let me know if you miss a Template Tag.', 'activitypub' ), 'activitypub' ) . '

                    ', ) ); From 5878a12c83b94681f94393202848998364e24033 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 24 Jan 2023 11:45:37 +0100 Subject: [PATCH 59/94] remove HTML allow list --- activitypub.php | 1 - includes/class-admin.php | 9 --------- templates/settings.php | 20 -------------------- 3 files changed, 30 deletions(-) diff --git a/activitypub.php b/activitypub.php index d855d9f..994ef10 100644 --- a/activitypub.php +++ b/activitypub.php @@ -22,7 +22,6 @@ function init() { \defined( 'ACTIVITYPUB_EXCERPT_LENGTH' ) || \define( 'ACTIVITYPUB_EXCERPT_LENGTH', 400 ); \defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 ); \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=

                    )|(?<=
                    )|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' ); - \defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '

                      1. ' );
                         	\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "

                        [ap_title]

                        \n\n[ap_content]\n\n

                        [ap_hashtags]

                        \n\n

                        [ap_shortlink]

                        " ); \define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); \define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); diff --git a/includes/class-admin.php b/includes/class-admin.php index 872ceea..e98d547 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -132,15 +132,6 @@ class Admin { 'default' => 0, ) ); - \register_setting( - 'activitypub', - 'activitypub_allowed_html', - array( - 'type' => 'string', - 'description' => \__( 'List of HTML elements that are allowed in activities.', 'activitypub' ), - 'default' => ACTIVITYPUB_ALLOWED_HTML, - ) - ); \register_setting( 'activitypub', 'activitypub_support_post_types', diff --git a/templates/settings.php b/templates/settings.php index 24cd5c4..97e7e4d 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -140,26 +140,6 @@

                        - - - - - - -

                        - Leave list empty to support all HTML elements. Default: %s', 'activitypub' ), - \esc_html( ACTIVITYPUB_ALLOWED_HTML ) - ), - 'default' - ); - ?> -

                        - - From dbaddd91890f4926ce3f8055e1c35947119dbd2f Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 27 Jan 2023 10:21:51 +0100 Subject: [PATCH 60/94] Simplified and optimized code based on the Shortcode changes --- includes/model/class-post.php | 209 +++++++++++++++++++++++++++------- 1 file changed, 167 insertions(+), 42 deletions(-) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index a052715..422e26b 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -7,27 +7,103 @@ namespace Activitypub\Model; * @author Matthias Pfefferle */ class Post { + /** + * The WordPress Post Object. + * + * @var WP_Post + */ private $post; - private $post_author; - private $id; - private $summary; - private $content; - private $attachments; - private $tags; - private $object_type; + /** + * The Post Author. + * + * @var string + */ + private $post_author; + + /** + * The Object ID. + * + * @var string + */ + private $id; + + /** + * The Object Summary. + * + * @var string + */ + private $summary; + + /** + * The Object Summary + * + * @var string + */ + private $content; + + /** + * The Object Attachments. This is usually a list of Images. + * + * @var array + */ + private $attachments; + + /** + * The Object Tags. This is usually the list of used Hashtags. + * + * @var array + */ + private $tags; + + /** + * The Onject Type + * + * @var string + */ + private $object_type = 'Note'; + + /** + * The Allowed Tags, used in the content. + * + * @var array + */ + private $allowed_tags = array( + 'a' => array( + 'href' => array(), + 'title' => array(), + 'class' => array(), + 'rel' => array(), + ), + 'br' => array(), + 'p' => array( + 'class' => array(), + ), + 'span' => array( + 'class' => array(), + ), + 'div' => array( + 'class' => array(), + ), + ); + + /** + * Constructor + * + * @param WP_Post $post + */ public function __construct( $post ) { $this->post = \get_post( $post ); - - $this->post_author = $this->post->post_author; - $this->id = $this->generate_id(); - $this->summary = $this->generate_the_title(); - $this->content = $this->generate_the_content(); - $this->attachments = $this->generate_attachments(); - $this->tags = $this->generate_tags(); - $this->object_type = $this->generate_object_type(); } + /** + * Magic function to implement getter and setter + * + * @param string $method + * @param string $params + * + * @return void + */ public function __call( $method, $params ) { $var = \strtolower( \substr( $method, 4 ) ); @@ -40,34 +116,53 @@ class Post { } } + /** + * Converts this Object into an Array. + * + * @return array + */ public function to_array() { $post = $this->post; $array = array( - 'id' => $this->id, - 'type' => $this->object_type, + 'id' => $this->get_id(), + 'type' => $this->get_object_type(), 'published' => \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_date_gmt ) ), 'attributedTo' => \get_author_posts_url( $post->post_author ), - 'summary' => $this->summary, + 'summary' => $this->get_summary(), 'inReplyTo' => null, - 'content' => $this->content, + 'content' => $this->get_content(), 'contentMap' => array( - \strstr( \get_locale(), '_', true ) => $this->content, + \strstr( \get_locale(), '_', true ) => $this->get_content(), ), 'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ), 'cc' => array( 'https://www.w3.org/ns/activitystreams#Public' ), - 'attachment' => $this->attachments, - 'tag' => $this->tags, + 'attachment' => $this->get_attachments(), + 'tag' => $this->get_tags(), ); return \apply_filters( 'activitypub_post', $array ); } + /** + * Converts this Object into a JSON String + * + * @return string + */ public function to_json() { return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT ); } - public function generate_id() { + /** + * Returns the ID of an Activity Object + * + * @return string + */ + public function get_id() { + if ( $this->id ) { + return $this->id; + } + $post = $this->post; if ( 'trash' === get_post_status( $post ) ) { @@ -76,10 +171,21 @@ class Post { $permalink = \get_permalink( $post ); } + $this->id = $permalink; + return $permalink; } - public function generate_attachments() { + /** + * Returns a list of Image Attachments + * + * @return array + */ + public function get_attachments() { + if ( $this->attachments ) { + return $this->attachments; + } + $max_images = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) ); $images = array(); @@ -140,10 +246,21 @@ class Post { } } + $this->attachments = $images; + return $images; } - public function generate_tags() { + /** + * Returns a list of Tags, used in the Post + * + * @return array + */ + public function get_tags() { + if ( $this->tags ) { + return $this->tags; + } + $tags = array(); $post_tags = \get_the_tags( $this->post->ID ); @@ -158,18 +275,21 @@ class Post { } } + $this->tags = $tags; + return $tags; } /** * Returns the as2 object-type for a given post * - * @param string $type the object-type - * @param Object $post the post-object - * * @return string the object-type */ - public function generate_object_type() { + public function get_object_type() { + if ( $this->object_type ) { + return $this->object_type; + } + if ( 'wordpress-post-format' !== \get_option( 'activitypub_object_type', 'note' ) ) { return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) ); } @@ -223,15 +343,21 @@ class Post { break; } + $this->object_type = $object_type; + return $object_type; } /** - * Generates the content for the activitypub item. + * Returns the content for the ActivityPub Item. * * @return string the content */ - public function generate_the_content() { + public function get_content() { + if ( $this->content ) { + return $this->content; + } + $post = $this->post; $content = $this->get_post_content_template(); @@ -240,18 +366,16 @@ class Post { $content = do_shortcode( $content ); wp_reset_postdata(); - $content = \trim( \preg_replace( '/[\r\n]{2,}/', '', $content ) ); + $content = \wpautop( \wp_kses( $content, $this->allowed_tags ) ); - $filtered_content = \apply_filters( 'activitypub_the_content', $content, $this->post ); + $filtered_content = \apply_filters( 'activitypub_the_content', $content, $post ); $decoded_content = \html_entity_decode( $filtered_content, \ENT_QUOTES, 'UTF-8' ); - $allowed_html = \apply_filters( 'activitypub_allowed_html', \get_option( 'activitypub_allowed_html', ACTIVITYPUB_ALLOWED_HTML ) ); + $content = \trim( \preg_replace( '/[\n\r]/', '', $content ) ); - if ( $allowed_html ) { - return \strip_tags( $decoded_content, $allowed_html ); - } + $this->content = $content; - return $decoded_content; + return $content; } /** @@ -261,15 +385,15 @@ class Post { */ public function get_post_content_template() { if ( 'excerpt' === \get_option( 'activitypub_post_content_type', 'content' ) ) { - return "[ap_excerpt]\n\n

                        [ap_permalink]

                        "; + return "[ap_excerpt]\n\n[ap_permalink]"; } if ( 'title' === \get_option( 'activitypub_post_content_type', 'content' ) ) { - return "

                        [ap_title]

                        \n\n

                        [ap_permalink]

                        "; + return "[ap_title]\n\n[ap_permalink]"; } if ( 'content' === \get_option( 'activitypub_post_content_type', 'content' ) ) { - return "[ap_content]\n\n

                        [ap_hashtags]

                        \n\n

                        [ap_permalink]

                        "; + return "[ap_content]\n\n[ap_hashtags]\n\n[ap_permalink]"; } // Upgrade from old template codes to shortcodes. @@ -294,7 +418,8 @@ class Post { // If the old contents is blank, use the defaults. if ( '' === $old_content ) { $old_content = ACTIVITYPUB_CUSTOM_POST_CONTENT; - $need_update = true; } + $need_update = true; + } // Set the new content to be the old content. $content = $old_content; From 32f5bec23ae7a4289568c5df0c8be8084d252db7 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 27 Jan 2023 12:13:41 +0100 Subject: [PATCH 61/94] Protect tags from being broken --- includes/class-hashtag.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/includes/class-hashtag.php b/includes/class-hashtag.php index 356cdb9..2b7fa54 100644 --- a/includes/class-hashtag.php +++ b/includes/class-hashtag.php @@ -43,8 +43,22 @@ class Hashtag { * @return string the filtered post-content */ public static function the_content( $the_content ) { + $protected_tags = array(); + $the_content = preg_replace_callback( + '#<[^>]+>#i', + function( $m ) use ( &$protected_tags ) { + $c = count( $protected_tags ); + $protect = '!#!#PROTECT' . $c . '#!#!'; + $protected_tags[ $protect ] = $m[0]; + return $protect; + }, + $the_content + ); + $the_content = \preg_replace_callback( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', array( '\Activitypub\Hashtag', 'replace_with_links' ), $the_content ); + $the_content = str_replace( array_keys( $protected_tags ), array_values( $protected_tags ), $the_content ); + return $the_content; } From 6b8fb5af0c57d76151e2c4fbd4cd94e423cfeaaa Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 27 Jan 2023 14:28:56 +0100 Subject: [PATCH 62/94] Fix accessing post properties --- includes/model/class-post.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 422e26b..c8ee85d 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -108,6 +108,9 @@ class Post { $var = \strtolower( \substr( $method, 4 ) ); if ( \strncasecmp( $method, 'get', 3 ) === 0 ) { + if ( empty( $this->$var ) && ! empty( $this->post->$var ) ) { + return $this->post->$var; + } return $this->$var; } From ddbcd44b6f33f7f57f438bcaac443b16e900e81d Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Sat, 10 Dec 2022 17:58:24 +0100 Subject: [PATCH 63/94] fix #214 thanks @mexon --- README.md | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f2d78d..65d73c5 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github ### 0.14.0 ### -* Friends support: https://wordpress.org/plugins/friends/ . props [@akirk](https://github.com/akirk) +* Friends support: https://wordpress.org/plugins/friends/ props [@akirk](https://github.com/akirk) * Massive guidance improvements. props [mediaformat](https://github.com/mediaformat) & [@akirk](https://github.com/akirk) * Add Custom Post Type support to outbox API. props [blueset](https://github.com/blueset) * Better hash-tag support. props [bocops](https://github.com/bocops) diff --git a/readme.txt b/readme.txt index bf786ca..e889ce9 100644 --- a/readme.txt +++ b/readme.txt @@ -113,7 +113,7 @@ Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github = 0.14.0 = -* Friends support: https://wordpress.org/plugins/friends/ . props [@akirk](https://github.com/akirk) +* Friends support: https://wordpress.org/plugins/friends/ props [@akirk](https://github.com/akirk) * Massive guidance improvements. props [mediaformat](https://github.com/mediaformat) & [@akirk](https://github.com/akirk) * Add Custom Post Type support to outbox API. props [blueset](https://github.com/blueset) * Better hash-tag support. props [bocops](https://github.com/bocops) From b5c4f473decc8573e1576826c298f2aca0ce9eaf Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Thu, 8 Dec 2022 21:23:19 +0100 Subject: [PATCH 64/94] Start adding support for outgoing mentions --- includes/class-activity-dispatcher.php | 23 ++++- includes/model/class-activity.php | 10 +- ...-class-activitypub-activity-dispatcher.php | 97 +++++++++++++++++++ tests/test-class-activitypub-activity.php | 32 ++++++ 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 tests/test-class-activitypub-activity-dispatcher.php create mode 100644 tests/test-class-activitypub-activity.php diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index a4ed0c7..a130ba3 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -23,17 +23,36 @@ class Activity_Dispatcher { * * @param \Activitypub\Model\Post $activitypub_post */ - public static function send_post_activity( $activitypub_post ) { + public static function send_post_activity( Model\Post $activitypub_post ) { // get latest version of post $user_id = $activitypub_post->get_post_author(); $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL ); - $activitypub_activity->from_post( $activitypub_post->to_array() ); + $activitypub_activity->from_post( $activitypub_post ); + $sent = array(); foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { + $sent[ $to ] = true; $activitypub_activity->set_to( $to ); $activity = $activitypub_activity->to_json(); // phpcs:ignore + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); + } + $followers_url = \get_rest_url( null, '/activitypub/1.0/users/' . intval( $user_id ) . '/followers' ); + foreach ( $activitypub_activity->get_cc() as $cc ) { + if ( $cc === $followers_url ) { + continue; + } + $inbox = \Activitypub\get_inbox_by_actor( $cc ); + if ( ! $inbox || \is_wp_error( $inbox ) ) { + continue; + } + if ( isset( $sent[ $cc ] ) ) { + continue; + } + $sent[ $cc ] = true; + $activity = $activitypub_activity->to_json(); // phpcs:ignore + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); } } diff --git a/includes/model/class-activity.php b/includes/model/class-activity.php index 9de1031..57bf3b1 100644 --- a/includes/model/class-activity.php +++ b/includes/model/class-activity.php @@ -45,16 +45,24 @@ class Activity { } } - public function from_post( $object ) { + public function from_post( Post $post ) { + $object = apply_filters( 'activitypub_from_post_array', $post->to_array() ); $this->object = $object; + if ( isset( $object['published'] ) ) { $this->published = $object['published']; } + $this->cc = array( \get_rest_url( null, '/activitypub/1.0/users/' . intval( $post->get_post_author() ) . '/followers' ) ); if ( isset( $object['attributedTo'] ) ) { $this->actor = $object['attributedTo']; } + $mentions = apply_filters( 'activitypub_extract_mentions', array(), $post ); + foreach ( $mentions as $mention ) { + $this->cc[] = $mention; + } + $type = \strtolower( $this->type ); if ( isset( $object['id'] ) ) { diff --git a/tests/test-class-activitypub-activity-dispatcher.php b/tests/test-class-activitypub-activity-dispatcher.php new file mode 100644 index 0000000..a5dd7b9 --- /dev/null +++ b/tests/test-class-activitypub-activity-dispatcher.php @@ -0,0 +1,97 @@ + 1, + 'post_content' => '@alex hello', + ) + ); + + self::$users['https://example.com/alex'] = array( + 'url' => 'https://example.com/alex', + 'inbox' => 'https://example.com/alex/inbox', + 'name' => 'alex', + ); + + add_filter( + 'activitypub_extract_mentions', + function( $mentions ) { + $mentions[] = 'https://example.com/alex'; + return $mentions; + }, + 10 + ); + + $pre_http_request = new MockAction(); + add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 ); + + $activitypub_post = new \Activitypub\Model\Post( $post ); + \Activitypub\Activity_Dispatcher::send_post_activity( $activitypub_post ); + + $this->assertSame( 1, $pre_http_request->get_call_count() ); + $all_args = $pre_http_request->get_args(); + $first_call_args = $all_args[0]; + $this->assertEquals( 'https://example.com/alex/inbox', $first_call_args[2] ); + + remove_all_filters( 'activitypub_from_post_object' ); + remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 ); + } + + public function set_up() { + parent::set_up(); + + add_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ), 10, 3 ); + add_filter( 'http_response', array( get_called_class(), 'http_response' ), 10, 3 ); + add_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ), 10, 2 ); + add_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ), 10, 2 ); + add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 ); + + _delete_all_posts(); + } + + public function tear_down() { + remove_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ) ); + remove_filter( 'http_response', array( get_called_class(), 'http_response' ) ); + remove_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ) ); + remove_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ) ); + remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) ); + parent::tear_down(); + } + + public static function pre_get_remote_metadata_by_actor( $pre, $actor ) { + if ( isset( self::$users[ $actor ] ) ) { + return self::$users[ $actor ]; + } + return $pre; + } + + public static function http_request_host_is_external( $in, $host ) { + if ( in_array( $host, array( 'example.com', 'example.org' ), true ) ) { + return true; + } + return $in; + } + public static function http_request_args( $args, $url ) { + if ( in_array( parse_url( $url, PHP_URL_HOST ), array( 'example.com', 'example.org' ), true ) ) { + $args['reject_unsafe_urls'] = false; + } + return $args; + } + public static function pre_http_request( $preempt, $request, $url ) { + return array( + 'headers' => array( + 'content-type' => 'text/json', + ), + 'body' => '', + 'response' => array( + 'code' => 202, + ), + ); + } + + public static function http_response( $response, $args, $url ) { + return $response; + } +} diff --git a/tests/test-class-activitypub-activity.php b/tests/test-class-activitypub-activity.php new file mode 100644 index 0000000..eff350d --- /dev/null +++ b/tests/test-class-activitypub-activity.php @@ -0,0 +1,32 @@ + 1, + 'post_content' => '@alex hello', + ) + ); + + add_filter( + 'activitypub_extract_mentions', + function( $mentions, $post ) { + $mentions[] = 'https://example.com/alex'; + return $mentions; + }, + 10, + 2 + ); + + $activitypub_post = new \Activitypub\Model\Post( $post ); + + $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL ); + $activitypub_activity->from_post( $activitypub_post ); + + $this->assertContains( \get_rest_url( null, '/activitypub/1.0/users/1/followers' ), $activitypub_activity->get_cc() ); + $this->assertContains( 'https://example.com/alex', $activitypub_activity->get_cc() ); + + remove_all_filters( 'activitypub_from_post_object' ); + \wp_trash_post( $post ); + } +} From 0230cf7d70d6310c88e3416c58e6dafa5a4a2274 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 09:20:25 +0100 Subject: [PATCH 65/94] Restructure unit test to make it work again --- tests/test-class-activitypub-hashtag.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test-class-activitypub-hashtag.php b/tests/test-class-activitypub-hashtag.php index e7699d2..5c207bd 100644 --- a/tests/test-class-activitypub-hashtag.php +++ b/tests/test-class-activitypub-hashtag.php @@ -4,26 +4,26 @@ class Test_Activitypub_Hashtag extends WP_UnitTestCase { * @dataProvider the_content_provider */ public function test_the_content( $content, $content_with_hashtag ) { - $content = \Activitypub\Hashtag::the_content( $content ); - - $this->assertEquals( $content_with_hashtag, $content ); - } - - public function the_content_provider() { \wp_create_term( 'object', 'post_tag' ); $object = \get_term_by( 'name', 'object', 'post_tag' ); $link = \get_term_link( $object, 'post_tag' ); + $content = \Activitypub\Hashtag::the_content( $content ); + + $this->assertEquals( sprintf( $content_with_hashtag, $link ), $content ); + } + + public function the_content_provider() { return array( array( 'test', 'test' ), array( '#test', '#test' ), array( 'hallo #test test', 'hallo #test test' ), - array( 'hallo #object test', 'hallo
                        test' ), - array( '#object test', ' test' ), + array( 'hallo #object test', 'hallo test' ), + array( '#object test', ' test' ), array( 'hallo test test', 'hallo test test' ), array( 'hallo #test test', 'hallo #test test' ), - array( '
                        hallo #object test
                        ', '
                        hallo test
                        ' ), - array( '
                        hallo #object
                        ', '
                        hallo
                        ' ), + array( '
                        hallo #object test
                        ', '
                        hallo test
                        ' ), + array( '
                        hallo #object
                        ', '
                        hallo
                        ' ), array( '
                        #object
                        ', '
                        #object
                        ' ), array( '#object', '#object' ), array( '
                        object', '
                        object' ), From 7ebb89e92ecdf1e6d4d1a7a092e1953b732ea1f0 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 09:28:00 +0100 Subject: [PATCH 66/94] phpcs lint fixes --- includes/class-hashtag.php | 8 ++++---- ...notiz-blog-2022-11-14-the-at-protocol.json | 1 + ...z-blog-2022-11-14-the-at-protocol.response | 1 - ...-class-activitypub-activity-dispatcher.php | 2 +- ...-class-friends-feed-parser-activitypub.php | 20 +++++++++---------- 5 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.json delete mode 100644 tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.response diff --git a/includes/class-hashtag.php b/includes/class-hashtag.php index 2b7fa54..342320d 100644 --- a/includes/class-hashtag.php +++ b/includes/class-hashtag.php @@ -21,15 +21,15 @@ class Hashtag { * Filter to save #tags as real WordPress tags * * @param int $id the rev-id - * @param array $data the post-data as array + * @param WP_Post $post the post * * @return */ - public static function insert_post( $id, $data ) { - if ( \preg_match_all( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', $data->post_content, $match ) ) { + public static function insert_post( $id, $post ) { + if ( \preg_match_all( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', $post->post_content, $match ) ) { $tags = \implode( ', ', $match[1] ); - \wp_add_post_tags( $data->post_parent, $tags ); + \wp_add_post_tags( $post->post_parent, $tags ); } return $id; diff --git a/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.json b/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.json new file mode 100644 index 0000000..8b5a942 --- /dev/null +++ b/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.json @@ -0,0 +1 @@ +{"headers":{"date":"Fri, 09 Dec 2022 08:25:15 GMT","content-type":"application\/activity+json","server":"nginx","x-xrds-location":"https:\/\/notiz.blog\/?xrds","x-yadis-location":"https:\/\/notiz.blog\/?xrds","x-pingback":"https:\/\/notiz.blog\/xmlrpc.php","link":"; rel=\"micropub_media\", ; rel=\"micropub\", ; rel=\"friends-base-url\", ; rel=\"webmention\", ; rel=\"http:\/\/webmention.org\/\", ; rel=\"https:\/\/api.w.org\/\", ; rel=\"alternate\"; type=\"application\/json\", ; rel=shortlink","cache-control":"max-age=0, public","expires":"Fri, 09 Dec 2022 08:25:15 GMT","x-xss-protection":"1; mode=block","x-content-type-options":"nosniff","strict-transport-security":"max-age=31536000","x-frame-options":"SAMEORIGIN","referrer-policy":"strict-origin-when-cross-origin","x-clacks-overhead":"GNU Terry Pratchett"},"body":"{\"@context\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams\",\"https:\\\/\\\/w3id.org\\\/security\\\/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"PropertyValue\":\"schema:PropertyValue\",\"schema\":\"http:\\\/\\\/schema.org#\",\"pt\":\"https:\\\/\\\/joinpeertube.org\\\/ns#\",\"toot\":\"http:\\\/\\\/joinmastodon.org\\\/ns#\",\"value\":\"schema:value\",\"Hashtag\":\"as:Hashtag\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"featuredTags\":{\"@id\":\"toot:featuredTags\",\"@type\":\"@id\"}}],\"id\":\"https:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\",\"type\":\"Note\",\"published\":\"2022-11-14T16:49:01Z\",\"attributedTo\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"summary\":null,\"inReplyTo\":null,\"content\":\"\\u003Cp\\u003EVor zwei Jahren wollte Twitter in das \\u201eDezentrale Netzwerke\\u201c-Business einsteigen und gr\\u00fcndete eigens daf\\u00fcr das \\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2019\\\/12\\\/13\\\/twitiverse\\\/\\u0022 data-type=\\u0022post\\u0022 data-id=\\u002218831\\u0022\\u003EProjekt Bluesky\\u003C\\\/a\\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\\u00f6sung f\\u00fcr Twitter sei und wir alle \\u003Cem\\u003Efieberten\\u003C\\\/em\\u003E mit ob es nun \\u003Ca href=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022\\u003EActivityPub\\u003C\\\/a\\u003E oder doch \\u003Ca href=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022\\u003EMatrix\\u003C\\\/a\\u003E werden w\\u00fcrde\\u2026 \\u003C\\\/p\\u003E\\u003Cp\\u003EAber das Warten hat ein Ende! \\u003Ca href=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022\\u003EBluesky hat verk\\u00fcndet wie es weiter geht\\u003C\\\/a\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003ESie entwickeln ein neues Protokoll!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EDas \\u003Cem\\u003EAT Protocol\\u003C\\\/em\\u003E, kurz f\\u00fcr \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003EIch hab mir die \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022\\u003EFAQ\\u003C\\\/a\\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/data-repos\\u0022\\u003Esigned data repositories\\u003C\\\/a\\u003E and \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/identity\\u0022\\u003EDIDs\\u003C\\\/a\\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\\u2019s previous data.\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas erinnert mich ein bisschen an die Subline von meinem \\u003Ca href=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022\\u003EOpenWeb-Icons Font\\u003C\\\/a\\u003E:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EWhy \\u003Cem\\u003EOpenWeb Icons\\u003C\\\/em\\u003E? Because \\u003Ca href=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022\\u003EFont Awesome\\u003C\\\/a\\u003E had no RSS-icon [\\u2026]\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWeil ActivityPub keine perfekte L\\u00f6sung f\\u00fcr \\u201eAccount portability\\u201c hat, bauen sie ein komplett neues Protokoll?\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EActivityPub ist sicherlich nicht \\u201efeature complete\\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\\u003C\\\/p\\u003E\\u003Cp\\u003EWarum macht sich das W3C \\u00fcberhaupt noch die M\\u00fche \\u201eStandards\\u201c zu definieren?\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWegen der Interoperabilit\\u00e4t!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EW\\u00fcrde Twitter mit HTTP(S), HTML oder CSS \\u00e4hnlich umgehen, w\\u00fcrde der Browser einfach leer bleiben, weil das \\u0026$%\\u00a7\\u0026 Internet nur mit einheitlichen Standards funktioniert!\\u003C\\\/p\\u003E\\u003Cp\\u003EUnd das gleiche gilt auch f\\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\\u00fcber hab ich tragischerweise schon vor \\u003Cstrong\\u003E10 Jahren\\u003C\\\/strong\\u003E geschrieben!\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EDiaspora* wurde kaum \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20130630113539\\\/http:\\\/\\\/blog.diasporafoundation.org\\\/2012\\\/08\\\/27\\\/announcement-diaspora-will-now-be-a-community-project.html\\u0022\\u003Ef\\u00fcr \\u201etot\\u201c erkl\\u00e4rt\\u003C\\\/a\\u003E und schon steht das n\\u00e4chste Projekt in den Startl\\u00f6chern! \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20190603031810\\\/https:\\\/\\\/tent.io\\\/\\u0022\\u003ETent.io\\u003C\\\/a\\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\\u2026 was haben die Diasporas \\u0026 Co. bisher geschaffen? Ziel war es Facebooks \\u201eWalled Gardens\\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \\u201eWalled Gardens\\u201c. Na danke!\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2012\\\/11\\\/15\\\/dezentrale-walled-gardens\\\/\\u0022\\u003EDezentrale \\u201eWalled Gardens\\u201c\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas \\u003Ca href=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022\\u003Efediverse\\u003C\\\/a\\u003E hat (wie schon erw\\u00e4hnt) bisher einen gro\\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\\u00e4gungen vernetzt! Ich \\u003Cs\\u003Eglaube\\u003C\\\/s\\u003E bin der festen \\u00dcberzeugung, dass sich diesmal wirklich das offene Format (\\u003Cem\\u003EActivityPub\\u003C\\\/em\\u003E) durchsetzen wird und Blueskys \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022\\u003EBen Werdmuller\\u003C\\\/a\\u003E hat eine gesunde Einstellung zu dem Thema:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EI\\u2019m so burned out by open source social, but I\\u2019m glad to see people throw energy at the problem, even if it\\u2019s not how I would have gone about it.\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022\\u003ETwitter\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\\u00dfer dass wir in der \\u003Ca href=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022\\u003Eaktuellen Folge\\u003C\\\/a\\u003E des neunetzcasts sehr ausgiebig \\u00fcber genau dieses Problem gesprochen haben!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/activitypub\\\/\\u0022\\u003E#activitypub\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/bluesky\\\/\\u0022\\u003E#bluesky\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/fediverse\\\/\\u0022\\u003E#fediverse\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/matrix\\\/\\u0022\\u003E#matrix\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/twitter\\\/\\u0022\\u003E#twitter\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u0022\\u003Ehttps:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\",\"contentMap\":{\"de\":\"\\u003Cp\\u003EVor zwei Jahren wollte Twitter in das \\u201eDezentrale Netzwerke\\u201c-Business einsteigen und gr\\u00fcndete eigens daf\\u00fcr das \\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2019\\\/12\\\/13\\\/twitiverse\\\/\\u0022 data-type=\\u0022post\\u0022 data-id=\\u002218831\\u0022\\u003EProjekt Bluesky\\u003C\\\/a\\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\\u00f6sung f\\u00fcr Twitter sei und wir alle \\u003Cem\\u003Efieberten\\u003C\\\/em\\u003E mit ob es nun \\u003Ca href=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022\\u003EActivityPub\\u003C\\\/a\\u003E oder doch \\u003Ca href=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022\\u003EMatrix\\u003C\\\/a\\u003E werden w\\u00fcrde\\u2026 \\u003C\\\/p\\u003E\\u003Cp\\u003EAber das Warten hat ein Ende! \\u003Ca href=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022\\u003EBluesky hat verk\\u00fcndet wie es weiter geht\\u003C\\\/a\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003ESie entwickeln ein neues Protokoll!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EDas \\u003Cem\\u003EAT Protocol\\u003C\\\/em\\u003E, kurz f\\u00fcr \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003EIch hab mir die \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022\\u003EFAQ\\u003C\\\/a\\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/data-repos\\u0022\\u003Esigned data repositories\\u003C\\\/a\\u003E and \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/identity\\u0022\\u003EDIDs\\u003C\\\/a\\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\\u2019s previous data.\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas erinnert mich ein bisschen an die Subline von meinem \\u003Ca href=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022\\u003EOpenWeb-Icons Font\\u003C\\\/a\\u003E:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EWhy \\u003Cem\\u003EOpenWeb Icons\\u003C\\\/em\\u003E? Because \\u003Ca href=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022\\u003EFont Awesome\\u003C\\\/a\\u003E had no RSS-icon [\\u2026]\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWeil ActivityPub keine perfekte L\\u00f6sung f\\u00fcr \\u201eAccount portability\\u201c hat, bauen sie ein komplett neues Protokoll?\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EActivityPub ist sicherlich nicht \\u201efeature complete\\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\\u003C\\\/p\\u003E\\u003Cp\\u003EWarum macht sich das W3C \\u00fcberhaupt noch die M\\u00fche \\u201eStandards\\u201c zu definieren?\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWegen der Interoperabilit\\u00e4t!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EW\\u00fcrde Twitter mit HTTP(S), HTML oder CSS \\u00e4hnlich umgehen, w\\u00fcrde der Browser einfach leer bleiben, weil das \\u0026$%\\u00a7\\u0026 Internet nur mit einheitlichen Standards funktioniert!\\u003C\\\/p\\u003E\\u003Cp\\u003EUnd das gleiche gilt auch f\\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\\u00fcber hab ich tragischerweise schon vor \\u003Cstrong\\u003E10 Jahren\\u003C\\\/strong\\u003E geschrieben!\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EDiaspora* wurde kaum \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20130630113539\\\/http:\\\/\\\/blog.diasporafoundation.org\\\/2012\\\/08\\\/27\\\/announcement-diaspora-will-now-be-a-community-project.html\\u0022\\u003Ef\\u00fcr \\u201etot\\u201c erkl\\u00e4rt\\u003C\\\/a\\u003E und schon steht das n\\u00e4chste Projekt in den Startl\\u00f6chern! \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20190603031810\\\/https:\\\/\\\/tent.io\\\/\\u0022\\u003ETent.io\\u003C\\\/a\\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\\u2026 was haben die Diasporas \\u0026 Co. bisher geschaffen? Ziel war es Facebooks \\u201eWalled Gardens\\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \\u201eWalled Gardens\\u201c. Na danke!\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2012\\\/11\\\/15\\\/dezentrale-walled-gardens\\\/\\u0022\\u003EDezentrale \\u201eWalled Gardens\\u201c\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas \\u003Ca href=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022\\u003Efediverse\\u003C\\\/a\\u003E hat (wie schon erw\\u00e4hnt) bisher einen gro\\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\\u00e4gungen vernetzt! Ich \\u003Cs\\u003Eglaube\\u003C\\\/s\\u003E bin der festen \\u00dcberzeugung, dass sich diesmal wirklich das offene Format (\\u003Cem\\u003EActivityPub\\u003C\\\/em\\u003E) durchsetzen wird und Blueskys \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022\\u003EBen Werdmuller\\u003C\\\/a\\u003E hat eine gesunde Einstellung zu dem Thema:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EI\\u2019m so burned out by open source social, but I\\u2019m glad to see people throw energy at the problem, even if it\\u2019s not how I would have gone about it.\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022\\u003ETwitter\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\\u00dfer dass wir in der \\u003Ca href=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022\\u003Eaktuellen Folge\\u003C\\\/a\\u003E des neunetzcasts sehr ausgiebig \\u00fcber genau dieses Problem gesprochen haben!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/activitypub\\\/\\u0022\\u003E#activitypub\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/bluesky\\\/\\u0022\\u003E#bluesky\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/fediverse\\\/\\u0022\\u003E#fediverse\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/matrix\\\/\\u0022\\u003E#matrix\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/twitter\\\/\\u0022\\u003E#twitter\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u0022\\u003Ehttps:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\"},\"to\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams#Public\"],\"cc\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams#Public\"],\"attachment\":[{\"type\":\"Image\",\"url\":\"https:\\\/\\\/notiz.blog\\\/wp-content\\\/uploads\\\/2022\\\/11\\\/the-at-protocol.png\",\"mediaType\":\"image\\\/png\",\"name\":\"The Logo of the AT Protocol\"}],\"tag\":[{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/activitypub\\\/\",\"name\":\"#activitypub\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/bluesky\\\/\",\"name\":\"#bluesky\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/fediverse\\\/\",\"name\":\"#fediverse\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/matrix\\\/\",\"name\":\"#matrix\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/twitter\\\/\",\"name\":\"#twitter\"}]}","response":{"code":200}} \ No newline at end of file diff --git a/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.response b/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.response deleted file mode 100644 index d6705a1..0000000 --- a/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.response +++ /dev/null @@ -1 +0,0 @@ -a:3:{s:7:"headers";a:15:{s:4:"date";s:29:"Fri, 02 Dec 2022 12:09:11 GMT";s:12:"content-type";s:25:"application/activity+json";s:6:"server";s:5:"nginx";s:15:"x-xrds-location";s:24:"https://notiz.blog/?xrds";s:16:"x-yadis-location";s:24:"https://notiz.blog/?xrds";s:10:"x-pingback";s:29:"https://notiz.blog/xmlrpc.php";s:4:"link";s:541:"; rel="micropub_media", ; rel="micropub", ; rel="friends-base-url", ; rel="webmention", ; rel="http://webmention.org/", ; rel="https://api.w.org/", ; rel="alternate"; type="application/json", ; rel=shortlink";s:13:"cache-control";s:17:"max-age=0, public";s:7:"expires";s:29:"Fri, 02 Dec 2022 12:09:11 GMT";s:16:"x-xss-protection";s:13:"1; mode=block";s:22:"x-content-type-options";s:7:"nosniff";s:25:"strict-transport-security";s:16:"max-age=31536000";s:15:"x-frame-options";s:10:"SAMEORIGIN";s:15:"referrer-policy";s:31:"strict-origin-when-cross-origin";s:17:"x-clacks-overhead";s:19:"GNU Terry Pratchett";}s:4:"body";s:18048:"{"@context":["https:\/\/www.w3.org\/ns\/activitystreams","https:\/\/w3id.org\/security\/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","PropertyValue":"schema:PropertyValue","schema":"http:\/\/schema.org#","pt":"https:\/\/joinpeertube.org\/ns#","toot":"http:\/\/joinmastodon.org\/ns#","value":"schema:value","Hashtag":"as:Hashtag","featured":{"@id":"toot:featured","@type":"@id"},"featuredTags":{"@id":"toot:featuredTags","@type":"@id"}}],"id":"https:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/","type":"Note","published":"2022-11-14T16:49:01Z","attributedTo":"https:\/\/notiz.blog\/author\/matthias-pfefferle\/","summary":null,"inReplyTo":null,"content":"\u003Cp\u003EVor zwei Jahren wollte Twitter in das \u201eDezentrale Netzwerke\u201c-Business einsteigen und gr\u00fcndete eigens daf\u00fcr das \u003Ca href=\u0022https:\/\/notiz.blog\/2019\/12\/13\/twitiverse\/\u0022 data-type=\u0022post\u0022 data-id=\u002218831\u0022\u003EProjekt Bluesky\u003C\/a\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\u00f6sung f\u00fcr Twitter sei und wir alle \u003Cem\u003Efieberten\u003C\/em\u003E mit ob es nun \u003Ca href=\u0022https:\/\/www.w3.org\/TR\/activitypub\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/www.w3.org\/TR\/activitypub\/\u0022\u003EActivityPub\u003C\/a\u003E oder doch \u003Ca href=\u0022https:\/\/matrix.org\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/matrix.org\/\u0022\u003EMatrix\u003C\/a\u003E werden w\u00fcrde\u2026 \u003C\/p\u003E\u003Cp\u003EAber das Warten hat ein Ende! \u003Ca href=\u0022https:\/\/blueskyweb.xyz\/blog\/10-18-2022-the-at-protocol\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/blueskyweb.xyz\/blog\/10-18-2022-the-at-protocol\u0022\u003EBluesky hat verk\u00fcndet wie es weiter geht\u003C\/a\u003E!\u003C\/p\u003E\u003Cp\u003E\u003Cstrong\u003ESie entwickeln ein neues Protokoll!\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EDas \u003Cem\u003EAT Protocol\u003C\/em\u003E, kurz f\u00fcr \u003Cem\u003EAuthenticated Transfer Protocol\u003C\/em\u003E!\u003C\/p\u003E\u003Cp\u003EIch hab mir die \u003Ca href=\u0022https:\/\/atproto.com\/guides\/faq\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/atproto.com\/guides\/faq\u0022\u003EFAQ\u003C\/a\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \u003Ca href=\u0022https:\/\/atproto.com\/guides\/data-repos\u0022\u003Esigned data repositories\u003C\/a\u003E and \u003Ca href=\u0022https:\/\/atproto.com\/guides\/identity\u0022\u003EDIDs\u003C\/a\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\u2019s previous data.\u003C\/p\u003E\n\u003C\/blockquote\u003E\u003Cp\u003EDas erinnert mich ein bisschen an die Subline von meinem \u003Ca href=\u0022https:\/\/pfefferle.dev\/openwebicons\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/pfefferle.dev\/openwebicons\/\u0022\u003EOpenWeb-Icons Font\u003C\/a\u003E:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EWhy \u003Cem\u003EOpenWeb Icons\u003C\/em\u003E? Because \u003Ca href=\u0022https:\/\/fortawesome.com\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/fortawesome.com\/\u0022\u003EFont Awesome\u003C\/a\u003E had no RSS-icon [\u2026]\u003C\/p\u003E\n\u003C\/blockquote\u003E\u003Cp\u003E\u003Cstrong\u003EWeil ActivityPub keine perfekte L\u00f6sung f\u00fcr \u201eAccount portability\u201c hat, bauen sie ein komplett neues Protokoll?\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EActivityPub ist sicherlich nicht \u201efeature complete\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\u003C\/p\u003E\u003Cp\u003EWarum macht sich das W3C \u00fcberhaupt noch die M\u00fche \u201eStandards\u201c zu definieren?\u003C\/p\u003E\u003Cp\u003E\u003Cstrong\u003EWegen der Interoperabilit\u00e4t!\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EW\u00fcrde Twitter mit HTTP(S), HTML oder CSS \u00e4hnlich umgehen, w\u00fcrde der Browser einfach leer bleiben, weil das \u0026$%\u00a7\u0026 Internet nur mit einheitlichen Standards funktioniert!\u003C\/p\u003E\u003Cp\u003EUnd das gleiche gilt auch f\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\u00fcber hab ich tragischerweise schon vor \u003Cstrong\u003E10 Jahren\u003C\/strong\u003E geschrieben!\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EDiaspora* wurde kaum \u003Ca href=\u0022https:\/\/web.archive.org\/web\/20130630113539\/http:\/\/blog.diasporafoundation.org\/2012\/08\/27\/announcement-diaspora-will-now-be-a-community-project.html\u0022\u003Ef\u00fcr \u201etot\u201c erkl\u00e4rt\u003C\/a\u003E und schon steht das n\u00e4chste Projekt in den Startl\u00f6chern! \u003Ca href=\u0022https:\/\/web.archive.org\/web\/20190603031810\/https:\/\/tent.io\/\u0022\u003ETent.io\u003C\/a\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\u2026 was haben die Diasporas \u0026 Co. bisher geschaffen? Ziel war es Facebooks \u201eWalled Gardens\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \u201eWalled Gardens\u201c. Na danke!\u003C\/p\u003E\n\u003Ccite\u003E\u003Ca href=\u0022https:\/\/notiz.blog\/2012\/11\/15\/dezentrale-walled-gardens\/\u0022\u003EDezentrale \u201eWalled Gardens\u201c\u003C\/a\u003E\u003C\/cite\u003E\u003C\/blockquote\u003E\u003Cp\u003EDas \u003Ca href=\u0022https:\/\/the-federation.info\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/the-federation.info\/\u0022\u003Efediverse\u003C\/a\u003E hat (wie schon erw\u00e4hnt) bisher einen gro\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\u00e4gungen vernetzt! Ich \u003Cs\u003Eglaube\u003C\/s\u003E bin der festen \u00dcberzeugung, dass sich diesmal wirklich das offene Format (\u003Cem\u003EActivityPub\u003C\/em\u003E) durchsetzen wird und Blueskys \u003Cem\u003EAuthenticated Transfer Protocol\u003C\/em\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \u003C\/p\u003E\u003Cp\u003E\u003Ca href=\u0022https:\/\/twitter.com\/benwerd\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/twitter.com\/benwerd\u0022\u003EBen Werdmuller\u003C\/a\u003E hat eine gesunde Einstellung zu dem Thema:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EI\u2019m so burned out by open source social, but I\u2019m glad to see people throw energy at the problem, even if it\u2019s not how I would have gone about it.\u003C\/p\u003E\n\u003Ccite\u003E\u003Ca href=\u0022https:\/\/twitter.com\/benwerd\/status\/1582554417693270016\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/twitter.com\/benwerd\/status\/1582554417693270016\u0022\u003ETwitter\u003C\/a\u003E\u003C\/cite\u003E\u003C\/blockquote\u003E\u003Cp\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\u00dfer dass wir in der \u003Ca href=\u0022https:\/\/neunetz.fm\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/neunetz.fm\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\/\u0022\u003Eaktuellen Folge\u003C\/a\u003E des neunetzcasts sehr ausgiebig \u00fcber genau dieses Problem gesprochen haben!\u003C\/p\u003E\u003Cp\u003E\u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/activitypub\/\u0022\u003E#activitypub\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/bluesky\/\u0022\u003E#bluesky\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/fediverse\/\u0022\u003E#fediverse\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/matrix\/\u0022\u003E#matrix\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/twitter\/\u0022\u003E#twitter\u003C\/a\u003E\u003C\/p\u003E\u003Cp\u003E\u003Ca href=\u0022https:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/\u0022\u003Ehttps:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/\u003C\/a\u003E\u003C\/p\u003E","contentMap":{"de":"\u003Cp\u003EVor zwei Jahren wollte Twitter in das \u201eDezentrale Netzwerke\u201c-Business einsteigen und gr\u00fcndete eigens daf\u00fcr das \u003Ca href=\u0022https:\/\/notiz.blog\/2019\/12\/13\/twitiverse\/\u0022 data-type=\u0022post\u0022 data-id=\u002218831\u0022\u003EProjekt Bluesky\u003C\/a\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\u00f6sung f\u00fcr Twitter sei und wir alle \u003Cem\u003Efieberten\u003C\/em\u003E mit ob es nun \u003Ca href=\u0022https:\/\/www.w3.org\/TR\/activitypub\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/www.w3.org\/TR\/activitypub\/\u0022\u003EActivityPub\u003C\/a\u003E oder doch \u003Ca href=\u0022https:\/\/matrix.org\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/matrix.org\/\u0022\u003EMatrix\u003C\/a\u003E werden w\u00fcrde\u2026 \u003C\/p\u003E\u003Cp\u003EAber das Warten hat ein Ende! \u003Ca href=\u0022https:\/\/blueskyweb.xyz\/blog\/10-18-2022-the-at-protocol\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/blueskyweb.xyz\/blog\/10-18-2022-the-at-protocol\u0022\u003EBluesky hat verk\u00fcndet wie es weiter geht\u003C\/a\u003E!\u003C\/p\u003E\u003Cp\u003E\u003Cstrong\u003ESie entwickeln ein neues Protokoll!\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EDas \u003Cem\u003EAT Protocol\u003C\/em\u003E, kurz f\u00fcr \u003Cem\u003EAuthenticated Transfer Protocol\u003C\/em\u003E!\u003C\/p\u003E\u003Cp\u003EIch hab mir die \u003Ca href=\u0022https:\/\/atproto.com\/guides\/faq\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/atproto.com\/guides\/faq\u0022\u003EFAQ\u003C\/a\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \u003Ca href=\u0022https:\/\/atproto.com\/guides\/data-repos\u0022\u003Esigned data repositories\u003C\/a\u003E and \u003Ca href=\u0022https:\/\/atproto.com\/guides\/identity\u0022\u003EDIDs\u003C\/a\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\u2019s previous data.\u003C\/p\u003E\n\u003C\/blockquote\u003E\u003Cp\u003EDas erinnert mich ein bisschen an die Subline von meinem \u003Ca href=\u0022https:\/\/pfefferle.dev\/openwebicons\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/pfefferle.dev\/openwebicons\/\u0022\u003EOpenWeb-Icons Font\u003C\/a\u003E:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EWhy \u003Cem\u003EOpenWeb Icons\u003C\/em\u003E? Because \u003Ca href=\u0022https:\/\/fortawesome.com\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/fortawesome.com\/\u0022\u003EFont Awesome\u003C\/a\u003E had no RSS-icon [\u2026]\u003C\/p\u003E\n\u003C\/blockquote\u003E\u003Cp\u003E\u003Cstrong\u003EWeil ActivityPub keine perfekte L\u00f6sung f\u00fcr \u201eAccount portability\u201c hat, bauen sie ein komplett neues Protokoll?\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EActivityPub ist sicherlich nicht \u201efeature complete\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\u003C\/p\u003E\u003Cp\u003EWarum macht sich das W3C \u00fcberhaupt noch die M\u00fche \u201eStandards\u201c zu definieren?\u003C\/p\u003E\u003Cp\u003E\u003Cstrong\u003EWegen der Interoperabilit\u00e4t!\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EW\u00fcrde Twitter mit HTTP(S), HTML oder CSS \u00e4hnlich umgehen, w\u00fcrde der Browser einfach leer bleiben, weil das \u0026$%\u00a7\u0026 Internet nur mit einheitlichen Standards funktioniert!\u003C\/p\u003E\u003Cp\u003EUnd das gleiche gilt auch f\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\u00fcber hab ich tragischerweise schon vor \u003Cstrong\u003E10 Jahren\u003C\/strong\u003E geschrieben!\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EDiaspora* wurde kaum \u003Ca href=\u0022https:\/\/web.archive.org\/web\/20130630113539\/http:\/\/blog.diasporafoundation.org\/2012\/08\/27\/announcement-diaspora-will-now-be-a-community-project.html\u0022\u003Ef\u00fcr \u201etot\u201c erkl\u00e4rt\u003C\/a\u003E und schon steht das n\u00e4chste Projekt in den Startl\u00f6chern! \u003Ca href=\u0022https:\/\/web.archive.org\/web\/20190603031810\/https:\/\/tent.io\/\u0022\u003ETent.io\u003C\/a\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\u2026 was haben die Diasporas \u0026 Co. bisher geschaffen? Ziel war es Facebooks \u201eWalled Gardens\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \u201eWalled Gardens\u201c. Na danke!\u003C\/p\u003E\n\u003Ccite\u003E\u003Ca href=\u0022https:\/\/notiz.blog\/2012\/11\/15\/dezentrale-walled-gardens\/\u0022\u003EDezentrale \u201eWalled Gardens\u201c\u003C\/a\u003E\u003C\/cite\u003E\u003C\/blockquote\u003E\u003Cp\u003EDas \u003Ca href=\u0022https:\/\/the-federation.info\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/the-federation.info\/\u0022\u003Efediverse\u003C\/a\u003E hat (wie schon erw\u00e4hnt) bisher einen gro\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\u00e4gungen vernetzt! Ich \u003Cs\u003Eglaube\u003C\/s\u003E bin der festen \u00dcberzeugung, dass sich diesmal wirklich das offene Format (\u003Cem\u003EActivityPub\u003C\/em\u003E) durchsetzen wird und Blueskys \u003Cem\u003EAuthenticated Transfer Protocol\u003C\/em\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \u003C\/p\u003E\u003Cp\u003E\u003Ca href=\u0022https:\/\/twitter.com\/benwerd\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/twitter.com\/benwerd\u0022\u003EBen Werdmuller\u003C\/a\u003E hat eine gesunde Einstellung zu dem Thema:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EI\u2019m so burned out by open source social, but I\u2019m glad to see people throw energy at the problem, even if it\u2019s not how I would have gone about it.\u003C\/p\u003E\n\u003Ccite\u003E\u003Ca href=\u0022https:\/\/twitter.com\/benwerd\/status\/1582554417693270016\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/twitter.com\/benwerd\/status\/1582554417693270016\u0022\u003ETwitter\u003C\/a\u003E\u003C\/cite\u003E\u003C\/blockquote\u003E\u003Cp\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\u00dfer dass wir in der \u003Ca href=\u0022https:\/\/neunetz.fm\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/neunetz.fm\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\/\u0022\u003Eaktuellen Folge\u003C\/a\u003E des neunetzcasts sehr ausgiebig \u00fcber genau dieses Problem gesprochen haben!\u003C\/p\u003E\u003Cp\u003E\u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/activitypub\/\u0022\u003E#activitypub\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/bluesky\/\u0022\u003E#bluesky\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/fediverse\/\u0022\u003E#fediverse\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/matrix\/\u0022\u003E#matrix\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/twitter\/\u0022\u003E#twitter\u003C\/a\u003E\u003C\/p\u003E\u003Cp\u003E\u003Ca href=\u0022https:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/\u0022\u003Ehttps:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/\u003C\/a\u003E\u003C\/p\u003E"},"to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"cc":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"attachment":[{"type":"Image","url":"https:\/\/notiz.blog\/wp-content\/uploads\/2022\/11\/the-at-protocol.png","mediaType":"image\/png","name":"The Logo of the AT Protocol"}],"tag":[{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/activitypub\/","name":"#activitypub"},{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/bluesky\/","name":"#bluesky"},{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/fediverse\/","name":"#fediverse"},{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/matrix\/","name":"#matrix"},{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/twitter\/","name":"#twitter"}]}";s:8:"response";a:1:{s:4:"code";i:200;}} \ No newline at end of file diff --git a/tests/test-class-activitypub-activity-dispatcher.php b/tests/test-class-activitypub-activity-dispatcher.php index a5dd7b9..228ee6d 100644 --- a/tests/test-class-activitypub-activity-dispatcher.php +++ b/tests/test-class-activitypub-activity-dispatcher.php @@ -74,7 +74,7 @@ class Test_Activitypub_Activity_Dispatcher extends WP_UnitTestCase { return $in; } public static function http_request_args( $args, $url ) { - if ( in_array( parse_url( $url, PHP_URL_HOST ), array( 'example.com', 'example.org' ), true ) ) { + if ( in_array( wp_parse_url( $url, PHP_URL_HOST ), array( 'example.com', 'example.org' ), true ) ) { $args['reject_unsafe_urls'] = false; } return $args; diff --git a/tests/test-class-friends-feed-parser-activitypub.php b/tests/test-class-friends-feed-parser-activitypub.php index 79f5a74..d0ae69f 100644 --- a/tests/test-class-friends-feed-parser-activitypub.php +++ b/tests/test-class-friends-feed-parser-activitypub.php @@ -22,7 +22,7 @@ class Test_Friends_Feed_Parser_ActivityPub extends \WP_UnitTestCase { // Let's post a new Note through the REST API. $date = gmdate( \DATE_W3C, $now++ ); $id = 'test' . $status_id; - $content = 'Test ' . $date . ' ' . rand(); + $content = 'Test ' . $date . ' ' . wp_rand(); $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/' . get_current_user_id() . '/inbox' ); $request->set_param( 'type', 'Create' ); @@ -74,7 +74,7 @@ class Test_Friends_Feed_Parser_ActivityPub extends \WP_UnitTestCase { // Do another test post, this time with a URL that has an @-id. $date = gmdate( \DATE_W3C, $now++ ); $id = 'test' . $status_id; - $content = 'Test ' . $date . ' ' . rand(); + $content = 'Test ' . $date . ' ' . wp_rand(); $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/' . get_current_user_id() . '/inbox' ); $request->set_param( 'type', 'Create' ); @@ -142,10 +142,10 @@ class Test_Friends_Feed_Parser_ActivityPub extends \WP_UnitTestCase { $this->assertEquals( 202, $response->get_status() ); $p = wp_parse_url( $object ); - $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.response'; + $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json'; $this->assertFileExists( $cache ); - $object = json_decode( wp_remote_retrieve_body( unserialize( file_get_contents( $cache ) ) ) ); + $object = json_decode( wp_remote_retrieve_body( json_decode( file_get_contents( $cache ), true ) ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $posts = get_posts( array( @@ -244,18 +244,18 @@ class Test_Friends_Feed_Parser_ActivityPub extends \WP_UnitTestCase { return $in; } public static function http_request_args( $args, $url ) { - if ( in_array( parse_url( $url, PHP_URL_HOST ), array( 'mastodon.local' ), true ) ) { + if ( in_array( wp_parse_url( $url, PHP_URL_HOST ), array( 'mastodon.local' ), true ) ) { $args['reject_unsafe_urls'] = false; } return $args; } public static function pre_http_request( $preempt, $request, $url ) { $p = wp_parse_url( $url ); - $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.response'; + $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json'; if ( file_exists( $cache ) ) { return apply_filters( 'fake_http_response', - unserialize( file_get_contents( $cache ) ), + json_decode( file_get_contents( $cache ), true ), // phpcs:ignore $p['scheme'] . '://' . $p['host'], $url, $request @@ -305,12 +305,12 @@ class Test_Friends_Feed_Parser_ActivityPub extends \WP_UnitTestCase { public static function http_response( $response, $args, $url ) { $p = wp_parse_url( $url ); - $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.response'; + $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json'; if ( ! file_exists( $cache ) ) { $headers = wp_remote_retrieve_headers( $response ); - file_put_contents( + file_put_contents( // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents $cache, - serialize( + wp_json_encode( array( 'headers' => $headers->getAll(), 'body' => wp_remote_retrieve_body( $response ), From be369b11e5f2ad6ccf22605a52077664018b7a56 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 11:59:11 +0100 Subject: [PATCH 67/94] Extract HTTP caching into new test base class --- tests/bootstrap.php | 1 + .../class-activitypub-testcase-cache-http.php | 120 ++++++++++++++++++ ...notiz-blog-2022-11-14-the-at-protocol.json | 24 +++- tests/test-class-activitypub-activity.php | 9 +- ...-class-friends-feed-parser-activitypub.php | 94 +------------- 5 files changed, 151 insertions(+), 97 deletions(-) create mode 100644 tests/class-activitypub-testcase-cache-http.php diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3867ab2..be9e470 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -30,3 +30,4 @@ function _manually_load_plugin() { // Start up the WP testing environment. require $_tests_dir . '/includes/bootstrap.php'; +require __DIR__ . '/class-activitypub-testcase-cache-http.php'; diff --git a/tests/class-activitypub-testcase-cache-http.php b/tests/class-activitypub-testcase-cache-http.php new file mode 100644 index 0000000..e6ede70 --- /dev/null +++ b/tests/class-activitypub-testcase-cache-http.php @@ -0,0 +1,120 @@ +server = $wp_rest_server; + do_action( 'rest_api_init' ); + + add_filter( + 'rest_url', + function() { + return get_option( 'home' ) . '/wp-json/'; + } + ); + + add_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ), 10, 3 ); + add_filter( 'http_response', array( get_called_class(), 'http_response' ), 10, 3 ); + add_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ), 10, 2 ); + add_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ), 10, 2 ); + } + + public function tear_down() { + remove_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ) ); + remove_filter( 'http_response', array( get_called_class(), 'http_response' ) ); + remove_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ) ); + remove_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ) ); + parent::tear_down(); + } + + public static function http_request_host_is_external( $in, $host ) { + if ( in_array( $host, array( 'mastodon.local' ), true ) ) { + return true; + } + return $in; + } + public static function http_request_args( $args, $url ) { + if ( in_array( wp_parse_url( $url, PHP_URL_HOST ), array( 'mastodon.local' ), true ) ) { + $args['reject_unsafe_urls'] = false; + } + return $args; + } + public static function pre_http_request( $preempt, $request, $url ) { + $p = wp_parse_url( $url ); + $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json'; + if ( file_exists( $cache ) ) { + return apply_filters( + 'fake_http_response', + json_decode( file_get_contents( $cache ), true ), // phpcs:ignore + $p['scheme'] . '://' . $p['host'], + $url, + $request + ); + } + + $home_url = home_url(); + + // Pretend the url now is the requested one. + update_option( 'home', $p['scheme'] . '://' . $p['host'] ); + $rest_prefix = home_url() . '/wp-json'; + + if ( false === strpos( $url, $rest_prefix ) ) { + // Restore the old home_url. + update_option( 'home', $home_url ); + return $preempt; + } + + $url = substr( $url, strlen( $rest_prefix ) ); + $r = new \WP_REST_Request( $request['method'], $url ); + if ( ! empty( $request['body'] ) ) { + foreach ( $request['body'] as $key => $value ) { + $r->set_param( $key, $value ); + } + } + global $wp_rest_server; + $response = $wp_rest_server->dispatch( $r ); + // Restore the old url. + update_option( 'home', $home_url ); + + return apply_filters( + 'fake_http_response', + array( + 'headers' => array( + 'content-type' => 'text/json', + ), + 'body' => wp_json_encode( $response->data ), + 'response' => array( + 'code' => $response->status, + ), + ), + $p['scheme'] . '://' . $p['host'], + $url, + $request + ); + } + + public static function http_response( $response, $args, $url ) { + $p = wp_parse_url( $url ); + $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json'; + if ( ! file_exists( $cache ) ) { + $headers = wp_remote_retrieve_headers( $response ); + file_put_contents( // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents + $cache, + wp_json_encode( + array( + 'headers' => $headers->getAll(), + 'body' => wp_remote_retrieve_body( $response ), + 'response' => array( + 'code' => wp_remote_retrieve_response_code( $response ), + ), + ) + ) + ); + } + return $response; + } +} diff --git a/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.json b/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.json index 8b5a942..ad79cc7 100644 --- a/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.json +++ b/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.json @@ -1 +1,23 @@ -{"headers":{"date":"Fri, 09 Dec 2022 08:25:15 GMT","content-type":"application\/activity+json","server":"nginx","x-xrds-location":"https:\/\/notiz.blog\/?xrds","x-yadis-location":"https:\/\/notiz.blog\/?xrds","x-pingback":"https:\/\/notiz.blog\/xmlrpc.php","link":"; rel=\"micropub_media\", ; rel=\"micropub\", ; rel=\"friends-base-url\", ; rel=\"webmention\", ; rel=\"http:\/\/webmention.org\/\", ; rel=\"https:\/\/api.w.org\/\", ; rel=\"alternate\"; type=\"application\/json\", ; rel=shortlink","cache-control":"max-age=0, public","expires":"Fri, 09 Dec 2022 08:25:15 GMT","x-xss-protection":"1; mode=block","x-content-type-options":"nosniff","strict-transport-security":"max-age=31536000","x-frame-options":"SAMEORIGIN","referrer-policy":"strict-origin-when-cross-origin","x-clacks-overhead":"GNU Terry Pratchett"},"body":"{\"@context\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams\",\"https:\\\/\\\/w3id.org\\\/security\\\/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"PropertyValue\":\"schema:PropertyValue\",\"schema\":\"http:\\\/\\\/schema.org#\",\"pt\":\"https:\\\/\\\/joinpeertube.org\\\/ns#\",\"toot\":\"http:\\\/\\\/joinmastodon.org\\\/ns#\",\"value\":\"schema:value\",\"Hashtag\":\"as:Hashtag\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"featuredTags\":{\"@id\":\"toot:featuredTags\",\"@type\":\"@id\"}}],\"id\":\"https:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\",\"type\":\"Note\",\"published\":\"2022-11-14T16:49:01Z\",\"attributedTo\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"summary\":null,\"inReplyTo\":null,\"content\":\"\\u003Cp\\u003EVor zwei Jahren wollte Twitter in das \\u201eDezentrale Netzwerke\\u201c-Business einsteigen und gr\\u00fcndete eigens daf\\u00fcr das \\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2019\\\/12\\\/13\\\/twitiverse\\\/\\u0022 data-type=\\u0022post\\u0022 data-id=\\u002218831\\u0022\\u003EProjekt Bluesky\\u003C\\\/a\\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\\u00f6sung f\\u00fcr Twitter sei und wir alle \\u003Cem\\u003Efieberten\\u003C\\\/em\\u003E mit ob es nun \\u003Ca href=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022\\u003EActivityPub\\u003C\\\/a\\u003E oder doch \\u003Ca href=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022\\u003EMatrix\\u003C\\\/a\\u003E werden w\\u00fcrde\\u2026 \\u003C\\\/p\\u003E\\u003Cp\\u003EAber das Warten hat ein Ende! \\u003Ca href=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022\\u003EBluesky hat verk\\u00fcndet wie es weiter geht\\u003C\\\/a\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003ESie entwickeln ein neues Protokoll!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EDas \\u003Cem\\u003EAT Protocol\\u003C\\\/em\\u003E, kurz f\\u00fcr \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003EIch hab mir die \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022\\u003EFAQ\\u003C\\\/a\\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/data-repos\\u0022\\u003Esigned data repositories\\u003C\\\/a\\u003E and \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/identity\\u0022\\u003EDIDs\\u003C\\\/a\\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\\u2019s previous data.\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas erinnert mich ein bisschen an die Subline von meinem \\u003Ca href=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022\\u003EOpenWeb-Icons Font\\u003C\\\/a\\u003E:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EWhy \\u003Cem\\u003EOpenWeb Icons\\u003C\\\/em\\u003E? Because \\u003Ca href=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022\\u003EFont Awesome\\u003C\\\/a\\u003E had no RSS-icon [\\u2026]\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWeil ActivityPub keine perfekte L\\u00f6sung f\\u00fcr \\u201eAccount portability\\u201c hat, bauen sie ein komplett neues Protokoll?\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EActivityPub ist sicherlich nicht \\u201efeature complete\\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\\u003C\\\/p\\u003E\\u003Cp\\u003EWarum macht sich das W3C \\u00fcberhaupt noch die M\\u00fche \\u201eStandards\\u201c zu definieren?\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWegen der Interoperabilit\\u00e4t!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EW\\u00fcrde Twitter mit HTTP(S), HTML oder CSS \\u00e4hnlich umgehen, w\\u00fcrde der Browser einfach leer bleiben, weil das \\u0026$%\\u00a7\\u0026 Internet nur mit einheitlichen Standards funktioniert!\\u003C\\\/p\\u003E\\u003Cp\\u003EUnd das gleiche gilt auch f\\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\\u00fcber hab ich tragischerweise schon vor \\u003Cstrong\\u003E10 Jahren\\u003C\\\/strong\\u003E geschrieben!\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EDiaspora* wurde kaum \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20130630113539\\\/http:\\\/\\\/blog.diasporafoundation.org\\\/2012\\\/08\\\/27\\\/announcement-diaspora-will-now-be-a-community-project.html\\u0022\\u003Ef\\u00fcr \\u201etot\\u201c erkl\\u00e4rt\\u003C\\\/a\\u003E und schon steht das n\\u00e4chste Projekt in den Startl\\u00f6chern! \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20190603031810\\\/https:\\\/\\\/tent.io\\\/\\u0022\\u003ETent.io\\u003C\\\/a\\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\\u2026 was haben die Diasporas \\u0026 Co. bisher geschaffen? Ziel war es Facebooks \\u201eWalled Gardens\\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \\u201eWalled Gardens\\u201c. Na danke!\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2012\\\/11\\\/15\\\/dezentrale-walled-gardens\\\/\\u0022\\u003EDezentrale \\u201eWalled Gardens\\u201c\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas \\u003Ca href=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022\\u003Efediverse\\u003C\\\/a\\u003E hat (wie schon erw\\u00e4hnt) bisher einen gro\\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\\u00e4gungen vernetzt! Ich \\u003Cs\\u003Eglaube\\u003C\\\/s\\u003E bin der festen \\u00dcberzeugung, dass sich diesmal wirklich das offene Format (\\u003Cem\\u003EActivityPub\\u003C\\\/em\\u003E) durchsetzen wird und Blueskys \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022\\u003EBen Werdmuller\\u003C\\\/a\\u003E hat eine gesunde Einstellung zu dem Thema:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EI\\u2019m so burned out by open source social, but I\\u2019m glad to see people throw energy at the problem, even if it\\u2019s not how I would have gone about it.\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022\\u003ETwitter\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\\u00dfer dass wir in der \\u003Ca href=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022\\u003Eaktuellen Folge\\u003C\\\/a\\u003E des neunetzcasts sehr ausgiebig \\u00fcber genau dieses Problem gesprochen haben!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/activitypub\\\/\\u0022\\u003E#activitypub\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/bluesky\\\/\\u0022\\u003E#bluesky\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/fediverse\\\/\\u0022\\u003E#fediverse\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/matrix\\\/\\u0022\\u003E#matrix\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/twitter\\\/\\u0022\\u003E#twitter\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u0022\\u003Ehttps:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\",\"contentMap\":{\"de\":\"\\u003Cp\\u003EVor zwei Jahren wollte Twitter in das \\u201eDezentrale Netzwerke\\u201c-Business einsteigen und gr\\u00fcndete eigens daf\\u00fcr das \\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2019\\\/12\\\/13\\\/twitiverse\\\/\\u0022 data-type=\\u0022post\\u0022 data-id=\\u002218831\\u0022\\u003EProjekt Bluesky\\u003C\\\/a\\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\\u00f6sung f\\u00fcr Twitter sei und wir alle \\u003Cem\\u003Efieberten\\u003C\\\/em\\u003E mit ob es nun \\u003Ca href=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022\\u003EActivityPub\\u003C\\\/a\\u003E oder doch \\u003Ca href=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022\\u003EMatrix\\u003C\\\/a\\u003E werden w\\u00fcrde\\u2026 \\u003C\\\/p\\u003E\\u003Cp\\u003EAber das Warten hat ein Ende! \\u003Ca href=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022\\u003EBluesky hat verk\\u00fcndet wie es weiter geht\\u003C\\\/a\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003ESie entwickeln ein neues Protokoll!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EDas \\u003Cem\\u003EAT Protocol\\u003C\\\/em\\u003E, kurz f\\u00fcr \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003EIch hab mir die \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022\\u003EFAQ\\u003C\\\/a\\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/data-repos\\u0022\\u003Esigned data repositories\\u003C\\\/a\\u003E and \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/identity\\u0022\\u003EDIDs\\u003C\\\/a\\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\\u2019s previous data.\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas erinnert mich ein bisschen an die Subline von meinem \\u003Ca href=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022\\u003EOpenWeb-Icons Font\\u003C\\\/a\\u003E:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EWhy \\u003Cem\\u003EOpenWeb Icons\\u003C\\\/em\\u003E? Because \\u003Ca href=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022\\u003EFont Awesome\\u003C\\\/a\\u003E had no RSS-icon [\\u2026]\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWeil ActivityPub keine perfekte L\\u00f6sung f\\u00fcr \\u201eAccount portability\\u201c hat, bauen sie ein komplett neues Protokoll?\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EActivityPub ist sicherlich nicht \\u201efeature complete\\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\\u003C\\\/p\\u003E\\u003Cp\\u003EWarum macht sich das W3C \\u00fcberhaupt noch die M\\u00fche \\u201eStandards\\u201c zu definieren?\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWegen der Interoperabilit\\u00e4t!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EW\\u00fcrde Twitter mit HTTP(S), HTML oder CSS \\u00e4hnlich umgehen, w\\u00fcrde der Browser einfach leer bleiben, weil das \\u0026$%\\u00a7\\u0026 Internet nur mit einheitlichen Standards funktioniert!\\u003C\\\/p\\u003E\\u003Cp\\u003EUnd das gleiche gilt auch f\\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\\u00fcber hab ich tragischerweise schon vor \\u003Cstrong\\u003E10 Jahren\\u003C\\\/strong\\u003E geschrieben!\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EDiaspora* wurde kaum \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20130630113539\\\/http:\\\/\\\/blog.diasporafoundation.org\\\/2012\\\/08\\\/27\\\/announcement-diaspora-will-now-be-a-community-project.html\\u0022\\u003Ef\\u00fcr \\u201etot\\u201c erkl\\u00e4rt\\u003C\\\/a\\u003E und schon steht das n\\u00e4chste Projekt in den Startl\\u00f6chern! \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20190603031810\\\/https:\\\/\\\/tent.io\\\/\\u0022\\u003ETent.io\\u003C\\\/a\\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\\u2026 was haben die Diasporas \\u0026 Co. bisher geschaffen? Ziel war es Facebooks \\u201eWalled Gardens\\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \\u201eWalled Gardens\\u201c. Na danke!\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2012\\\/11\\\/15\\\/dezentrale-walled-gardens\\\/\\u0022\\u003EDezentrale \\u201eWalled Gardens\\u201c\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas \\u003Ca href=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022\\u003Efediverse\\u003C\\\/a\\u003E hat (wie schon erw\\u00e4hnt) bisher einen gro\\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\\u00e4gungen vernetzt! Ich \\u003Cs\\u003Eglaube\\u003C\\\/s\\u003E bin der festen \\u00dcberzeugung, dass sich diesmal wirklich das offene Format (\\u003Cem\\u003EActivityPub\\u003C\\\/em\\u003E) durchsetzen wird und Blueskys \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022\\u003EBen Werdmuller\\u003C\\\/a\\u003E hat eine gesunde Einstellung zu dem Thema:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EI\\u2019m so burned out by open source social, but I\\u2019m glad to see people throw energy at the problem, even if it\\u2019s not how I would have gone about it.\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022\\u003ETwitter\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\\u00dfer dass wir in der \\u003Ca href=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022\\u003Eaktuellen Folge\\u003C\\\/a\\u003E des neunetzcasts sehr ausgiebig \\u00fcber genau dieses Problem gesprochen haben!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/activitypub\\\/\\u0022\\u003E#activitypub\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/bluesky\\\/\\u0022\\u003E#bluesky\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/fediverse\\\/\\u0022\\u003E#fediverse\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/matrix\\\/\\u0022\\u003E#matrix\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/twitter\\\/\\u0022\\u003E#twitter\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u0022\\u003Ehttps:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\"},\"to\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams#Public\"],\"cc\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams#Public\"],\"attachment\":[{\"type\":\"Image\",\"url\":\"https:\\\/\\\/notiz.blog\\\/wp-content\\\/uploads\\\/2022\\\/11\\\/the-at-protocol.png\",\"mediaType\":\"image\\\/png\",\"name\":\"The Logo of the AT Protocol\"}],\"tag\":[{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/activitypub\\\/\",\"name\":\"#activitypub\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/bluesky\\\/\",\"name\":\"#bluesky\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/fediverse\\\/\",\"name\":\"#fediverse\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/matrix\\\/\",\"name\":\"#matrix\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/twitter\\\/\",\"name\":\"#twitter\"}]}","response":{"code":200}} \ No newline at end of file +{ + "headers": { + "date": "Fri, 09 Dec 2022 08:25:15 GMT", + "content-type": "application\/activity+json", + "server": "nginx", + "x-xrds-location": "https:\/\/notiz.blog\/?xrds", + "x-yadis-location": "https:\/\/notiz.blog\/?xrds", + "x-pingback": "https:\/\/notiz.blog\/xmlrpc.php", + "link": "; rel=\"micropub_media\", ; rel=\"micropub\", ; rel=\"friends-base-url\", ; rel=\"webmention\", ; rel=\"http:\/\/webmention.org\/\", ; rel=\"https:\/\/api.w.org\/\", ; rel=\"alternate\"; type=\"application\/json\", ; rel=shortlink", + "cache-control": "max-age=0, public", + "expires": "Fri, 09 Dec 2022 08:25:15 GMT", + "x-xss-protection": "1; mode=block", + "x-content-type-options": "nosniff", + "strict-transport-security": "max-age=31536000", + "x-frame-options": "SAMEORIGIN", + "referrer-policy": "strict-origin-when-cross-origin", + "x-clacks-overhead": "GNU Terry Pratchett" + }, + "body": "{\"@context\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams\",\"https:\\\/\\\/w3id.org\\\/security\\\/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"PropertyValue\":\"schema:PropertyValue\",\"schema\":\"http:\\\/\\\/schema.org#\",\"pt\":\"https:\\\/\\\/joinpeertube.org\\\/ns#\",\"toot\":\"http:\\\/\\\/joinmastodon.org\\\/ns#\",\"value\":\"schema:value\",\"Hashtag\":\"as:Hashtag\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"featuredTags\":{\"@id\":\"toot:featuredTags\",\"@type\":\"@id\"}}],\"id\":\"https:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\",\"type\":\"Note\",\"published\":\"2022-11-14T16:49:01Z\",\"attributedTo\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"summary\":null,\"inReplyTo\":null,\"content\":\"\\u003Cp\\u003EVor zwei Jahren wollte Twitter in das \\u201eDezentrale Netzwerke\\u201c-Business einsteigen und gr\\u00fcndete eigens daf\\u00fcr das \\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2019\\\/12\\\/13\\\/twitiverse\\\/\\u0022 data-type=\\u0022post\\u0022 data-id=\\u002218831\\u0022\\u003EProjekt Bluesky\\u003C\\\/a\\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\\u00f6sung f\\u00fcr Twitter sei und wir alle \\u003Cem\\u003Efieberten\\u003C\\\/em\\u003E mit ob es nun \\u003Ca href=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022\\u003EActivityPub\\u003C\\\/a\\u003E oder doch \\u003Ca href=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022\\u003EMatrix\\u003C\\\/a\\u003E werden w\\u00fcrde\\u2026 \\u003C\\\/p\\u003E\\u003Cp\\u003EAber das Warten hat ein Ende! \\u003Ca href=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022\\u003EBluesky hat verk\\u00fcndet wie es weiter geht\\u003C\\\/a\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003ESie entwickeln ein neues Protokoll!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EDas \\u003Cem\\u003EAT Protocol\\u003C\\\/em\\u003E, kurz f\\u00fcr \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003EIch hab mir die \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022\\u003EFAQ\\u003C\\\/a\\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/data-repos\\u0022\\u003Esigned data repositories\\u003C\\\/a\\u003E and \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/identity\\u0022\\u003EDIDs\\u003C\\\/a\\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\\u2019s previous data.\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas erinnert mich ein bisschen an die Subline von meinem \\u003Ca href=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022\\u003EOpenWeb-Icons Font\\u003C\\\/a\\u003E:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EWhy \\u003Cem\\u003EOpenWeb Icons\\u003C\\\/em\\u003E? Because \\u003Ca href=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022\\u003EFont Awesome\\u003C\\\/a\\u003E had no RSS-icon [\\u2026]\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWeil ActivityPub keine perfekte L\\u00f6sung f\\u00fcr \\u201eAccount portability\\u201c hat, bauen sie ein komplett neues Protokoll?\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EActivityPub ist sicherlich nicht \\u201efeature complete\\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\\u003C\\\/p\\u003E\\u003Cp\\u003EWarum macht sich das W3C \\u00fcberhaupt noch die M\\u00fche \\u201eStandards\\u201c zu definieren?\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWegen der Interoperabilit\\u00e4t!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EW\\u00fcrde Twitter mit HTTP(S), HTML oder CSS \\u00e4hnlich umgehen, w\\u00fcrde der Browser einfach leer bleiben, weil das \\u0026$%\\u00a7\\u0026 Internet nur mit einheitlichen Standards funktioniert!\\u003C\\\/p\\u003E\\u003Cp\\u003EUnd das gleiche gilt auch f\\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\\u00fcber hab ich tragischerweise schon vor \\u003Cstrong\\u003E10 Jahren\\u003C\\\/strong\\u003E geschrieben!\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EDiaspora* wurde kaum \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20130630113539\\\/http:\\\/\\\/blog.diasporafoundation.org\\\/2012\\\/08\\\/27\\\/announcement-diaspora-will-now-be-a-community-project.html\\u0022\\u003Ef\\u00fcr \\u201etot\\u201c erkl\\u00e4rt\\u003C\\\/a\\u003E und schon steht das n\\u00e4chste Projekt in den Startl\\u00f6chern! \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20190603031810\\\/https:\\\/\\\/tent.io\\\/\\u0022\\u003ETent.io\\u003C\\\/a\\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\\u2026 was haben die Diasporas \\u0026 Co. bisher geschaffen? Ziel war es Facebooks \\u201eWalled Gardens\\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \\u201eWalled Gardens\\u201c. Na danke!\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2012\\\/11\\\/15\\\/dezentrale-walled-gardens\\\/\\u0022\\u003EDezentrale \\u201eWalled Gardens\\u201c\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas \\u003Ca href=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022\\u003Efediverse\\u003C\\\/a\\u003E hat (wie schon erw\\u00e4hnt) bisher einen gro\\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\\u00e4gungen vernetzt! Ich \\u003Cs\\u003Eglaube\\u003C\\\/s\\u003E bin der festen \\u00dcberzeugung, dass sich diesmal wirklich das offene Format (\\u003Cem\\u003EActivityPub\\u003C\\\/em\\u003E) durchsetzen wird und Blueskys \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022\\u003EBen Werdmuller\\u003C\\\/a\\u003E hat eine gesunde Einstellung zu dem Thema:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EI\\u2019m so burned out by open source social, but I\\u2019m glad to see people throw energy at the problem, even if it\\u2019s not how I would have gone about it.\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022\\u003ETwitter\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\\u00dfer dass wir in der \\u003Ca href=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022\\u003Eaktuellen Folge\\u003C\\\/a\\u003E des neunetzcasts sehr ausgiebig \\u00fcber genau dieses Problem gesprochen haben!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/activitypub\\\/\\u0022\\u003E#activitypub\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/bluesky\\\/\\u0022\\u003E#bluesky\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/fediverse\\\/\\u0022\\u003E#fediverse\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/matrix\\\/\\u0022\\u003E#matrix\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/twitter\\\/\\u0022\\u003E#twitter\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u0022\\u003Ehttps:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\",\"contentMap\":{\"de\":\"\\u003Cp\\u003EVor zwei Jahren wollte Twitter in das \\u201eDezentrale Netzwerke\\u201c-Business einsteigen und gr\\u00fcndete eigens daf\\u00fcr das \\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2019\\\/12\\\/13\\\/twitiverse\\\/\\u0022 data-type=\\u0022post\\u0022 data-id=\\u002218831\\u0022\\u003EProjekt Bluesky\\u003C\\\/a\\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\\u00f6sung f\\u00fcr Twitter sei und wir alle \\u003Cem\\u003Efieberten\\u003C\\\/em\\u003E mit ob es nun \\u003Ca href=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/www.w3.org\\\/TR\\\/activitypub\\\/\\u0022\\u003EActivityPub\\u003C\\\/a\\u003E oder doch \\u003Ca href=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/matrix.org\\\/\\u0022\\u003EMatrix\\u003C\\\/a\\u003E werden w\\u00fcrde\\u2026 \\u003C\\\/p\\u003E\\u003Cp\\u003EAber das Warten hat ein Ende! \\u003Ca href=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/blueskyweb.xyz\\\/blog\\\/10-18-2022-the-at-protocol\\u0022\\u003EBluesky hat verk\\u00fcndet wie es weiter geht\\u003C\\\/a\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003ESie entwickeln ein neues Protokoll!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EDas \\u003Cem\\u003EAT Protocol\\u003C\\\/em\\u003E, kurz f\\u00fcr \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E!\\u003C\\\/p\\u003E\\u003Cp\\u003EIch hab mir die \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/faq\\u0022\\u003EFAQ\\u003C\\\/a\\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/data-repos\\u0022\\u003Esigned data repositories\\u003C\\\/a\\u003E and \\u003Ca href=\\u0022https:\\\/\\\/atproto.com\\\/guides\\\/identity\\u0022\\u003EDIDs\\u003C\\\/a\\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\\u2019s previous data.\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas erinnert mich ein bisschen an die Subline von meinem \\u003Ca href=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/pfefferle.dev\\\/openwebicons\\\/\\u0022\\u003EOpenWeb-Icons Font\\u003C\\\/a\\u003E:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EWhy \\u003Cem\\u003EOpenWeb Icons\\u003C\\\/em\\u003E? Because \\u003Ca href=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/fortawesome.com\\\/\\u0022\\u003EFont Awesome\\u003C\\\/a\\u003E had no RSS-icon [\\u2026]\\u003C\\\/p\\u003E\\n\\u003C\\\/blockquote\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWeil ActivityPub keine perfekte L\\u00f6sung f\\u00fcr \\u201eAccount portability\\u201c hat, bauen sie ein komplett neues Protokoll?\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EActivityPub ist sicherlich nicht \\u201efeature complete\\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\\u003C\\\/p\\u003E\\u003Cp\\u003EWarum macht sich das W3C \\u00fcberhaupt noch die M\\u00fche \\u201eStandards\\u201c zu definieren?\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Cstrong\\u003EWegen der Interoperabilit\\u00e4t!\\u003C\\\/strong\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003EW\\u00fcrde Twitter mit HTTP(S), HTML oder CSS \\u00e4hnlich umgehen, w\\u00fcrde der Browser einfach leer bleiben, weil das \\u0026$%\\u00a7\\u0026 Internet nur mit einheitlichen Standards funktioniert!\\u003C\\\/p\\u003E\\u003Cp\\u003EUnd das gleiche gilt auch f\\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\\u00fcber hab ich tragischerweise schon vor \\u003Cstrong\\u003E10 Jahren\\u003C\\\/strong\\u003E geschrieben!\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EDiaspora* wurde kaum \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20130630113539\\\/http:\\\/\\\/blog.diasporafoundation.org\\\/2012\\\/08\\\/27\\\/announcement-diaspora-will-now-be-a-community-project.html\\u0022\\u003Ef\\u00fcr \\u201etot\\u201c erkl\\u00e4rt\\u003C\\\/a\\u003E und schon steht das n\\u00e4chste Projekt in den Startl\\u00f6chern! \\u003Ca href=\\u0022https:\\\/\\\/web.archive.org\\\/web\\\/20190603031810\\\/https:\\\/\\\/tent.io\\\/\\u0022\\u003ETent.io\\u003C\\\/a\\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\\u2026 was haben die Diasporas \\u0026 Co. bisher geschaffen? Ziel war es Facebooks \\u201eWalled Gardens\\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \\u201eWalled Gardens\\u201c. Na danke!\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2012\\\/11\\\/15\\\/dezentrale-walled-gardens\\\/\\u0022\\u003EDezentrale \\u201eWalled Gardens\\u201c\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EDas \\u003Ca href=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/the-federation.info\\\/\\u0022\\u003Efediverse\\u003C\\\/a\\u003E hat (wie schon erw\\u00e4hnt) bisher einen gro\\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\\u00e4gungen vernetzt! Ich \\u003Cs\\u003Eglaube\\u003C\\\/s\\u003E bin der festen \\u00dcberzeugung, dass sich diesmal wirklich das offene Format (\\u003Cem\\u003EActivityPub\\u003C\\\/em\\u003E) durchsetzen wird und Blueskys \\u003Cem\\u003EAuthenticated Transfer Protocol\\u003C\\\/em\\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\u0022\\u003EBen Werdmuller\\u003C\\\/a\\u003E hat eine gesunde Einstellung zu dem Thema:\\u003C\\\/p\\u003E\\u003Cblockquote class=\\u0022wp-block-quote\\u0022\\u003E\\n\\u003Cp\\u003EI\\u2019m so burned out by open source social, but I\\u2019m glad to see people throw energy at the problem, even if it\\u2019s not how I would have gone about it.\\u003C\\\/p\\u003E\\n\\u003Ccite\\u003E\\u003Ca href=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/twitter.com\\\/benwerd\\\/status\\\/1582554417693270016\\u0022\\u003ETwitter\\u003C\\\/a\\u003E\\u003C\\\/cite\\u003E\\u003C\\\/blockquote\\u003E\\u003Cp\\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\\u00dfer dass wir in der \\u003Ca href=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022 data-type=\\u0022URL\\u0022 data-id=\\u0022https:\\\/\\\/neunetz.fm\\\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\\\/\\u0022\\u003Eaktuellen Folge\\u003C\\\/a\\u003E des neunetzcasts sehr ausgiebig \\u00fcber genau dieses Problem gesprochen haben!\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/activitypub\\\/\\u0022\\u003E#activitypub\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/bluesky\\\/\\u0022\\u003E#bluesky\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/fediverse\\\/\\u0022\\u003E#fediverse\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/matrix\\\/\\u0022\\u003E#matrix\\u003C\\\/a\\u003E \\u003Ca rel=\\u0022tag\\u0022 class=\\u0022u-tag u-category\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/tag\\\/twitter\\\/\\u0022\\u003E#twitter\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\\u003Cp\\u003E\\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u0022\\u003Ehttps:\\\/\\\/notiz.blog\\\/2022\\\/11\\\/14\\\/the-at-protocol\\\/\\u003C\\\/a\\u003E\\u003C\\\/p\\u003E\"},\"to\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams#Public\"],\"cc\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams#Public\"],\"attachment\":[{\"type\":\"Image\",\"url\":\"https:\\\/\\\/notiz.blog\\\/wp-content\\\/uploads\\\/2022\\\/11\\\/the-at-protocol.png\",\"mediaType\":\"image\\\/png\",\"name\":\"The Logo of the AT Protocol\"}],\"tag\":[{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/activitypub\\\/\",\"name\":\"#activitypub\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/bluesky\\\/\",\"name\":\"#bluesky\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/fediverse\\\/\",\"name\":\"#fediverse\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/matrix\\\/\",\"name\":\"#matrix\"},{\"type\":\"Hashtag\",\"href\":\"https:\\\/\\\/notiz.blog\\\/tag\\\/twitter\\\/\",\"name\":\"#twitter\"}]}", + "response": { + "code": 200 + } +} diff --git a/tests/test-class-activitypub-activity.php b/tests/test-class-activitypub-activity.php index eff350d..254d860 100644 --- a/tests/test-class-activitypub-activity.php +++ b/tests/test-class-activitypub-activity.php @@ -10,12 +10,11 @@ class Test_Activitypub_Activity extends WP_UnitTestCase { add_filter( 'activitypub_extract_mentions', - function( $mentions, $post ) { - $mentions[] = 'https://example.com/alex'; + function( $mentions ) { + $mentions['@alex'] = 'https://example.com/alex'; return $mentions; }, - 10, - 2 + 10 ); $activitypub_post = new \Activitypub\Model\Post( $post ); @@ -26,7 +25,7 @@ class Test_Activitypub_Activity extends WP_UnitTestCase { $this->assertContains( \get_rest_url( null, '/activitypub/1.0/users/1/followers' ), $activitypub_activity->get_cc() ); $this->assertContains( 'https://example.com/alex', $activitypub_activity->get_cc() ); - remove_all_filters( 'activitypub_from_post_object' ); + remove_all_filters( 'activitypub_extract_mentions' ); \wp_trash_post( $post ); } } diff --git a/tests/test-class-friends-feed-parser-activitypub.php b/tests/test-class-friends-feed-parser-activitypub.php index d0ae69f..f48a371 100644 --- a/tests/test-class-friends-feed-parser-activitypub.php +++ b/tests/test-class-friends-feed-parser-activitypub.php @@ -1,9 +1,10 @@ $value ) { - $r->set_param( $key, $value ); - } - } - global $wp_rest_server; - $response = $wp_rest_server->dispatch( $r ); - // Restore the old url. - update_option( 'home', $home_url ); - - return apply_filters( - 'fake_http_response', - array( - 'headers' => array( - 'content-type' => 'text/json', - ), - 'body' => wp_json_encode( $response->data ), - 'response' => array( - 'code' => $response->status, - ), - ), - $p['scheme'] . '://' . $p['host'], - $url, - $request - ); - } - - public static function http_response( $response, $args, $url ) { - $p = wp_parse_url( $url ); - $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json'; - if ( ! file_exists( $cache ) ) { - $headers = wp_remote_retrieve_headers( $response ); - file_put_contents( // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents - $cache, - wp_json_encode( - array( - 'headers' => $headers->getAll(), - 'body' => wp_remote_retrieve_body( $response ), - 'response' => array( - 'code' => wp_remote_retrieve_response_code( $response ), - ), - ) - ) - ); - } - return $response; - } } From e065880085400c6047b118b9204cd6792920344e Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 11:59:24 +0100 Subject: [PATCH 68/94] Add ActivityPub mentions --- activitypub.php | 5 ++ includes/class-mention.php | 63 +++++++++++++++++++ includes/functions.php | 6 +- includes/model/class-activity.php | 10 +-- includes/model/class-post.php | 12 ++++ includes/rest/class-webfinger.php | 43 +++++++++++++ .../class-friends-feed-parser-activitypub.php | 24 ++++++- .../notiz-blog-author-matthias-pfefferle.json | 22 +++++++ .../notiz-blog-well-known-webfinger.json | 22 +++++++ tests/test-class-activitypub-mention.php | 34 ++++++++++ ...-class-friends-feed-parser-activitypub.php | 52 ++++++++++----- tests/test-functions.php | 7 +++ 12 files changed, 273 insertions(+), 27 deletions(-) create mode 100644 includes/class-mention.php create mode 100644 tests/fixtures/notiz-blog-author-matthias-pfefferle.json create mode 100644 tests/fixtures/notiz-blog-well-known-webfinger.json create mode 100644 tests/test-class-activitypub-mention.php create mode 100644 tests/test-functions.php diff --git a/activitypub.php b/activitypub.php index 994ef10..991f106 100644 --- a/activitypub.php +++ b/activitypub.php @@ -22,6 +22,8 @@ function init() { \defined( 'ACTIVITYPUB_EXCERPT_LENGTH' ) || \define( 'ACTIVITYPUB_EXCERPT_LENGTH', 400 ); \defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 ); \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=

                        )|(?<=
                        )|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' ); + \defined( 'ACTIVITYPUB_USERNAME_REGEXP' ) || \define( 'ACTIVITYPUB_USERNAME_REGEXP', '(?:[^@]+@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))' ); + \defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '

                          1. ' );
                             	\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "

                            [ap_title]

                            \n\n[ap_content]\n\n

                            [ap_hashtags]

                            \n\n

                            [ap_shortlink]

                            " ); \define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); \define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); @@ -72,6 +74,9 @@ function init() { require_once \dirname( __FILE__ ) . '/includes/class-shortcodes.php'; \Activitypub\Shortcodes::init(); + + require_once \dirname( __FILE__ ) . '/includes/class-mention.php'; + \Activitypub\Mention::init(); require_once \dirname( __FILE__ ) . '/includes/class-debug.php'; \Activitypub\Debug::init(); diff --git a/includes/class-mention.php b/includes/class-mention.php new file mode 100644 index 0000000..f3cc168 --- /dev/null +++ b/includes/class-mention.php @@ -0,0 +1,63 @@ +' . $username . ''; + return \sprintf( '
                            %s', $metadata['url'], $username ); + } + + return $username; + } + + public static function extract_mentions( $mentions, \ActivityPub\Model\Post $post ) { + \preg_match_all( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $post->get_content(), $matches ); + foreach ( $matches[0] as $match ) { + $link = \Activitypub\Rest\Webfinger::resolve( $match ); + if ( ! is_wp_error( $link ) ) { + $mentions[ $match ] = $link; + } + } + return $mentions; + + } +} diff --git a/includes/functions.php b/includes/functions.php index 1abb9d1..db1611e 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -110,8 +110,8 @@ function get_remote_metadata_by_actor( $actor ) { if ( $pre ) { return $pre; } - if ( preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $actor ) ) { - $actor = \Activitypub\Webfinger::resolve( $actor ); + if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $actor ) ) { + $actor = Rest\Webfinger::resolve( $actor ); } if ( ! $actor ) { @@ -135,7 +135,7 @@ function get_remote_metadata_by_actor( $actor ) { $user = \get_users( array( 'number' => 1, - 'who' => 'authors', + 'capability__in' => array( 'publish_posts' ), 'fields' => 'ID', ) ); diff --git a/includes/model/class-activity.php b/includes/model/class-activity.php index 57bf3b1..1bdf660 100644 --- a/includes/model/class-activity.php +++ b/includes/model/class-activity.php @@ -46,8 +46,7 @@ class Activity { } public function from_post( Post $post ) { - $object = apply_filters( 'activitypub_from_post_array', $post->to_array() ); - $this->object = $object; + $this->object = $post->to_array(); if ( isset( $object['published'] ) ) { $this->published = $object['published']; @@ -58,9 +57,10 @@ class Activity { $this->actor = $object['attributedTo']; } - $mentions = apply_filters( 'activitypub_extract_mentions', array(), $post ); - foreach ( $mentions as $mention ) { - $this->cc[] = $mention; + foreach ( $post->get_tags() as $tag ) { + if ( 'Mention' === $tag['type'] ) { + $this->cc[] = $tag['href']; + } } $type = \strtolower( $this->type ); diff --git a/includes/model/class-post.php b/includes/model/class-post.php index c8ee85d..5496500 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -278,6 +278,18 @@ class Post { } } + $mentions = apply_filters( 'activitypub_extract_mentions', array(), $this ); + if ( $mentions ) { + foreach ( $mentions as $mention => $url ) { + $tag = array( + 'type' => 'Mention', + 'href' => $url, + 'name' => $mention, + ); + $tags[] = $tag; + } + } + $this->tags = $tags; return $tags; diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php index 0c6d5f1..e2c5325 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger.php @@ -118,4 +118,47 @@ class Webfinger { return $array; } + + public static function resolve( $account ) { + if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $account, $m ) ) { + return null; + } + + $url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[1] . '/.well-known/webfinger' ); + if ( ! \wp_http_validate_url( $url ) ) { + echo $url; + exit; + return new \WP_Error( 'invalid_webfinger_url', null, $url ); + } + + // try to access author URL + $response = \wp_remote_get( + $url, + array( + 'headers' => array( 'Accept' => 'application/activity+json' ), + 'redirection' => 0, + ) + ); + + if ( \is_wp_error( $response ) ) { + return new \WP_Error( 'webfinger_url_not_accessible', null, $url ); + } + + $response_code = \wp_remote_retrieve_response_code( $response ); + + $body = \wp_remote_retrieve_body( $response ); + $body = \json_decode( $body, true ); + + if ( ! isset( $body['links'] ) ) { + return new \WP_Error( 'webfinger_url_invalid_response', null, $url ); + } + + foreach ( $body['links'] as $link ) { + if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) { + return $link['href']; + } + } + + return new \WP_Error( 'webfinger_url_no_activity_pub', null, $body ); + } } diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index 981c9f4..688b03b 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -29,6 +29,8 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { \add_action( 'friends_feed_parser_activitypub_follow', array( $this, 'follow_user' ), 10, 2 ); \add_action( 'friends_feed_parser_activitypub_unfollow', array( $this, 'unfollow_user' ), 10, 2 ); \add_filter( 'friends_rewrite_incoming_url', array( $this, 'friends_rewrite_incoming_url' ), 10, 2 ); + + \add_filter( 'activitypub_extract_mentions', array( $this, 'activitypub_extract_mentions' ), 10, 2 ); } /** @@ -94,8 +96,8 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { * @return ( description_of_the_return_value ) */ public function friends_rewrite_incoming_url( $url, $incoming_url ) { - if ( preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $incoming_url ) ) { - $resolved_url = \Activitypub\Webfinger::resolve( $incoming_url ); + if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $incoming_url ) ) { + $resolved_url = \Activitypub\Rest\Webfinger::resolve( $incoming_url ); if ( ! is_wp_error( $resolved_url ) ) { return $resolved_url; } @@ -124,6 +126,7 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { 'autoselect' => true, ); } + return $discovered_feeds; } @@ -406,4 +409,21 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { $activity = $activity->to_json(); \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); } + + public function activitypub_extract_mentions( $mentions, \ActivityPub\Model\Post $post ) { + $feeds = \Friends\User_Feed::get_by_parser( 'activitypub' ); + $users = array(); + foreach ( $feeds as $feed ) { + $user = $feed->get_friend_user(); + $slug = sanitize_title( $user->user_nicename ); + $users[ '@' . $slug ] = $feed->get_url(); + } + preg_match_all( '/@(?:[a-zA-Z0-9_-]+)/', $post->get_content(), $matches ); + foreach ( $matches[0] as $match ) { + if ( isset( $users[ $match ] ) ) { + $mentions[ $match ] = $users[ $match ]; + } + } + return $mentions; + } } diff --git a/tests/fixtures/notiz-blog-author-matthias-pfefferle.json b/tests/fixtures/notiz-blog-author-matthias-pfefferle.json new file mode 100644 index 0000000..d93d7fa --- /dev/null +++ b/tests/fixtures/notiz-blog-author-matthias-pfefferle.json @@ -0,0 +1,22 @@ +{ + "headers": { + "date": "Fri, 09 Dec 2022 10:39:51 GMT", + "content-type": "application\/activity+json", + "server": "nginx", + "x-xrds-location": "https:\/\/notiz.blog\/?xrds", + "x-yadis-location": "https:\/\/notiz.blog\/?xrds", + "link": "; rel=\"micropub_media\", ; rel=\"micropub\", ; rel=\"friends-base-url\", ; rel=\"authorization_endpoint\", ; rel=\"token_endpoint\", ; rel=\"indieauth-metadata\", ; rel=\"https:\/\/api.w.org\/\", ; rel=\"alternate\"; type=\"application\/json\"", + "cache-control": "max-age=0, public", + "expires": "Fri, 09 Dec 2022 10:39:51 GMT", + "x-xss-protection": "1; mode=block", + "x-content-type-options": "nosniff", + "strict-transport-security": "max-age=31536000", + "x-frame-options": "SAMEORIGIN", + "referrer-policy": "strict-origin-when-cross-origin", + "x-clacks-overhead": "GNU Terry Pratchett" + }, + "body": "{\"@context\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams\",\"https:\\\/\\\/w3id.org\\\/security\\\/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"PropertyValue\":\"schema:PropertyValue\",\"schema\":\"http:\\\/\\\/schema.org#\",\"pt\":\"https:\\\/\\\/joinpeertube.org\\\/ns#\",\"toot\":\"http:\\\/\\\/joinmastodon.org\\\/ns#\",\"value\":\"schema:value\",\"Hashtag\":\"as:Hashtag\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"featuredTags\":{\"@id\":\"toot:featuredTags\",\"@type\":\"@id\"}}],\"id\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"type\":\"Person\",\"name\":\"Matthias Pfefferle\",\"summary\":\"Ich bin Webworker und arbeite als \\u0022Head of WordPress Development\\u0022 f\\u00fcr IONOS in Karlsruhe. Ich blogge, podcaste und schreibe \\u003Cdel\\u003Eeine Kolumne\\u003C\\\/del\\u003E \\u00fcber das open, independent und federated social Web. \\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/about\\\/\\u0022\\u003EMehr \\u00fcber mich.\\u003C\\\/a\\u003E\",\"preferredUsername\":\"pfefferle\",\"url\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"icon\":{\"type\":\"Image\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/75512bb584bbceae57dfc503692b16b2?s=120\\u0026d=mm\\u0026r=g\"},\"image\":{\"type\":\"Image\",\"url\":\"https:\\\/\\\/notiz.blog\\\/wp-content\\\/uploads\\\/2017\\\/02\\\/cropped-Unknown-2.jpeg\"},\"inbox\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/inbox\",\"outbox\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/outbox\",\"followers\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/followers\",\"following\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/following\",\"manuallyApprovesFollowers\":false,\"publicKey\":{\"id\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/#main-key\",\"owner\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA039CnlArzn6nsRjcC2RJ\\nrjY3K5ZrLnFUbPtHLGNXMJUGW+rFYE1DzhdKPTj9giiXE+J7ADI0Tme5rSWw14bT\\nLhOMBs2ma8d03\\\/wnF1+kxDBeRyvyoki2TjtiJdoPu1jwZLLYTuzWTXdDiqrwSKOL\\nncKFGIkjyzOLoYuIKPgIuFg3Mt8rI6teQ2Q65YsGvOG\\\/mjBOUwl5FjgcGt9aQARd\\nmFxW5XydxfNrCZwuE34Zbq\\\/IC7rvaUx98zvrEHrD237YQ8O4M3afC9Kbu5Xp7k8Q\\n5JG80RItV7n8xjyt0i9LaVwlZDDYmLDYv50VhjcwRvtVFVfaN7yxDnHttd1NNENK\\nCwIDAQAB\\n-----END PUBLIC KEY-----\"},\"tag\":[],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"Blog\",\"value\":\"\\u003Ca rel=\\u0022me\\u0022 title=\\u0022https:\\\/\\\/notiz.blog\\\/\\u0022 target=\\u0022_blank\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/\\u0022\\u003Enotiz.blog\\u003C\\\/a\\u003E\"},{\"type\":\"PropertyValue\",\"name\":\"Profil\",\"value\":\"\\u003Ca rel=\\u0022me\\u0022 title=\\u0022https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\\u0022 target=\\u0022_blank\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\\u0022\\u003Enotiz.blog\\u003C\\\/a\\u003E\"},{\"type\":\"PropertyValue\",\"name\":\"Website\",\"value\":\"\\u003Ca rel=\\u0022me\\u0022 title=\\u0022https:\\\/\\\/pfefferle.org\\\/\\u0022 target=\\u0022_blank\\u0022 href=\\u0022https:\\\/\\\/pfefferle.org\\\/\\u0022\\u003Epfefferle.org\\u003C\\\/a\\u003E\"}]}", + "response": { + "code": 200 + } +} diff --git a/tests/fixtures/notiz-blog-well-known-webfinger.json b/tests/fixtures/notiz-blog-well-known-webfinger.json new file mode 100644 index 0000000..3578ef1 --- /dev/null +++ b/tests/fixtures/notiz-blog-well-known-webfinger.json @@ -0,0 +1,22 @@ +{ + "headers": { + "date": "Fri, 09 Dec 2022 10:39:51 GMT", + "content-type": "application\/jrd+json; charset=UTF-8", + "server": "nginx", + "x-xrds-location": "https:\/\/notiz.blog\/?xrds", + "x-yadis-location": "https:\/\/notiz.blog\/?xrds", + "access-control-allow-origin": "*", + "cache-control": "max-age=2592000, public", + "expires": "Sun, 08 Jan 2023 10:39:50 GMT", + "x-xss-protection": "1; mode=block", + "x-content-type-options": "nosniff", + "strict-transport-security": "max-age=31536000", + "x-frame-options": "SAMEORIGIN", + "referrer-policy": "strict-origin-when-cross-origin", + "x-clacks-overhead": "GNU Terry Pratchett" + }, + "body": "{\"subject\":\"acct:pfefferle@notiz.blog\",\"aliases\":[\"acct:pfefferle@notiz.blog\",\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"mailto:pfefferle@notiz.blog\"],\"links\":[{\"rel\":\"http:\\\/\\\/webfinger.net\\\/rel\\\/profile-page\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"type\":\"text\\\/html\"},{\"rel\":\"http:\\\/\\\/webfinger.net\\\/rel\\\/avatar\",\"href\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/75512bb584bbceae57dfc503692b16b2?s=96&d=mm&r=g\"},{\"rel\":\"http:\\\/\\\/webfinger.net\\\/rel\\\/profile-page\",\"href\":\"https:\\\/\\\/pfefferle.org\\\/\",\"type\":\"text\\\/html\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/www.paypal.me\\\/matthiaspfefferle\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/liberapay.com\\\/pfefferle\\\/\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/notiz.blog\\\/donate\\\/\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/flattr.com\\\/@pfefferle\"},{\"href\":\"https:\\\/\\\/notiz.blog\\\/\",\"rel\":\"http:\\\/\\\/specs.openid.net\\\/auth\\\/2.0\\\/provider\"},{\"rel\":\"self\",\"type\":\"application\\\/activity+json\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\"},{\"rel\":\"micropub_media\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/micropub\\\/1.0\\\/media\"},{\"rel\":\"micropub\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/micropub\\\/1.0\\\/endpoint\"},{\"rel\":\"http:\\\/\\\/nodeinfo.diaspora.software\\\/ns\\\/schema\\\/2.0\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/nodeinfo\\\/2.0\"},{\"rel\":\"http:\\\/\\\/nodeinfo.diaspora.software\\\/ns\\\/schema\\\/1.1\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/nodeinfo\\\/1.1\"},{\"rel\":\"http:\\\/\\\/nodeinfo.diaspora.software\\\/ns\\\/schema\\\/1.0\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/nodeinfo\\\/1.0\"},{\"rel\":\"https:\\\/\\\/feneas.org\\\/ns\\\/serviceinfo\",\"type\":\"application\\\/ld+json\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/serviceinfo\\\/1.0\",\"properties\":{\"https:\\\/\\\/feneas.org\\\/ns\\\/serviceinfo#software.name\":\"notizBlog\"}},{\"rel\":\"http:\\\/\\\/schemas.google.com\\\/g\\\/2010#updates-from\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/ostatus\\\/\",\"type\":\"application\\\/atom+xml\"},{\"rel\":\"http:\\\/\\\/ostatus.org\\\/schema\\\/1.0\\\/subscribe\",\"template\":\"https:\\\/\\\/notiz.blog\\\/?profile={uri}\"},{\"rel\":\"magic-public-key\",\"href\":\"data:application\\\/magic-public-key,RSA.039CnlArzn6nsRjcC2RJrjY3K5ZrLnFUbPtHLGNXMJUGW-rFYE1DzhdKPTj9giiXE-J7ADI0Tme5rSWw14bTLhOMBs2ma8d03_wnF1-kxDBeRyvyoki2TjtiJdoPu1jwZLLYTuzWTXdDiqrwSKOLncKFGIkjyzOLoYuIKPgIuFg3Mt8rI6teQ2Q65YsGvOG_mjBOUwl5FjgcGt9aQARdmFxW5XydxfNrCZwuE34Zbq_IC7rvaUx98zvrEHrD237YQ8O4M3afC9Kbu5Xp7k8Q5JG80RItV7n8xjyt0i9LaVwlZDDYmLDYv50VhjcwRvtVFVfaN7yxDnHttd1NNENKCw==.AQAB\"},{\"rel\":\"salmon\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/?salmon=endpoint\"},{\"rel\":\"http:\\\/\\\/salmon-protocol.org\\\/ns\\\/salmon-replies\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/?salmon=endpoint\"},{\"rel\":\"http:\\\/\\\/salmon-protocol.org\\\/ns\\\/salmon-mention\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/?salmon=endpoint\"},{\"rel\":\"feed\",\"type\":\"application\\\/stream+json\",\"title\":\"Activity-Streams 1.0 Feed\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/as1\\\/\"},{\"rel\":\"feed\",\"type\":\"application\\\/activity+json\",\"title\":\"Activity-Streams 2.0 Feed\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/as2\\\/\"},{\"rel\":\"http:\\\/\\\/oexchange.org\\\/spec\\\/0.8\\\/rel\\\/user-target\",\"href\":\"https:\\\/\\\/notiz.blog\\\/?oexchange=xrd\",\"type\":\"application\\\/xrd+xml\"},{\"rel\":\"http:\\\/\\\/a9.com\\\/-\\\/spec\\\/opensearch\\\/1.1\\\/\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/opensearch\\\/1.1\\\/document\",\"type\":\"application\\\/opensearchdescription+xml\"},{\"rel\":\"describedby\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/foaf\\\/\",\"type\":\"application\\\/rdf+xml\"},{\"rel\":\"webmention\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/webmention\\\/1.0\\\/endpoint\"},{\"rel\":\"http:\\\/\\\/webmention.org\\\/\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/webmention\\\/1.0\\\/endpoint\"}],\"properties\":{\"http:\\\/\\\/salmon-protocol.org\\\/ns\\\/magic-key\":\"RSA.039CnlArzn6nsRjcC2RJrjY3K5ZrLnFUbPtHLGNXMJUGW-rFYE1DzhdKPTj9giiXE-J7ADI0Tme5rSWw14bTLhOMBs2ma8d03_wnF1-kxDBeRyvyoki2TjtiJdoPu1jwZLLYTuzWTXdDiqrwSKOLncKFGIkjyzOLoYuIKPgIuFg3Mt8rI6teQ2Q65YsGvOG_mjBOUwl5FjgcGt9aQARdmFxW5XydxfNrCZwuE34Zbq_IC7rvaUx98zvrEHrD237YQ8O4M3afC9Kbu5Xp7k8Q5JG80RItV7n8xjyt0i9LaVwlZDDYmLDYv50VhjcwRvtVFVfaN7yxDnHttd1NNENKCw==.AQAB\"}}", + "response": { + "code": 200 + } +} diff --git a/tests/test-class-activitypub-mention.php b/tests/test-class-activitypub-mention.php new file mode 100644 index 0000000..b3f8864 --- /dev/null +++ b/tests/test-class-activitypub-mention.php @@ -0,0 +1,34 @@ + array( + 'url' => 'https://example.org/users/username', + 'name' => 'username', + ), + ); + /** + * @dataProvider the_content_provider + */ + public function test_the_content( $content, $content_with_mention ) { + add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 ); + $content = \Activitypub\Mention::the_content( $content ); + remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) ); + + $this->assertEquals( $content_with_mention, $content ); + } + + public function the_content_provider() { + return array( + array( 'hallo @username@example.org test', 'hallo @username test' ), + array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle test' ), + ); + } + + public static function pre_get_remote_metadata_by_actor( $pre, $actor ) { + $actor = ltrim( $actor, '@' ); + if ( isset( self::$users[ $actor ] ) ) { + return self::$users[ $actor ]; + } + return $pre; + } +} diff --git a/tests/test-class-friends-feed-parser-activitypub.php b/tests/test-class-friends-feed-parser-activitypub.php index f48a371..889d43b 100644 --- a/tests/test-class-friends-feed-parser-activitypub.php +++ b/tests/test-class-friends-feed-parser-activitypub.php @@ -161,29 +161,43 @@ class Test_Friends_Feed_Parser_ActivityPub extends ActivityPub_TestCase_Cache_HT $this->assertEquals( 'Matthias Pfefferle', get_post_meta( $posts[0]->ID, 'author', true ) ); } + + public function test_friend_mentions() { + $post = \wp_insert_post( + array( + 'post_author' => 1, + 'post_content' => '@' . sanitize_title( $this->friend_nicename ) . ' hello', + ) + ); + + $activitypub_post = new \Activitypub\Model\Post( $post ); + + $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL ); + $activitypub_activity->from_post( $activitypub_post ); + + $this->assertContains( + array( + 'type' => 'Mention', + 'href' => $this->actor, + 'name' => '@' . $this->friend_nicename, + ), + $activitypub_post->get_tags() + ); + + $this->assertContains( \get_rest_url( null, '/activitypub/1.0/users/1/followers' ), $activitypub_activity->get_cc() ); + $this->assertContains( $this->actor, $activitypub_activity->get_cc() ); + + remove_all_filters( 'activitypub_from_post_object' ); + \wp_trash_post( $post ); + + } + public function set_up() { if ( ! class_exists( '\Friends\Friends' ) ) { return $this->markTestSkipped( 'The Friends plugin is not loaded.' ); } parent::set_up(); - // Manually activate the REST server. - global $wp_rest_server; - $wp_rest_server = new \Spy_REST_Server(); - $this->server = $wp_rest_server; - do_action( 'rest_api_init' ); - - add_filter( - 'rest_url', - function() { - return get_option( 'home' ) . '/wp-json/'; - } - ); - - add_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ), 10, 3 ); - add_filter( 'http_response', array( get_called_class(), 'http_response' ), 10, 3 ); - add_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ), 10, 2 ); - add_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ), 10, 2 ); add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 ); $user_id = $this->factory->user->create( @@ -200,7 +214,9 @@ class Test_Friends_Feed_Parser_ActivityPub extends ActivityPub_TestCase_Cache_HT if ( is_wp_error( $user_feed ) ) { $this->friend_id = $this->factory->user->create( array( + 'user_login' => 'akirk.blog', 'display_name' => $this->friend_name, + 'nicename' => $this->friend_nicename, 'role' => 'friend', ) ); @@ -214,6 +230,8 @@ class Test_Friends_Feed_Parser_ActivityPub extends ActivityPub_TestCase_Cache_HT } else { $this->friend_id = $user_feed->get_friend_user()->ID; } + $userdata = get_userdata( $this->friend_id ); + $this->friend_nicename = $userdata->user_nicename; self::$users[ $this->actor ] = array( 'url' => $this->actor, diff --git a/tests/test-functions.php b/tests/test-functions.php new file mode 100644 index 0000000..b62669c --- /dev/null +++ b/tests/test-functions.php @@ -0,0 +1,7 @@ +assertArrayHasKey( 'url', $metadata ); + } +} From 4d05d3710bebacbeafe6a882573dd6154117ae00 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 12:02:55 +0100 Subject: [PATCH 69/94] Ensure more metadata --- includes/class-mention.php | 2 +- includes/rest/class-webfinger.php | 2 -- tests/test-functions.php | 4 +++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/class-mention.php b/includes/class-mention.php index f3cc168..022b033 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -46,7 +46,7 @@ class Mention { return \sprintf( '%s', $metadata['url'], $username ); } - return $username; + return $result[0]; } public static function extract_mentions( $mentions, \ActivityPub\Model\Post $post ) { diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php index e2c5325..9115bcb 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger.php @@ -126,8 +126,6 @@ class Webfinger { $url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[1] . '/.well-known/webfinger' ); if ( ! \wp_http_validate_url( $url ) ) { - echo $url; - exit; return new \WP_Error( 'invalid_webfinger_url', null, $url ); } diff --git a/tests/test-functions.php b/tests/test-functions.php index b62669c..68140e0 100644 --- a/tests/test-functions.php +++ b/tests/test-functions.php @@ -2,6 +2,8 @@ class Test_Functions extends ActivityPub_TestCase_Cache_HTTP { public function test_get_remote_metadata_by_actor() { $metadata = \ActivityPub\get_remote_metadata_by_actor( 'pfefferle@notiz.blog' ); - $this->assertArrayHasKey( 'url', $metadata ); + $this->assertEquals( 'https://notiz.blog/author/matthias-pfefferle/', $metadata['url'] ); + $this->assertEquals( 'pfefferle', $metadata['preferredUsername'] ); + $this->assertEquals( 'Matthias Pfefferle', $metadata['name'] ); } } From 05575fe6e7b374271546c42b7ec8ef0ddd143b44 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 13:16:34 +0100 Subject: [PATCH 70/94] Add test for a normal dispatch activity --- includes/class-activity-dispatcher.php | 42 +++++++------- ...-class-activitypub-activity-dispatcher.php | 58 +++++++++++++++---- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index a130ba3..4385ebe 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -30,14 +30,8 @@ class Activity_Dispatcher { $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL ); $activitypub_activity->from_post( $activitypub_post ); - $sent = array(); - foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { - $sent[ $to ] = true; - $activitypub_activity->set_to( $to ); - $activity = $activitypub_activity->to_json(); // phpcs:ignore + $inboxes = \Activitypub\get_follower_inboxes( $user_id ); - \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); - } $followers_url = \get_rest_url( null, '/activitypub/1.0/users/' . intval( $user_id ) . '/followers' ); foreach ( $activitypub_activity->get_cc() as $cc ) { if ( $cc === $followers_url ) { @@ -47,21 +41,27 @@ class Activity_Dispatcher { if ( ! $inbox || \is_wp_error( $inbox ) ) { continue; } - if ( isset( $sent[ $cc ] ) ) { - continue; + // init array if empty + if ( ! isset( $inboxes[ $inbox ] ) ) { + $inboxes[ $inbox ] = array(); } - $sent[ $cc ] = true; - $activity = $activitypub_activity->to_json(); // phpcs:ignore + $inboxes[ $inbox ][] = $cc; + } + + foreach ( $inboxes as $inbox => $to ) { + $to = array_unique( $to ); + $activitypub_activity->set_to( $to ); + $activity = $activitypub_activity->to_json(); \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); } } - /** - * Send "update" activities. - * - * @param \Activitypub\Model\Post $activitypub_post - */ + /** + * Send "update" activities. + * + * @param \Activitypub\Model\Post $activitypub_post + */ public static function send_update_activity( $activitypub_post ) { // get latest version of post $user_id = $activitypub_post->get_post_author(); @@ -77,11 +77,11 @@ class Activity_Dispatcher { } } - /** - * Send "delete" activities. - * - * @param \Activitypub\Model\Post $activitypub_post - */ + /** + * Send "delete" activities. + * + * @param \Activitypub\Model\Post $activitypub_post + */ public static function send_delete_activity( $activitypub_post ) { // get latest version of post $user_id = $activitypub_post->get_post_author(); diff --git a/tests/test-class-activitypub-activity-dispatcher.php b/tests/test-class-activitypub-activity-dispatcher.php index 228ee6d..d34a24e 100644 --- a/tests/test-class-activitypub-activity-dispatcher.php +++ b/tests/test-class-activitypub-activity-dispatcher.php @@ -1,6 +1,44 @@ array( + 'url' => 'https://example.org/users/username', + 'inbox' => 'https://example.org/users/username/inbox', + 'name' => 'username', + ), + 'jon@example.com' => array( + 'url' => 'https://example.com/author/jon', + 'inbox' => 'https://example.com/author/jon/inbox', + 'name' => 'jon', + ), + ); + + public function test_dispatch_activity() { + $followers = array( 'https://example.com/author/jon', 'https://example.org/users/username' ); + \update_user_meta( 1, 'activitypub_followers', $followers ); + + $post = \wp_insert_post( + array( + 'post_author' => 1, + 'post_content' => 'hello', + ) + ); + + $pre_http_request = new MockAction(); + add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 ); + + $activitypub_post = new \Activitypub\Model\Post( $post ); + \Activitypub\Activity_Dispatcher::send_post_activity( $activitypub_post ); + + $this->assertSame( 2, $pre_http_request->get_call_count() ); + $all_args = $pre_http_request->get_args(); + $first_call_args = array_shift( $all_args ); + $this->assertEquals( 'https://example.com/author/jon/inbox', $first_call_args[2] ); + $second_call_args = array_shift( $all_args ); + $this->assertEquals( 'https://example.org/users/username/inbox', $second_call_args[2] ); + + remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 ); + } public function test_dispatch_mentions() { $post = \wp_insert_post( array( @@ -41,21 +79,11 @@ class Test_Activitypub_Activity_Dispatcher extends WP_UnitTestCase { public function set_up() { parent::set_up(); - - add_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ), 10, 3 ); - add_filter( 'http_response', array( get_called_class(), 'http_response' ), 10, 3 ); - add_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ), 10, 2 ); - add_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ), 10, 2 ); add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 ); - _delete_all_posts(); } public function tear_down() { - remove_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ) ); - remove_filter( 'http_response', array( get_called_class(), 'http_response' ) ); - remove_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ) ); - remove_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ) ); remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) ); parent::tear_down(); } @@ -64,6 +92,11 @@ class Test_Activitypub_Activity_Dispatcher extends WP_UnitTestCase { if ( isset( self::$users[ $actor ] ) ) { return self::$users[ $actor ]; } + foreach ( self::$users as $username => $data ) { + if ( $data['url'] === $actor ) { + return $data; + } + } return $pre; } @@ -79,6 +112,7 @@ class Test_Activitypub_Activity_Dispatcher extends WP_UnitTestCase { } return $args; } + public static function pre_http_request( $preempt, $request, $url ) { return array( 'headers' => array( From 99b316db346d476c485f77b1bfee5adb682f648b Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 13:39:48 +0100 Subject: [PATCH 71/94] Rework inboxes for cc --- includes/class-activity-dispatcher.php | 2 +- includes/class-mention.php | 9 ++--- includes/functions.php | 9 +++-- includes/rest/class-webfinger.php | 19 +++++++-- .../class-friends-feed-parser-activitypub.php | 39 +++++++++++++++---- tests/test-class-activitypub-mention.php | 4 +- 6 files changed, 61 insertions(+), 21 deletions(-) diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index 4385ebe..a4348f5 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -49,7 +49,7 @@ class Activity_Dispatcher { } foreach ( $inboxes as $inbox => $to ) { - $to = array_unique( $to ); + $to = array_values( array_unique( $to ) ); $activitypub_activity->set_to( $to ); $activity = $activitypub_activity->to_json(); diff --git a/includes/class-mention.php b/includes/class-mention.php index 022b033..5bf88c1 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -16,34 +16,33 @@ class Mention { } /** - * Filter to replace the #tags in the content with links + * Filter to replace the mentions in the content with links * * @param string $the_content the post-content * * @return string the filtered post-content */ public static function the_content( $the_content ) { - $the_content = \preg_replace_callback( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', array( '\Activitypub\Mention', 'replace_with_links' ), $the_content ); + $the_content = \preg_replace_callback( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/', array( '\Activitypub\Mention', 'replace_with_links' ), $the_content ); return $the_content; } /** - * A callback for preg_replace to build the term links + * A callback for preg_replace to build the user links * * @param array $result the preg_match results * @return string the final string */ public static function replace_with_links( $result ) { $metadata = \ActivityPub\get_remote_metadata_by_actor( $result[0] ); - if ( ! is_wp_error( $metadata ) ) { $username = $metadata['name']; if ( ! empty( $metadata['preferredUsername'] ) ) { $username = $metadata['preferredUsername']; } $username = '@' . $username . ''; - return \sprintf( '%s', $metadata['url'], $username ); + return \sprintf( '%s', $metadata['url'], $username ); } return $result[0]; diff --git a/includes/functions.php b/includes/functions.php index db1611e..aba5e07 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -122,7 +122,8 @@ function get_remote_metadata_by_actor( $actor ) { return $actor; } - $metadata = \get_transient( 'activitypub_' . $actor ); + $transient_key = 'activitypub_' . $actor; + $metadata = \get_transient( $transient_key ); if ( $metadata ) { return $metadata; @@ -153,10 +154,12 @@ function get_remote_metadata_by_actor( $actor ) { $metadata = \json_decode( $metadata, true ); if ( ! $metadata ) { - return new \WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), $actor ); + $metadata = new \WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), $actor ); + \set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period. + return $metadata; } - \set_transient( 'activitypub_' . $actor, $metadata, WEEK_IN_SECONDS ); + \set_transient( $transient_key, $metadata, WEEK_IN_SECONDS ); return $metadata; } diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php index 9115bcb..5ec4385 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger.php @@ -123,6 +123,12 @@ class Webfinger { if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $account, $m ) ) { return null; } + $transient_key = 'activitypub_resolve_' . ltrim( $account, '@' ); + + $link = \get_transient( $transient_key ); + if ( $link ) { + return $link; + } $url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[1] . '/.well-known/webfinger' ); if ( ! \wp_http_validate_url( $url ) ) { @@ -139,7 +145,9 @@ class Webfinger { ); if ( \is_wp_error( $response ) ) { - return new \WP_Error( 'webfinger_url_not_accessible', null, $url ); + $link = new \WP_Error( 'webfinger_url_not_accessible', null, $url ); + \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period. + return $link; } $response_code = \wp_remote_retrieve_response_code( $response ); @@ -148,15 +156,20 @@ class Webfinger { $body = \json_decode( $body, true ); if ( ! isset( $body['links'] ) ) { - return new \WP_Error( 'webfinger_url_invalid_response', null, $url ); + $link = new \WP_Error( 'webfinger_url_invalid_response', null, $url ); + \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period. + return $link; } foreach ( $body['links'] as $link ) { if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) { + \set_transient( $transient_key, $link['href'], WEEK_IN_SECONDS ); return $link['href']; } } - return new \WP_Error( 'webfinger_url_no_activity_pub', null, $body ); + $link = new \WP_Error( 'webfinger_url_no_activity_pub', null, $body ); + \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period. + return $link; } } diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index 688b03b..00e17b6 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -30,6 +30,7 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { \add_action( 'friends_feed_parser_activitypub_unfollow', array( $this, 'unfollow_user' ), 10, 2 ); \add_filter( 'friends_rewrite_incoming_url', array( $this, 'friends_rewrite_incoming_url' ), 10, 2 ); + \add_filter( 'the_content', array( $this, 'the_content' ), 99, 2 ); \add_filter( 'activitypub_extract_mentions', array( $this, 'activitypub_extract_mentions' ), 10, 2 ); } @@ -410,14 +411,22 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); } - public function activitypub_extract_mentions( $mentions, \ActivityPub\Model\Post $post ) { - $feeds = \Friends\User_Feed::get_by_parser( 'activitypub' ); - $users = array(); - foreach ( $feeds as $feed ) { - $user = $feed->get_friend_user(); - $slug = sanitize_title( $user->user_nicename ); - $users[ '@' . $slug ] = $feed->get_url(); + public function get_possible_mentions() { + static $users = null; + if ( is_null( $users ) ) { + $feeds = \Friends\User_Feed::get_by_parser( 'activitypub' ); + $users = array(); + foreach ( $feeds as $feed ) { + $user = $feed->get_friend_user(); + $slug = sanitize_title( $user->user_nicename ); + $users[ '@' . $slug ] = $feed->get_url(); + } } + return $users; + } + + public function activitypub_extract_mentions( $mentions, \ActivityPub\Model\Post $post ) { + $users = $this->get_possible_mentions(); preg_match_all( '/@(?:[a-zA-Z0-9_-]+)/', $post->get_content(), $matches ); foreach ( $matches[0] as $match ) { if ( isset( $users[ $match ] ) ) { @@ -426,4 +435,20 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { } return $mentions; } + + + public function the_content( $the_content ) { + $the_content = \preg_replace_callback( '/@(?:[a-zA-Z0-9_-]+)/', array( $this, 'replace_with_links' ), $the_content ); + + return $the_content; + } + + public function replace_with_links( $result ) { + $users = $this->get_possible_mentions(); + if ( isset( $users[ $result[0] ] ) ) { + return \Activitypub\Mention::replace_with_links( array( $users[ $result[0] ] ) ); + } + + return $result[0]; + } } diff --git a/tests/test-class-activitypub-mention.php b/tests/test-class-activitypub-mention.php index b3f8864..274f432 100644 --- a/tests/test-class-activitypub-mention.php +++ b/tests/test-class-activitypub-mention.php @@ -19,8 +19,8 @@ class Test_Activitypub_Mention extends ActivityPub_TestCase_Cache_HTTP { public function the_content_provider() { return array( - array( 'hallo @username@example.org test', 'hallo @username test' ), - array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle test' ), + array( 'hallo @username@example.org test', 'hallo @username test' ), + array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle test' ), ); } From 483e0a85b2499bf4a00f13aa99920e09175dcc97 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 18:41:26 +0100 Subject: [PATCH 72/94] Extract mentions from the unmodified post content. --- includes/class-mention.php | 11 ++++++-- includes/model/class-post.php | 26 ++++++++++++++++++- .../class-friends-feed-parser-activitypub.php | 13 +++++++--- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/includes/class-mention.php b/includes/class-mention.php index 5bf88c1..c5752bb 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -48,8 +48,15 @@ class Mention { return $result[0]; } - public static function extract_mentions( $mentions, \ActivityPub\Model\Post $post ) { - \preg_match_all( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $post->get_content(), $matches ); + /** + * Extract the mentions from the post_content. + * + * @param array $mentions The already found mentions. + * @param string $post_content The post content. + * @return mixed The discovered mentions. + */ + public static function extract_mentions( $mentions, $post_content ) { + \preg_match_all( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $post_content, $matches ); foreach ( $matches[0] as $match ) { $link = \Activitypub\Rest\Webfinger::resolve( $match ); if ( ! is_wp_error( $link ) ) { diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 5496500..543a890 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -278,7 +278,7 @@ class Post { } } - $mentions = apply_filters( 'activitypub_extract_mentions', array(), $this ); + $mentions = apply_filters( 'activitypub_extract_mentions', array(), $this->post->post_content, $this ); if ( $mentions ) { foreach ( $mentions as $mention => $url ) { $tag = array( @@ -456,4 +456,28 @@ class Post { return $content; } + /** + * Adds all tags as hashtags to the post/summary content + * + * @param string $content + * @param WP_Post $post + * + * @return string + */ + public function get_the_mentions() { + $post = $this->post; + $tags = \get_the_tags( $post->ID ); + + if ( ! $tags ) { + return ''; + } + + $hash_tags = array(); + + foreach ( $tags as $tag ) { + $hash_tags[] = \sprintf( '', \get_tag_link( $tag ), $tag->slug ); + } + + return \implode( ' ', $hash_tags ); + } } diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index 00e17b6..c4f59f2 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -413,7 +413,7 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { public function get_possible_mentions() { static $users = null; - if ( is_null( $users ) ) { + if ( is_null( $users ) || true ) { $feeds = \Friends\User_Feed::get_by_parser( 'activitypub' ); $users = array(); foreach ( $feeds as $feed ) { @@ -425,9 +425,16 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { return $users; } - public function activitypub_extract_mentions( $mentions, \ActivityPub\Model\Post $post ) { + /** + * Extract the mentions from the post_content. + * + * @param array $mentions The already found mentions. + * @param string $post_content The post content. + * @return mixed The discovered mentions. + */ + public function activitypub_extract_mentions( $mentions, $post_content ) { $users = $this->get_possible_mentions(); - preg_match_all( '/@(?:[a-zA-Z0-9_-]+)/', $post->get_content(), $matches ); + preg_match_all( '/@(?:[a-zA-Z0-9_-]+)/', $post_content, $matches ); foreach ( $matches[0] as $match ) { if ( isset( $users[ $match ] ) ) { $mentions[ $match ] = $users[ $match ]; From 0925405430d3b2cc44c3ca80b3d402e534dadc1d Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 18:44:46 +0100 Subject: [PATCH 73/94] Fix missing id --- includes/model/class-activity.php | 8 ++++---- tests/test-class-activitypub-activity-dispatcher.php | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/includes/model/class-activity.php b/includes/model/class-activity.php index 1bdf660..f865d6d 100644 --- a/includes/model/class-activity.php +++ b/includes/model/class-activity.php @@ -53,8 +53,8 @@ class Activity { } $this->cc = array( \get_rest_url( null, '/activitypub/1.0/users/' . intval( $post->get_post_author() ) . '/followers' ) ); - if ( isset( $object['attributedTo'] ) ) { - $this->actor = $object['attributedTo']; + if ( isset( $this->object['attributedTo'] ) ) { + $this->actor = $this->object['attributedTo']; } foreach ( $post->get_tags() as $tag ) { @@ -65,8 +65,8 @@ class Activity { $type = \strtolower( $this->type ); - if ( isset( $object['id'] ) ) { - $this->id = add_query_arg( 'activity', $type, $object['id'] ); + if ( isset( $this->object['id'] ) ) { + $this->id = add_query_arg( 'activity', $type, $this->object['id'] ); } } diff --git a/tests/test-class-activitypub-activity-dispatcher.php b/tests/test-class-activitypub-activity-dispatcher.php index d34a24e..6693ba1 100644 --- a/tests/test-class-activitypub-activity-dispatcher.php +++ b/tests/test-class-activitypub-activity-dispatcher.php @@ -34,6 +34,7 @@ class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HT $all_args = $pre_http_request->get_args(); $first_call_args = array_shift( $all_args ); $this->assertEquals( 'https://example.com/author/jon/inbox', $first_call_args[2] ); + $second_call_args = array_shift( $all_args ); $this->assertEquals( 'https://example.org/users/username/inbox', $second_call_args[2] ); @@ -73,6 +74,9 @@ class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HT $first_call_args = $all_args[0]; $this->assertEquals( 'https://example.com/alex/inbox', $first_call_args[2] ); + $body = json_decode( $first_call_args[1]['body'], true ); + $this->assertArrayHasKey( 'id', $body ); + remove_all_filters( 'activitypub_from_post_object' ); remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 ); } From 7d598d92a8428de63d7d071a7705adb75fec5b12 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Fri, 9 Dec 2022 19:05:43 +0100 Subject: [PATCH 74/94] Revert erroneous changes --- includes/class-activity-dispatcher.php | 20 +++---- includes/class-mention.php | 2 +- includes/class-webfinger.php | 22 ++++++-- includes/functions.php | 2 +- includes/rest/class-webfinger.php | 54 ------------------- .../class-friends-feed-parser-activitypub.php | 2 +- 6 files changed, 31 insertions(+), 71 deletions(-) diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index a4348f5..cf88231 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -57,11 +57,11 @@ class Activity_Dispatcher { } } - /** - * Send "update" activities. - * - * @param \Activitypub\Model\Post $activitypub_post - */ + /** + * Send "update" activities. + * + * @param \Activitypub\Model\Post $activitypub_post + */ public static function send_update_activity( $activitypub_post ) { // get latest version of post $user_id = $activitypub_post->get_post_author(); @@ -77,11 +77,11 @@ class Activity_Dispatcher { } } - /** - * Send "delete" activities. - * - * @param \Activitypub\Model\Post $activitypub_post - */ + /** + * Send "delete" activities. + * + * @param \Activitypub\Model\Post $activitypub_post + */ public static function send_delete_activity( $activitypub_post ) { // get latest version of post $user_id = $activitypub_post->get_post_author(); diff --git a/includes/class-mention.php b/includes/class-mention.php index c5752bb..09c7046 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -58,7 +58,7 @@ class Mention { public static function extract_mentions( $mentions, $post_content ) { \preg_match_all( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $post_content, $matches ); foreach ( $matches[0] as $match ) { - $link = \Activitypub\Rest\Webfinger::resolve( $match ); + $link = \Activitypub\Webfinger::resolve( $match ); if ( ! is_wp_error( $link ) ) { $mentions[ $match ] = $link; } diff --git a/includes/class-webfinger.php b/includes/class-webfinger.php index 8906b60..97b05b4 100644 --- a/includes/class-webfinger.php +++ b/includes/class-webfinger.php @@ -28,9 +28,16 @@ class Webfinger { } public static function resolve( $account ) { - if ( ! preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $account, $m ) ) { + if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $account, $m ) ) { return null; } + $transient_key = 'activitypub_resolve_' . ltrim( $account, '@' ); + + $link = \get_transient( $transient_key ); + if ( $link ) { + return $link; + } + $url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[1] . '/.well-known/webfinger' ); if ( ! \wp_http_validate_url( $url ) ) { return new \WP_Error( 'invalid_webfinger_url', null, $url ); @@ -46,7 +53,9 @@ class Webfinger { ); if ( \is_wp_error( $response ) ) { - return new \WP_Error( 'webfinger_url_not_accessible', null, $url ); + $link = new \WP_Error( 'webfinger_url_not_accessible', null, $url ); + \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period. + return $link; } $response_code = \wp_remote_retrieve_response_code( $response ); @@ -55,15 +64,20 @@ class Webfinger { $body = \json_decode( $body, true ); if ( ! isset( $body['links'] ) ) { - return new \WP_Error( 'webfinger_url_invalid_response', null, $url ); + $link = new \WP_Error( 'webfinger_url_invalid_response', null, $url ); + \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period. + return $link; } foreach ( $body['links'] as $link ) { if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) { + \set_transient( $transient_key, $link['href'], WEEK_IN_SECONDS ); return $link['href']; } } - return new \WP_Error( 'webfinger_url_no_activity_pub', null, $body ); + $link = new \WP_Error( 'webfinger_url_no_activity_pub', null, $body ); + \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period. + return $link; } } diff --git a/includes/functions.php b/includes/functions.php index aba5e07..3fb79a4 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -111,7 +111,7 @@ function get_remote_metadata_by_actor( $actor ) { return $pre; } if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $actor ) ) { - $actor = Rest\Webfinger::resolve( $actor ); + $actor = Webfinger::resolve( $actor ); } if ( ! $actor ) { diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php index 5ec4385..0c6d5f1 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger.php @@ -118,58 +118,4 @@ class Webfinger { return $array; } - - public static function resolve( $account ) { - if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $account, $m ) ) { - return null; - } - $transient_key = 'activitypub_resolve_' . ltrim( $account, '@' ); - - $link = \get_transient( $transient_key ); - if ( $link ) { - return $link; - } - - $url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[1] . '/.well-known/webfinger' ); - if ( ! \wp_http_validate_url( $url ) ) { - return new \WP_Error( 'invalid_webfinger_url', null, $url ); - } - - // try to access author URL - $response = \wp_remote_get( - $url, - array( - 'headers' => array( 'Accept' => 'application/activity+json' ), - 'redirection' => 0, - ) - ); - - if ( \is_wp_error( $response ) ) { - $link = new \WP_Error( 'webfinger_url_not_accessible', null, $url ); - \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period. - return $link; - } - - $response_code = \wp_remote_retrieve_response_code( $response ); - - $body = \wp_remote_retrieve_body( $response ); - $body = \json_decode( $body, true ); - - if ( ! isset( $body['links'] ) ) { - $link = new \WP_Error( 'webfinger_url_invalid_response', null, $url ); - \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period. - return $link; - } - - foreach ( $body['links'] as $link ) { - if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) { - \set_transient( $transient_key, $link['href'], WEEK_IN_SECONDS ); - return $link['href']; - } - } - - $link = new \WP_Error( 'webfinger_url_no_activity_pub', null, $body ); - \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period. - return $link; - } } diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index c4f59f2..e360c08 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -98,7 +98,7 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { */ public function friends_rewrite_incoming_url( $url, $incoming_url ) { if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $incoming_url ) ) { - $resolved_url = \Activitypub\Rest\Webfinger::resolve( $incoming_url ); + $resolved_url = \Activitypub\Webfinger::resolve( $incoming_url ); if ( ! is_wp_error( $resolved_url ) ) { return $resolved_url; } From 6feac1be3bea8f14be67f87493cd12e112fe7870 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Sat, 10 Dec 2022 08:46:44 +0100 Subject: [PATCH 75/94] Add filter to disable possible mention cache --- integration/class-friends-feed-parser-activitypub.php | 6 +++++- tests/test-class-friends-feed-parser-activitypub.php | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index e360c08..a59546b 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -413,7 +413,11 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { public function get_possible_mentions() { static $users = null; - if ( is_null( $users ) || true ) { + if ( ! method_exists( '\Friends\User_Feed', 'get_by_parser' ) ) { + return array(); + } + + if ( is_null( $users ) || ! apply_filters( 'activitypub_cache_possible_friend_mentions', true ) ) { $feeds = \Friends\User_Feed::get_by_parser( 'activitypub' ); $users = array(); foreach ( $feeds as $feed ) { diff --git a/tests/test-class-friends-feed-parser-activitypub.php b/tests/test-class-friends-feed-parser-activitypub.php index 889d43b..55a4230 100644 --- a/tests/test-class-friends-feed-parser-activitypub.php +++ b/tests/test-class-friends-feed-parser-activitypub.php @@ -163,6 +163,7 @@ class Test_Friends_Feed_Parser_ActivityPub extends ActivityPub_TestCase_Cache_HT } public function test_friend_mentions() { + add_filter( 'friends_cache_possible_mentions', '__return_false' ); $post = \wp_insert_post( array( 'post_author' => 1, @@ -188,6 +189,8 @@ class Test_Friends_Feed_Parser_ActivityPub extends ActivityPub_TestCase_Cache_HT $this->assertContains( $this->actor, $activitypub_activity->get_cc() ); remove_all_filters( 'activitypub_from_post_object' ); + remove_all_filters( 'activitypub_cache_possible_friend_mentions' ); + \wp_trash_post( $post ); } From a61f1168c3cad2cb82c8c42afa9ee9875b1bd356 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Sat, 10 Dec 2022 09:33:48 +0100 Subject: [PATCH 76/94] Automatically hide unknown @mentions in the Friends plugin, add a setting to change this --- .../class-friends-feed-parser-activitypub.php | 71 +++++++++++++++++++ ...-class-friends-feed-parser-activitypub.php | 58 ++++++++++++++- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index a59546b..7500ca0 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -30,6 +30,10 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { \add_action( 'friends_feed_parser_activitypub_unfollow', array( $this, 'unfollow_user' ), 10, 2 ); \add_filter( 'friends_rewrite_incoming_url', array( $this, 'friends_rewrite_incoming_url' ), 10, 2 ); + \add_filter( 'friends_edit_friend_table_end', array( $this, 'activitypub_settings' ), 10 ); + \add_filter( 'friends_edit_friend_after_form_submit', array( $this, 'activitypub_save_settings' ), 10 ); + \add_filter( 'friends_modify_feed_item', array( $this, 'modify_incoming_item' ), 9, 3 ); + \add_filter( 'the_content', array( $this, 'the_content' ), 99, 2 ); \add_filter( 'activitypub_extract_mentions', array( $this, 'activitypub_extract_mentions' ), 10, 2 ); } @@ -462,4 +466,71 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { return $result[0]; } + + public function activitypub_save_settings( \Friends\User $friend ) { + if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'edit-friend-feeds-' . $friend->ID ) ) { + + if ( isset( $_POST['friends_show_replies'] ) && $_POST['friends_show_replies'] ) { + $friend->update_user_option( 'activitypub_friends_show_replies', '1' ); + } else { + $friend->delete_user_option( 'activitypub_friends_show_replies' ); + } + } + } + + public function activitypub_settings( \Friends\User $friend ) { + foreach ( $friend->get_active_feeds() as $feed ) { + if ( 'activitypub' === $feed->parser ) { + return; + } + } + $show_replies = $friend->get_user_option( 'activitypub_friends_show_replies' ); + ?> + + ActivityPub + +
                            +
                            + /> + +
                            +
                            +

                            + +

                            + + + get_parser() ) { + return $item; + } + + if ( ! $friend_user->get_user_option( 'activitypub_friends_show_replies' ) ) { + $plain_text_content = \wp_strip_all_tags( $item->post_content ); + + if ( preg_match( ' /^@(?:[a-zA-Z0-9_.-]+)/i', $plain_text_content, $m ) ) { + $users = $this->get_possible_mentions(); + if ( ! isset( $users[ $m[0] ] ) ) { + $item->_feed_rule_transform = array( + 'post_status' => 'trash', + ); + } + } + } + + return $item; + } } diff --git a/tests/test-class-friends-feed-parser-activitypub.php b/tests/test-class-friends-feed-parser-activitypub.php index 55a4230..b4d5cf7 100644 --- a/tests/test-class-friends-feed-parser-activitypub.php +++ b/tests/test-class-friends-feed-parser-activitypub.php @@ -8,6 +8,7 @@ class Test_Friends_Feed_Parser_ActivityPub extends ActivityPub_TestCase_Cache_HT private $actor; public function test_incoming_post() { + update_user_option( 'activitypub_friends_show_replies', '1', $this->friend_id ); $now = time() - 10; $status_id = 123; @@ -107,6 +108,60 @@ class Test_Friends_Feed_Parser_ActivityPub extends ActivityPub_TestCase_Cache_HT $this->assertEquals( $content, $posts[0]->post_content ); $this->assertEquals( $this->friend_id, $posts[0]->post_author ); $this->assertEquals( $this->friend_name, get_post_meta( $posts[0]->ID, 'author', true ) ); + + delete_user_option( 'activitypub_friends_show_replies', $this->friend_id ); + + } + + public function test_incoming_mention_of_others() { + $now = time() - 10; + $status_id = 123; + + $posts = get_posts( + array( + 'post_type' => \Friends\Friends::CPT, + 'author' => $this->friend_id, + ) + ); + + $post_count = count( $posts ); + + // Let's post a new Note through the REST API. + $date = gmdate( \DATE_W3C, $now++ ); + $id = 'test' . $status_id; + $content = '@abc Test ' . $date . ' ' . wp_rand(); + + $request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/' . get_current_user_id() . '/inbox' ); + $request->set_param( 'type', 'Create' ); + $request->set_param( 'id', $id ); + $request->set_param( 'actor', $this->actor ); + + $request->set_param( + 'object', + array( + 'type' => 'Note', + 'id' => $id, + 'attributedTo' => $this->actor, + 'content' => $content, + 'url' => 'https://mastodon.local/users/akirk/statuses/' . ( $status_id++ ), + 'published' => $date, + ) + ); + + $response = $this->server->dispatch( $request ); + $this->assertEquals( 202, $response->get_status() ); + + $posts = get_posts( + array( + 'post_type' => \Friends\Friends::CPT, + 'author' => $this->friend_id, + 'post_status' => 'trash', + ) + ); + + $this->assertEquals( $post_count + 1, count( $posts ) ); + $this->assertStringStartsWith( $content, $posts[0]->post_content ); + $this->assertEquals( $this->friend_id, $posts[0]->post_author ); } public function test_incoming_announce() { @@ -159,11 +214,10 @@ class Test_Friends_Feed_Parser_ActivityPub extends ActivityPub_TestCase_Cache_HT $this->assertStringContainsString( 'Dezentrale Netzwerke', $posts[0]->post_content ); $this->assertEquals( $this->friend_id, $posts[0]->post_author ); $this->assertEquals( 'Matthias Pfefferle', get_post_meta( $posts[0]->ID, 'author', true ) ); - } public function test_friend_mentions() { - add_filter( 'friends_cache_possible_mentions', '__return_false' ); + add_filter( 'activitypub_cache_possible_friend_mentions', '__return_false' ); $post = \wp_insert_post( array( 'post_author' => 1, From a0c48f3a4835f6a028959ca854609e37cca54a99 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Sat, 10 Dec 2022 09:35:38 +0100 Subject: [PATCH 77/94] Fix typo --- integration/class-friends-feed-parser-activitypub.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index 7500ca0..5919189 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -496,9 +496,9 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser {

                        - +

                        From b027d1a8d0fe6c3443f81b75fb51687fe31ed2fa Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Sat, 10 Dec 2022 09:36:35 +0100 Subject: [PATCH 78/94] Fix typo --- integration/class-friends-feed-parser-activitypub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index 5919189..01d7b1a 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -497,7 +497,7 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser {

                        From 995c6c714d5b610aee1683e83dbc0edf69ad8278 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Sat, 10 Dec 2022 12:47:36 +0100 Subject: [PATCH 79/94] Don't show the ActivityPub section if there is no ActivityPub feed --- integration/class-friends-feed-parser-activitypub.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index 01d7b1a..6122e47 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -479,11 +479,16 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { } public function activitypub_settings( \Friends\User $friend ) { + $has_activitypub_feed = false; foreach ( $friend->get_active_feeds() as $feed ) { - if ( 'activitypub' === $feed->parser ) { - return; + if ( 'activitypub' === $feed->get_parser() ) { + $has_activitypub_feed = true; } } + + if ( ! $has_activitypub_feed ) { + return; + } $show_replies = $friend->get_user_option( 'activitypub_friends_show_replies' ); ?> From 0506e85aa6f220a6a321914ee98542939a2fe9e9 Mon Sep 17 00:00:00 2001 From: Alex Kirk Date: Sat, 10 Dec 2022 12:48:55 +0100 Subject: [PATCH 80/94] Code cleanup --- integration/class-friends-feed-parser-activitypub.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration/class-friends-feed-parser-activitypub.php b/integration/class-friends-feed-parser-activitypub.php index 6122e47..58717f3 100644 --- a/integration/class-friends-feed-parser-activitypub.php +++ b/integration/class-friends-feed-parser-activitypub.php @@ -489,14 +489,14 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser { if ( ! $has_activitypub_feed ) { return; } - $show_replies = $friend->get_user_option( 'activitypub_friends_show_replies' ); + ?> ActivityPub
                        - /> + get_user_option( 'activitypub_friends_show_replies' ) ); ?> />
                        @@ -507,7 +507,7 @@ class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser {

                        - Date: Mon, 12 Dec 2022 16:34:39 +0100 Subject: [PATCH 81/94] Improve regex --- activitypub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.php b/activitypub.php index 991f106..a483425 100644 --- a/activitypub.php +++ b/activitypub.php @@ -22,7 +22,7 @@ function init() { \defined( 'ACTIVITYPUB_EXCERPT_LENGTH' ) || \define( 'ACTIVITYPUB_EXCERPT_LENGTH', 400 ); \defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 ); \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=

                        )|(?<=
                        )|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' ); - \defined( 'ACTIVITYPUB_USERNAME_REGEXP' ) || \define( 'ACTIVITYPUB_USERNAME_REGEXP', '(?:[^@]+@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))' ); + \defined( 'ACTIVITYPUB_USERNAME_REGEXP' ) || \define( 'ACTIVITYPUB_USERNAME_REGEXP', '(?:[A-Za-z0-9_-]+@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))' ); \defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '