From 64ad7d61ef73fd3549a09d0cc25f5142e17f9f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Menrath?= Date: Mon, 18 Nov 2024 16:07:09 +0100 Subject: [PATCH] wip --- .../collection/class-event-sources.php | 164 ++++++++++++ .../activitypub/model/class-event-source.php | 59 +++++ includes/admin/class-event-sources.php | 85 +++++++ includes/admin/class-settings-page.php | 5 + includes/class-event-sources.php | 97 +++++++ includes/class-setup.php | 1 + includes/table/class-event-sources.php | 240 ++++++++++++++++++ templates/admin-header.php | 9 +- templates/event-sources.php | 51 ++++ 9 files changed, 709 insertions(+), 2 deletions(-) create mode 100644 includes/activitypub/collection/class-event-sources.php create mode 100644 includes/activitypub/model/class-event-source.php create mode 100644 includes/admin/class-event-sources.php create mode 100644 includes/class-event-sources.php create mode 100644 includes/table/class-event-sources.php create mode 100644 templates/event-sources.php diff --git a/includes/activitypub/collection/class-event-sources.php b/includes/activitypub/collection/class-event-sources.php new file mode 100644 index 0000000..fed94ff --- /dev/null +++ b/includes/activitypub/collection/class-event-sources.php @@ -0,0 +1,164 @@ + array( + 'name' => _x( 'Event Sources', 'post_type plural name', 'activitypub' ), + 'singular_name' => _x( 'Event Source', 'post_type single name', 'activitypub' ), + ), + 'public' => false, + 'hierarchical' => false, + 'rewrite' => false, + 'query_var' => false, + 'delete_with_user' => false, + 'can_export' => true, + 'supports' => array(), + ) + ); + + \register_post_meta( + self::POST_TYPE, + 'activitypub_inbox', + array( + 'type' => 'string', + 'single' => true, + 'sanitize_callback' => 'sanitize_url', + ) + ); + + \register_post_meta( + self::POST_TYPE, + 'activitypub_errors', + array( + 'type' => 'string', + 'single' => false, + 'sanitize_callback' => function ( $value ) { + if ( ! is_string( $value ) ) { + throw new Exception( 'Error message is no valid string' ); + } + + return esc_sql( $value ); + }, + ) + ); + + \register_post_meta( + self::POST_TYPE, + 'activitypub_user_id', + array( + 'type' => 'string', + 'single' => false, + 'sanitize_callback' => function ( $value ) { + return esc_sql( $value ); + }, + ) + ); + + \register_post_meta( + self::POST_TYPE, + 'activitypub_actor_json', + array( + 'type' => 'string', + 'single' => true, + 'sanitize_callback' => function ( $value ) { + return sanitize_text_field( $value ); + }, + ) + ); + } + + /** + * Add new Event Source. + * + * @param string $actor The Actor ID. + * + * @return Event_Source|WP_Error The Followed (WP_Post array) or an WP_Error. + */ + public static function add_event_source( $actor ) { + $meta = get_remote_metadata_by_actor( $actor ); + + if ( is_tombstone( $meta ) ) { + return $meta; + } + + if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) { + return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ), array( 'status' => 400 ) ); + } + + $event_source = new Event_Source(); + $event_source->from_array( $meta ); + + $post_id = $event_source->save(); + + if ( is_wp_error( $post_id ) ) { + return $post_id; + } + + return $event_source; + } + + /** + * Remove an Event Source (=Followed ActivityPub actor). + * + * @param string $actor The Actor URL. + * + * @return bool True on success, false on failure. + */ + public static function remove_event_source( $actor ) { + $actor = true; + return $actor; + } + + /** + * Get all Followers. + * + * @return array The Term list of Followers. + */ + public static function get_all_followers() { + $args = array( + 'nopaging' => true, + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'activitypub_inbox', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'activitypub_actor_json', + 'compare' => 'EXISTS', + ), + ), + ); + return self::get_followers( null, null, null, $args ); + } +} diff --git a/includes/activitypub/model/class-event-source.php b/includes/activitypub/model/class-event-source.php new file mode 100644 index 0000000..c59113e --- /dev/null +++ b/includes/activitypub/model/class-event-source.php @@ -0,0 +1,59 @@ +get_icon(); + + if ( ! $icon ) { + return ''; + } + + if ( is_array( $icon ) ) { + return $icon['url']; + } + + return $icon; + } + + /** + * Convert a Custom-Post-Type input to an \ActivityPub_Event_Bridge\ActivityPub\Model\Event_Source. + * + * @param \WP_Post $post The post object. + * @return \ActivityPub_Event_Bridge\ActivityPub\Event_Source|WP_Error + */ + public static function init_from_cpt( $post ) { + if ( Event_Sources::POST_TYPE !== $post->post_type ) { + return false; + } + $actor_json = get_post_meta( $post->ID, 'activitypub_actor_json', true ); + $object = self::init_from_json( $actor_json ); + $object->set__id( $post->ID ); + $object->set_id( $post->guid ); + $object->set_name( $post->post_title ); + $object->set_summary( $post->post_excerpt ); + $object->set_published( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_date ) ) ); + $object->set_updated( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) ) ); + + return $object; + } +} diff --git a/includes/admin/class-event-sources.php b/includes/admin/class-event-sources.php new file mode 100644 index 0000000..f979dd9 --- /dev/null +++ b/includes/admin/class-event-sources.php @@ -0,0 +1,85 @@ +retrieve(); + + wp_send_json_success(); + } +} diff --git a/includes/admin/class-settings-page.php b/includes/admin/class-settings-page.php index b2dbb3e..c8132b6 100644 --- a/includes/admin/class-settings-page.php +++ b/includes/admin/class-settings-page.php @@ -116,6 +116,11 @@ class Settings_Page { \load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/settings.php', true, $args ); break; + case 'event-sources': + wp_enqueue_script( 'thickbox' ); + wp_enqueue_style( 'thickbox' ); + \load_template( ACTIVITYPUB_EVENT_BRIDGE_PLUGIN_DIR . 'templates/event-sources.php', true ); + break; case 'welcome': default: wp_enqueue_script( 'plugin-install' ); diff --git a/includes/class-event-sources.php b/includes/class-event-sources.php new file mode 100644 index 0000000..6081a2d --- /dev/null +++ b/includes/class-event-sources.php @@ -0,0 +1,97 @@ + array( + 'name' => _x( 'Event Sources', 'post_type plural name', 'activitypub' ), + 'singular_name' => _x( 'Event Source', 'post_type single name', 'activitypub' ), + ), + 'public' => false, + 'hierarchical' => false, + 'rewrite' => false, + 'query_var' => false, + 'delete_with_user' => false, + 'can_export' => true, + 'supports' => array(), + ) + ); + + \register_post_meta( + self::POST_TYPE, + 'activitypub_inbox', + array( + 'type' => 'string', + 'single' => true, + 'sanitize_callback' => 'sanitize_url', + ) + ); + + \register_post_meta( + self::POST_TYPE, + 'activitypub_errors', + array( + 'type' => 'string', + 'single' => false, + 'sanitize_callback' => function ( $value ) { + if ( ! is_string( $value ) ) { + throw new Exception( 'Error message is no valid string' ); + } + + return esc_sql( $value ); + }, + ) + ); + + \register_post_meta( + self::POST_TYPE, + 'activitypub_user_id', + array( + 'type' => 'string', + 'single' => false, + 'sanitize_callback' => function ( $value ) { + return esc_sql( $value ); + }, + ) + ); + + \register_post_meta( + self::POST_TYPE, + 'activitypub_actor_json', + array( + 'type' => 'string', + 'single' => true, + 'sanitize_callback' => function ( $value ) { + return sanitize_text_field( $value ); + }, + ) + ); + } +} diff --git a/includes/class-setup.php b/includes/class-setup.php index 2912002..e6217a6 100644 --- a/includes/class-setup.php +++ b/includes/class-setup.php @@ -181,6 +181,7 @@ class Setup { } add_action( 'init', array( Health_Check::class, 'init' ) ); + add_action( 'init', array( Event_Sources::class, 'register_taxonomy' ) ); // Check if the minimum required version of the ActivityPub plugin is installed. if ( ! version_compare( $this->activitypub_plugin_version, ACTIVITYPUB_EVENT_BRIDGE_ACTIVITYPUB_PLUGIN_MIN_VERSION ) ) { diff --git a/includes/table/class-event-sources.php b/includes/table/class-event-sources.php new file mode 100644 index 0000000..32e552a --- /dev/null +++ b/includes/table/class-event-sources.php @@ -0,0 +1,240 @@ + \__( 'Event Source', 'activitypub' ), + 'plural' => \__( 'Event Sources', 'activitypub' ), + 'ajax' => false, + ) + ); + } + + /** + * Get columns. + * + * @return array + */ + public function get_columns() { + return array( + 'cb' => '', + 'avatar' => \__( 'Avatar', 'activitypub' ), + 'post_title' => \__( 'Name', 'activitypub' ), + 'username' => \__( 'Username', 'activitypub' ), + 'url' => \__( 'URL', 'activitypub' ), + 'published' => \__( 'Followed', 'activitypub' ), + 'modified' => \__( 'Last updated', 'activitypub' ), + ); + } + + /** + * Returns sortable columns. + * + * @return array + */ + public function get_sortable_columns() { + return array( + 'post_title' => array( 'post_title', true ), + 'modified' => array( 'modified', false ), + 'published' => array( 'published', false ), + ); + } + + /** + * Prepare items. + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); + + $this->process_action(); + $this->_column_headers = array( $columns, $hidden, $this->get_sortable_columns() ); + + $page_num = $this->get_pagenum(); + $per_page = 20; + + $args = array(); + + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['orderby'] ) ) { + $args['orderby'] = sanitize_text_field( wp_unslash( $_GET['orderby'] ) ); + } + + if ( isset( $_GET['order'] ) ) { + $args['order'] = sanitize_text_field( wp_unslash( $_GET['order'] ) ); + } + + if ( isset( $_GET['s'] ) && isset( $_REQUEST['_wpnonce'] ) ) { + $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ); + if ( wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) { + $args['s'] = sanitize_text_field( wp_unslash( $_GET['s'] ) ); + } + } + // phpcs:enable WordPress.Security.NonceVerification.Recommended + + $dummy_event_sources = array( + 'total' => 1, + 'actors' => array( + Event_Source::init_from_array( + array( + 'id' => 'https://graz.social/@linos', + 'url' => 'https://graz.social/@linos', + 'preferredUsername' => 'linos', + 'name' => 'André Menrath', + 'icon' => 'https://graz.social/system/accounts/avatars/000/000/001/original/fe1c795256720361.jpeg', + ) + ), + ), + ); + + $event_sources = $dummy_event_sources; + $actors = $event_sources['actors']; + $counter = $event_sources['total']; + + $this->items = array(); + $this->set_pagination_args( + array( + 'total_items' => $counter, + 'total_pages' => ceil( $counter / $per_page ), + 'per_page' => $per_page, + ) + ); + + foreach ( $actors as $actor ) { + $item = array( + 'icon' => esc_attr( $actor->get_icon_url() ), + 'post_title' => esc_attr( $actor->get_name() ), + 'username' => esc_attr( $actor->get_preferred_username() ), + 'url' => esc_attr( object_to_uri( $actor->get_url() ) ), + 'identifier' => esc_attr( $actor->get_id() ), + 'published' => esc_attr( $actor->get_published() ), + 'modified' => esc_attr( $actor->get_updated() ), + ); + + $this->items[] = $item; + } + } + + /** + * Returns bulk actions. + * + * @return array + */ + public function get_bulk_actions() { + return array( + 'delete' => __( 'Delete', 'activitypub' ), + ); + } + + /** + * Column default. + * + * @param array $item Item. + * @param string $column_name Column name. + * @return string + */ + public function column_default( $item, $column_name ) { + if ( ! array_key_exists( $column_name, $item ) ) { + return __( 'None', 'activitypub' ); + } + return $item[ $column_name ]; + } + + /** + * Column avatar. + * + * @param array $item Item. + * @return string + */ + public function column_avatar( $item ) { + return sprintf( + '', + $item['icon'] + ); + } + + /** + * Column url. + * + * @param array $item Item. + * @return string + */ + public function column_url( $item ) { + return sprintf( + '%s', + esc_url( $item['url'] ), + $item['url'] + ); + } + + /** + * Column cb. + * + * @param array $item Item. + * @return string + */ + public function column_cb( $item ) { + return sprintf( '', esc_attr( $item['identifier'] ) ); + } + + /** + * Process action. + */ + public function process_action() { + if ( ! isset( $_REQUEST['followers'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) { + return; + } + $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ); + if ( ! wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) { + return; + } + + if ( ! current_user_can( 'edit_user', $this->user_id ) ) { + return; + } + + $followers = $_REQUEST['followers']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput + + if ( $this->current_action() === 'delete' ) { + if ( ! is_array( $followers ) ) { + $followers = array( $followers ); + } + foreach ( $followers as $follower ) { + FollowerCollection::remove_follower( $this->user_id, $follower ); + } + } + } + + /** + * Returns user count. + * + * @return int + */ + public function get_user_count() { + return FollowerCollection::count_followers( $this->user_id ); + } +} diff --git a/templates/admin-header.php b/templates/admin-header.php index efc02bc..855f055 100644 --- a/templates/admin-header.php +++ b/templates/admin-header.php @@ -9,8 +9,9 @@ $args = wp_parse_args( $args, array( - 'welcome' => '', - 'settings' => '', + 'welcome' => '', + 'settings' => '', + 'event-sources' => '', ) ); ?> @@ -28,6 +29,10 @@ $args = wp_parse_args( + + + +
diff --git a/templates/event-sources.php b/templates/event-sources.php new file mode 100644 index 0000000..2e46916 --- /dev/null +++ b/templates/event-sources.php @@ -0,0 +1,51 @@ + 'active', + ) +); + + +$table = new \ActivityPub_Event_Bridge\Table\Event_Sources(); +?> + +
+ +
+

+

+ + + + + + + + +
+ +
+
+ + + prepare_items(); + $table->search_box( 'Search', 'search' ); + $table->display(); + ?> +
+