PluginProbe
ActivityPub / 9.2.1
ActivityPub v9.2.1
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
← All changes | includes/collection/class-posts.php +48 -17 8.2.19.2.1 View file →
@@ -11,8 +11,9 @@
11 11 use Activitypub\Hashtag;
12 12 use Activitypub\Link;
13 13
14 14 use function Activitypub\get_content_visibility;
15 +use function Activitypub\user_can_act_as_blog;
15 16
16 17 /**
17 18 * Posts collection.
18 19 *
@@ -33,24 +34,46 @@
33 34 *
34 35 * @return \WP_Post|\WP_Error The created post on success, WP_Error on failure.
35 36 */
36 37 public static function create( $activity, $user_id, $visibility = null ) {
37 - // Verify the user has permission to create posts.
38 - if ( $user_id > 0 && ! \user_can( $user_id, 'publish_posts' ) ) {
39 - return new \WP_Error(
40 - 'activitypub_forbidden',
41 - \__( 'You do not have permission to create posts.', 'activitypub' ),
42 - array( 'status' => 403 )
43 - );
38 + // Resolve the post author. Blog actor falls back to the current user for a real byline.
39 + $post_author = $user_id > 0 ? $user_id : \get_current_user_id();
40 +
41 + /*
42 + * Authorize the request:
43 + * - Per-user path: require `publish_posts` on the URL-specified user.
44 + * - Blog actor path (post_author falls back to current user): require
45 + * the act-as-blog grant. `publish_posts` is implicit because the
46 + * helper defaults to `manage_options` (administrators).
47 + * - Cron/CLI path keeps `post_author = 0` and bypasses both checks.
48 + */
49 + if ( $post_author > 0 ) {
50 + $authorized = $post_author === (int) $user_id
51 + ? \user_can( $user_id, 'publish_posts' )
52 + : user_can_act_as_blog();
53 +
54 + if ( ! $authorized ) {
55 + return new \WP_Error(
56 + 'activitypub_forbidden',
57 + \__( 'You do not have permission to create posts.', 'activitypub' ),
58 + array( 'status' => 403 )
59 + );
60 + }
44 61 }
45 62
46 63 $object = $activity['object'] ?? array();
47 64
48 - $object_type = $object['type'] ?? '';
49 - $content = \wp_kses_post( $object['content'] ?? '' );
50 - $name = \sanitize_text_field( $object['name'] ?? '' );
51 - $summary = \wp_kses_post( $object['summary'] ?? '' );
65 + $object_type = $object['type'] ?? '';
66 + $content = \wp_kses_post( $object['content'] ?? '' );
67 + $name = \sanitize_text_field( $object['name'] ?? '' );
68 + $summary = \wp_kses_post( $object['summary'] ?? '' );
69 + $plain_summary = \sanitize_text_field( $summary );
52 70
71 + // A summary marked sensitive is a content warning (plain text); otherwise it's a regular excerpt.
72 + // Route on the sanitized summary so whitespace-only values don't pollute either field.
73 + $content_warning = ! empty( $object['sensitive'] ) && '' !== $plain_summary ? $plain_summary : '';
74 + $post_excerpt = '' === $content_warning && '' !== $plain_summary ? $summary : '';
75 +
53 76 // Process content: autop, autolink, hashtags, and convert to blocks.
54 77 $content = self::prepare_content( $content );
55 78
56 79 // Use name as title for Articles, or generate from content for Notes.
@@ -64,16 +87,17 @@
64 87 $visibility = get_content_visibility( $activity );
65 88 }
66 89
67 90 $post_data = array(
68 - 'post_author' => $user_id > 0 ? $user_id : 0,
91 + 'post_author' => $post_author,
69 92 'post_title' => $title,
70 93 'post_content' => $content,
71 - 'post_excerpt' => $summary,
94 + 'post_excerpt' => $post_excerpt,
72 95 'post_status' => ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE === $visibility ? 'private' : 'publish',
73 96 'post_type' => 'post',
74 97 'meta_input' => array(
75 98 'activitypub_content_visibility' => $visibility,
99 + 'activitypub_content_warning' => $content_warning,
76 100 ),
77 101 );
78 102
79 103 $post_id = \wp_insert_post( $post_data, true );
@@ -103,12 +127,18 @@
103 127 */
104 128 public static function update( $post, $activity, $visibility = null ) {
105 129 $object = $activity['object'] ?? array();
106 130
107 - $content = \wp_kses_post( $object['content'] ?? '' );
108 - $name = \sanitize_text_field( $object['name'] ?? '' );
109 - $summary = \wp_kses_post( $object['summary'] ?? '' );
131 + $content = \wp_kses_post( $object['content'] ?? '' );
132 + $name = \sanitize_text_field( $object['name'] ?? '' );
133 + $summary = \wp_kses_post( $object['summary'] ?? '' );
134 + $plain_summary = \sanitize_text_field( $summary );
110 135
136 + // A summary marked sensitive is a content warning (plain text); otherwise it's a regular excerpt.
137 + // Route on the sanitized summary so whitespace-only values don't pollute either field.
138 + $content_warning = ! empty( $object['sensitive'] ) && '' !== $plain_summary ? $plain_summary : '';
139 + $post_excerpt = '' === $content_warning && '' !== $plain_summary ? $summary : '';
140 +
111 141 // Process content: autop, autolink, hashtags, and convert to blocks.
112 142 $content = self::prepare_content( $content );
113 143
114 144 // Use name as title for Articles, or generate from content for Notes.
@@ -125,11 +155,12 @@
125 155 $post_data = array(
126 156 'ID' => $post->ID,
127 157 'post_title' => $title,
128 158 'post_content' => $content,
129 - 'post_excerpt' => $summary,
159 + 'post_excerpt' => $post_excerpt,
130 160 'meta_input' => array(
131 161 'activitypub_content_visibility' => $visibility,
162 + 'activitypub_content_warning' => $content_warning,
132 163 ),
133 164 );
134 165
135 166 $post_id = \wp_update_post( $post_data, true );