<?php /** * Event-Source (=ActivityPub Actor that is followed) model. * * @package Event_Bridge_For_ActivityPub * @license AGPL-3.0-or-later */ namespace Event_Bridge_For_ActivityPub\ActivityPub\Model; use Activitypub\Activity\Actor; use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources; use WP_Error; /** * Event-Source (=ActivityPub Actor that is followed) model. */ class Event_Source extends Actor { const ACTIVITYPUB_USER_HANDLE_REGEXP = '(?:([A-Za-z0-9_.-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))'; /** * The complete remote ActivityPub profile of the Event Source. * * @var int */ protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore /** * Get the Icon URL (Avatar). * * @return string The URL to the Avatar. */ public function get_icon_url() { $icon = $this->get_icon(); if ( ! $icon ) { return ''; } if ( is_array( $icon ) ) { return $icon['url']; } return $icon; } /** * Get the WordPress post which stores the Event Source by the ActivityPub actor id of the event source. * * @param string $actor_id The ActivityPub actor ID. * @return ?int The WordPress post ID if the actor is found, null if not. */ public static function get_wp_post_from_activitypub_actor_id( $actor_id ) { global $wpdb; $post_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE guid=%s AND post_type=%s", esc_sql( $actor_id ), Event_Sources::POST_TYPE ) ); return $post_id ? intval( $post_id ) : null; } /** * Convert a Custom-Post-Type input to an \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source. * * @param \WP_Post $post The post object. * @return \Event_Bridge_For_ActivityPub\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 ) ) ); $thumbnail_id = get_post_thumbnail_id( $post ); if ( $thumbnail_id ) { $object->set_icon( array( 'type' => 'Image', 'url' => wp_get_attachment_image_url( $thumbnail_id, 'thumbnail', true ), ) ); } return $object; } /** * Validate the current Event Source ActivityPub actor object. * * @return boolean True if the verification was successful. */ public function is_valid() { // The minimum required attributes. $required_attributes = array( 'id', 'preferredUsername', 'inbox', 'publicKey', 'publicKeyPem', ); foreach ( $required_attributes as $attribute ) { if ( ! $this->get( $attribute ) ) { return false; } } return true; } /** * Update the post meta. */ protected function get_post_meta_input() { $meta_input = array(); $meta_input['activitypub_inbox'] = $this->get_shared_inbox(); $meta_input['activitypub_actor_json'] = $this->to_json(); return $meta_input; } /** * Get the shared inbox, with a fallback to the inbox. * * @return string|null The URL to the shared inbox, the inbox or null. */ public function get_shared_inbox() { if ( ! empty( $this->get_endpoints()['sharedInbox'] ) ) { return $this->get_endpoints()['sharedInbox']; } elseif ( ! empty( $this->get_inbox() ) ) { return $this->get_inbox(); } return null; } /** * Save the current Event Source object to Database within custom post type. * * @return int|WP_Error The post ID or an WP_Error. */ public function save() { if ( ! $this->is_valid() ) { return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ), array( 'status' => 400 ) ); } if ( ! $this->get__id() ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery $post_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE guid=%s", esc_sql( $this->get_id() ) ) ); if ( $post_id ) { $post = get_post( $post_id ); $this->set__id( $post->ID ); } } $post_id = $this->get__id(); $args = array( 'ID' => $post_id, 'guid' => esc_url_raw( $this->get_id() ), 'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ), 'post_author' => 0, 'post_type' => Event_Sources::POST_TYPE, 'post_name' => esc_url_raw( $this->get_id() ), 'post_excerpt' => sanitize_text_field( wp_kses( $this->get_summary(), 'user_description' ) ), 'post_status' => 'publish', 'meta_input' => $this->get_post_meta_input(), ); if ( ! empty( $post_id ) ) { // If this is an update, prevent the "added" date from being overwritten by the current date. $post = get_post( $post_id ); $args['post_date'] = $post->post_date; $args['post_date_gmt'] = $post->post_date_gmt; } $post_id = wp_insert_post( $args ); $this->_id = $post_id; // Abort if inserting or updating the post didn't work. if ( 0 === $post_id || is_wp_error( $post_id ) ) { return $post_id; } // Delete old icon. // Check if the post has a thumbnail. $thumbnail_id = get_post_thumbnail_id( $post_id ); if ( $thumbnail_id ) { // Remove the thumbnail from the post. delete_post_thumbnail( $post_id ); // Delete the attachment (and its files) from the media library. wp_delete_attachment( $thumbnail_id, true ); } // Set new icon. $icon = $this->get_icon(); if ( isset( $icon['url'] ) ) { $image = media_sideload_image( $icon['url'], $post_id, null, 'id' ); } if ( isset( $image ) && ! is_wp_error( $image ) ) { set_post_thumbnail( $post_id, $image ); } return $post_id; } }