367 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			367 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace Activitypub\Model;
 | |
| 
 | |
| use WP_Error;
 | |
| use WP_Query;
 | |
| use Activitypub\Activity\Actor;
 | |
| use Activitypub\Collection\Followers;
 | |
| 
 | |
| /**
 | |
|  * ActivityPub Follower Class
 | |
|  *
 | |
|  * This Object represents a single Follower.
 | |
|  * There is no direct reference to a WordPress User here.
 | |
|  *
 | |
|  * @author Matt Wiebe
 | |
|  * @author Matthias Pfefferle
 | |
|  *
 | |
|  * @see https://www.w3.org/TR/activitypub/#follow-activity-inbox
 | |
|  */
 | |
| class Follower extends Actor {
 | |
| 	/**
 | |
| 	 * The complete Remote-Profile of the Follower
 | |
| 	 *
 | |
| 	 * @var array
 | |
| 	 */
 | |
| 	protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
 | |
| 
 | |
| 	/**
 | |
| 	 * Get the errors.
 | |
| 	 *
 | |
| 	 * @return mixed
 | |
| 	 */
 | |
| 	public function get_errors() {
 | |
| 		return get_post_meta( $this->_id, 'activitypub_errors' );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get the Summary.
 | |
| 	 *
 | |
| 	 * @return int The Summary.
 | |
| 	 */
 | |
| 	public function get_summary() {
 | |
| 		if ( isset( $this->summary ) ) {
 | |
| 			return $this->summary;
 | |
| 		}
 | |
| 
 | |
| 		return '';
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Getter for URL attribute.
 | |
| 	 *
 | |
| 	 * Falls back to ID, if no URL is set. This is relevant for
 | |
| 	 * Plattforms like Lemmy, where the ID is the URL.
 | |
| 	 *
 | |
| 	 * @return string The URL.
 | |
| 	 */
 | |
| 	public function get_url() {
 | |
| 		if ( $this->url ) {
 | |
| 			return $this->url;
 | |
| 		}
 | |
| 
 | |
| 		return $this->id;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Reset (delete) all errors.
 | |
| 	 *
 | |
| 	 * @return void
 | |
| 	 */
 | |
| 	public function reset_errors() {
 | |
| 		delete_post_meta( $this->_id, 'activitypub_errors' );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Count the errors.
 | |
| 	 *
 | |
| 	 * @return int The number of errors.
 | |
| 	 */
 | |
| 	public function count_errors() {
 | |
| 		$errors = $this->get_errors();
 | |
| 
 | |
| 		if ( is_array( $errors ) && ! empty( $errors ) ) {
 | |
| 			return count( $errors );
 | |
| 		}
 | |
| 
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Return the latest error message.
 | |
| 	 *
 | |
| 	 * @return string The error message.
 | |
| 	 */
 | |
| 	public function get_latest_error_message() {
 | |
| 		$errors = $this->get_errors();
 | |
| 
 | |
| 		if ( is_array( $errors ) && ! empty( $errors ) ) {
 | |
| 			return reset( $errors );
 | |
| 		}
 | |
| 
 | |
| 		return '';
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Update the current Follower-Object.
 | |
| 	 *
 | |
| 	 * @return void
 | |
| 	 */
 | |
| 	public function update() {
 | |
| 		$this->save();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Validate the current Follower-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;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Save the current Follower-Object.
 | |
| 	 *
 | |
| 	 * @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.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
 | |
| 			$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 );
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		$args = array(
 | |
| 			'ID'           => $this->get__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'    => Followers::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(),
 | |
| 		);
 | |
| 
 | |
| 		$post_id = wp_insert_post( $args );
 | |
| 		$this->_id = $post_id;
 | |
| 
 | |
| 		return $post_id;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Upsert the current Follower-Object.
 | |
| 	 *
 | |
| 	 * @return int|WP_Error The Post-ID or an WP_Error.
 | |
| 	 */
 | |
| 	public function upsert() {
 | |
| 		return $this->save();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Delete the current Follower-Object.
 | |
| 	 *
 | |
| 	 * Beware that this os deleting a Follower for ALL users!!!
 | |
| 	 *
 | |
| 	 * To delete only the User connection (unfollow)
 | |
| 	 * @see \Activitypub\Rest\Followers::remove_follower()
 | |
| 	 *
 | |
| 	 * @return void
 | |
| 	 */
 | |
| 	public function delete() {
 | |
| 		wp_delete_post( $this->_id );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Update the post meta.
 | |
| 	 *
 | |
| 	 * @return void
 | |
| 	 */
 | |
| 	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 icon.
 | |
| 	 *
 | |
| 	 * Sets a fallback to better handle API and HTML outputs.
 | |
| 	 *
 | |
| 	 * @return array The icon.
 | |
| 	 */
 | |
| 	public function get_icon() {
 | |
| 		if ( isset( $this->icon['url'] ) ) {
 | |
| 			return $this->icon;
 | |
| 		}
 | |
| 
 | |
| 		return array(
 | |
| 			'type' => 'Image',
 | |
| 			'mediaType' => 'image/jpeg',
 | |
| 			'url'  => ACTIVITYPUB_PLUGIN_URL . 'assets/img/mp.jpg',
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get Name.
 | |
| 	 *
 | |
| 	 * Tries to extract a name from the URL or ID if not set.
 | |
| 	 *
 | |
| 	 * @return string The name.
 | |
| 	 */
 | |
| 	public function get_name() {
 | |
| 		if ( $this->name ) {
 | |
| 			return $this->name;
 | |
| 		} elseif ( $this->preferred_username ) {
 | |
| 			return $this->preferred_username;
 | |
| 		}
 | |
| 
 | |
| 		return $this->extract_name_from_uri();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * The preferred Username.
 | |
| 	 *
 | |
| 	 * Tries to extract a name from the URL or ID if not set.
 | |
| 	 *
 | |
| 	 * @return string The preferred Username.
 | |
| 	 */
 | |
| 	public function get_preferred_username() {
 | |
| 		if ( $this->preferred_username ) {
 | |
| 			return $this->preferred_username;
 | |
| 		}
 | |
| 
 | |
| 		return $this->extract_name_from_uri();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * 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 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;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Convert a Custom-Post-Type input to an Activitypub\Model\Follower.
 | |
| 	 *
 | |
| 	 * @return string The JSON string.
 | |
| 	 *
 | |
| 	 * @return array Activitypub\Model\Follower
 | |
| 	 */
 | |
| 	public static function init_from_cpt( $post ) {
 | |
| 		$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;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Infer a shortname from the Actor ID or URL. Used only for fallbacks,
 | |
| 	 * we will try to use what's supplied.
 | |
| 	 *
 | |
| 	 * @return string Hopefully the name of the Follower.
 | |
| 	 */
 | |
| 	protected function extract_name_from_uri() {
 | |
| 		// prefer the URL, but fall back to the ID.
 | |
| 		if ( $this->url ) {
 | |
| 			$name = $this->url;
 | |
| 		} else {
 | |
| 			$name = $this->id;
 | |
| 		}
 | |
| 
 | |
| 		if ( \filter_var( $name, FILTER_VALIDATE_URL ) ) {
 | |
| 			$name = \rtrim( $name, '/' );
 | |
| 			$path = \wp_parse_url( $name, PHP_URL_PATH );
 | |
| 
 | |
| 			if ( $path ) {
 | |
| 				if ( \strpos( $name, '@' ) !== false ) {
 | |
| 					// expected: https://example.com/@user (default URL pattern)
 | |
| 					$name = \preg_replace( '|^/@?|', '', $path );
 | |
| 				} else {
 | |
| 					// expected: https://example.com/users/user (default ID pattern)
 | |
| 					$parts = \explode( '/', $path );
 | |
| 					$name  = \array_pop( $parts );
 | |
| 				}
 | |
| 			}
 | |
| 		} elseif (
 | |
| 			\is_email( $name ) ||
 | |
| 			\strpos( $name, 'acct' ) === 0 ||
 | |
| 			\strpos( $name, '@' ) === 0
 | |
| 		) {
 | |
| 			// expected: user@example.com or acct:user@example (WebFinger)
 | |
| 			$name  = \ltrim( $name, '@' );
 | |
| 			$name  = \ltrim( $name, 'acct:' );
 | |
| 			$parts = \explode( '@', $name );
 | |
| 			$name  = $parts[0];
 | |
| 		}
 | |
| 
 | |
| 		return $name;
 | |
| 	}
 | |
| }
 |