PluginProbe
ActivityPub / 8.1.1
ActivityPub v8.1.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
activitypub / integration / class-classic-editor.php

class-classic-editor.php in ActivityPub 8.1.1, at integration/class-classic-editor.php

306 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Classic Editor integration file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Integration;
9
10 /**
11 * Classic Editor integration class.
12 *
13 * Handles compatibility with the Classic Editor plugin and sites without block editor support.
14 */
15 class Classic_Editor {
16
17 /**
18 * Initialize the class, registering WordPress hooks.
19 */
20 public static function init() {
21 \add_filter( 'activitypub_attachments_media_markup', array( self::class, 'filter_attachments_media_markup' ), 10, 2 );
22 \add_filter( 'activitypub_attachment_ids', array( self::class, 'filter_attached_media_ids' ), 10, 2 );
23 \add_action( 'add_meta_boxes', array( self::class, 'add_meta_box' ) );
24 \add_action( 'save_post', array( self::class, 'save_meta_data' ) );
25
26 if ( \function_exists( 'classicpress_version' ) ) {
27 \add_filter( 'activitypub_site_supports_blocks', '__return_false' );
28 }
29 }
30
31 /**
32 * Filter attachment media markup to use shortcodes instead of blocks.
33 *
34 * @param string $markup The custom markup. Empty string by default.
35 * @param array $attachment_ids Array of attachment IDs.
36 *
37 * @return string The generated shortcode markup.
38 */
39 public static function filter_attachments_media_markup( $markup, $attachment_ids ) {
40 if ( empty( $attachment_ids ) ) {
41 return $markup;
42 }
43
44 $type = strtok( \get_post_mime_type( $attachment_ids[0] ), '/' );
45
46 // Single video or audio file: use media shortcode.
47 if ( 1 === \count( $attachment_ids ) && ( 'video' === $type || 'audio' === $type ) ) {
48 return sprintf(
49 '[%1$s src="%2$s"]',
50 \esc_attr( $type ),
51 \esc_url( \wp_get_attachment_url( $attachment_ids[0] ) )
52 );
53 }
54
55 // Multiple attachments or images: use gallery shortcode.
56 return '[gallery ids="' . implode( ',', $attachment_ids ) . '" link="none"]';
57 }
58
59 /**
60 * Filter to add attached media IDs from the post's media library.
61 *
62 * Returns additional image attachments from the post's media library,
63 * respecting the maximum image attachment limit. Only returns new
64 * attachments that can be added without exceeding the limit.
65 *
66 * @param array $attachments The current list of attachments.
67 * @param \WP_Post $item The post item.
68 *
69 * @return array Array of new media IDs to add (as associative arrays with 'id' key).
70 * Returns empty array if already at or over the limit.
71 */
72 public static function filter_attached_media_ids( $attachments, $item ) {
73 $max_media = \get_option( 'activitypub_max_image_attachments', \ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS );
74 $actual_count = \max( 0, $max_media - \count( $attachments ) );
75
76 if ( $actual_count <= 0 ) {
77 return $attachments;
78 }
79
80 $query = new \WP_Query(
81 array(
82 'post_parent' => $item->ID,
83 'post_status' => 'inherit',
84 'post_type' => 'attachment',
85 'post_mime_type' => 'image',
86 'order' => 'ASC',
87 'orderby' => 'menu_order ID',
88 'fields' => 'ids',
89 'posts_per_page' => $actual_count,
90 )
91 );
92
93 // Transform IDs into associative arrays.
94 $media_ids = \array_map(
95 static function ( $id ) {
96 return array( 'id' => $id );
97 },
98 $query->get_posts()
99 );
100
101 return \array_merge( $attachments, $media_ids );
102 }
103
104 /**
105 * Add ActivityPub meta box to the post editor.
106 *
107 * @param string $post_type The post type.
108 */
109 public static function add_meta_box( $post_type ) {
110 // Only add for post types that support ActivityPub.
111 if ( ! \post_type_supports( $post_type, 'activitypub' ) ) {
112 return;
113 }
114
115 \add_meta_box(
116 'activitypub-settings',
117 \__( 'Fediverse ⁂', 'activitypub' ),
118 array( self::class, 'render_meta_box' ),
119 $post_type,
120 'side',
121 'default'
122 );
123 }
124
125 /**
126 * Render the ActivityPub meta box.
127 *
128 * @param \WP_Post $post The post object.
129 */
130 public static function render_meta_box( $post ) {
131 // Add nonce for security.
132 \wp_nonce_field( 'activitypub_meta_box', 'activitypub_meta_box_nonce' );
133
134 // Get current values.
135 $content_warning = \get_post_meta( $post->ID, 'activitypub_content_warning', true );
136 $max_image_attachments = \get_post_meta( $post->ID, 'activitypub_max_image_attachments', true );
137 $content_visibility = self::get_default_visibility( $post );
138 $default_quote_policy = \get_option( 'activitypub_default_quote_policy', ACTIVITYPUB_INTERACTION_POLICY_ANYONE );
139 $quote_interaction = \get_post_meta( $post->ID, 'activitypub_interaction_policy_quote', true ) ?: $default_quote_policy;
140
141 ?>
142 <p>
143 <label for="activitypub_content_warning">
144 <strong><?php \esc_html_e( 'Content Warning', 'activitypub' ); ?></strong>
145 </label><br />
146 <input type="text" id="activitypub_content_warning" name="activitypub_content_warning" value="<?php echo \esc_attr( $content_warning ); ?>" class="widefat" placeholder="<?php \esc_attr_e( 'Optional content warning', 'activitypub' ); ?>" />
147 <span class="howto"><?php \esc_html_e( 'Content warnings do not change the content on your site, only in the fediverse.', 'activitypub' ); ?></span>
148 </p>
149
150 <p>
151 <label for="activitypub_max_image_attachments">
152 <strong><?php \esc_html_e( 'Maximum Image Attachments', 'activitypub' ); ?></strong>
153 </label><br />
154 <input type="number" id="activitypub_max_image_attachments" name="activitypub_max_image_attachments" value="<?php echo \esc_attr( $max_image_attachments ); ?>" min="0" max="10" class="small-text" />
155 <span class="howto"><?php \esc_html_e( 'Maximum number of image attachments to include when sharing to the fediverse.', 'activitypub' ); ?></span>
156 </p>
157
158 <p>
159 <strong><?php \esc_html_e( 'Visibility', 'activitypub' ); ?></strong><br />
160 <label>
161 <input type="radio" name="activitypub_content_visibility" value="<?php echo \esc_attr( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC ); ?>" <?php \checked( $content_visibility, ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC ); ?> />
162 <?php \esc_html_e( 'Public', 'activitypub' ); ?>
163 </label><br />
164 <label>
165 <input type="radio" name="activitypub_content_visibility" value="<?php echo \esc_attr( ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC ); ?>" <?php \checked( $content_visibility, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC ); ?> />
166 <?php \esc_html_e( 'Quiet public', 'activitypub' ); ?>
167 </label><br />
168 <label>
169 <input type="radio" name="activitypub_content_visibility" value="<?php echo \esc_attr( ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL ); ?>" <?php \checked( $content_visibility, ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL ); ?> />
170 <?php \esc_html_e( 'Do not federate', 'activitypub' ); ?>
171 </label><br />
172 <span class="howto"><?php \esc_html_e( 'This adjusts the visibility of a post in the fediverse, but note that it won\'t affect how the post appears on the blog.', 'activitypub' ); ?></span>
173 </p>
174
175 <p>
176 <label for="activitypub_interaction_policy_quote">
177 <strong><?php \esc_html_e( 'Who can quote this post?', 'activitypub' ); ?></strong>
178 </label><br />
179 <select id="activitypub_interaction_policy_quote" name="activitypub_interaction_policy_quote" class="widefat">
180 <option value="<?php echo \esc_attr( ACTIVITYPUB_INTERACTION_POLICY_ANYONE ); ?>" <?php \selected( $quote_interaction, ACTIVITYPUB_INTERACTION_POLICY_ANYONE ); ?>><?php \esc_html_e( 'Anyone', 'activitypub' ); ?></option>
181 <option value="<?php echo \esc_attr( ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS ); ?>" <?php \selected( $quote_interaction, ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS ); ?>><?php \esc_html_e( 'Followers only', 'activitypub' ); ?></option>
182 <option value="<?php echo \esc_attr( ACTIVITYPUB_INTERACTION_POLICY_ME ); ?>" <?php \selected( $quote_interaction, ACTIVITYPUB_INTERACTION_POLICY_ME ); ?>><?php \esc_html_e( 'Just me', 'activitypub' ); ?></option>
183 </select>
184 <span class="howto">
185 <?php \esc_html_e( 'Quoting allows others to cite your post while adding their own commentary.', 'activitypub' ); ?>
186 <?php
187 printf(
188 /* translators: %s: The current site default quote policy. Note the leading space. */
189 \esc_html__( ' Site default: %s', 'activitypub' ),
190 \esc_html( self::get_quote_policy_label( $default_quote_policy ) )
191 );
192 ?>
193 </span>
194 </p>
195 <?php
196 }
197
198 /**
199 * Get the label for a quote policy value.
200 *
201 * @param string $policy The policy value.
202 *
203 * @return string The translated label.
204 */
205 private static function get_quote_policy_label( $policy ) {
206 $labels = array(
207 ACTIVITYPUB_INTERACTION_POLICY_ANYONE => \__( 'Anyone', 'activitypub' ),
208 ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS => \__( 'Followers only', 'activitypub' ),
209 ACTIVITYPUB_INTERACTION_POLICY_ME => \__( 'Just me', 'activitypub' ),
210 );
211
212 return $labels[ $policy ] ?? $labels[ ACTIVITYPUB_INTERACTION_POLICY_ANYONE ];
213 }
214
215 /**
216 * Get default visibility based on post age and federation status.
217 *
218 * @param \WP_Post $post The post object.
219 *
220 * @return string The default visibility value.
221 */
222 private static function get_default_visibility( $post ) {
223 // If already set, use that value.
224 $saved_visibility = \get_post_meta( $post->ID, 'activitypub_content_visibility', true );
225 if ( $saved_visibility ) {
226 return $saved_visibility;
227 }
228
229 // If post is federated, use public.
230 $status = \get_post_meta( $post->ID, 'activitypub_status', true );
231 if ( ACTIVITYPUB_OBJECT_STATE_FEDERATED === $status ) {
232 return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC;
233 }
234
235 // If post is older than 1 month, default to local.
236 $post_timestamp = \strtotime( $post->post_date );
237 $one_month_ago = \strtotime( '-30 days' );
238
239 if ( $post_timestamp < $one_month_ago ) {
240 return ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL;
241 }
242
243 // Default to public for new posts.
244 return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC;
245 }
246
247 /**
248 * Save ActivityPub meta data.
249 *
250 * @param int $post_id The post ID.
251 */
252 public static function save_meta_data( $post_id ) {
253 // Check if this is an autosave.
254 if ( \defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
255 return;
256 }
257
258 // Only process for post types that support ActivityPub.
259 if ( ! \post_type_supports( \get_post_type( $post_id ), 'activitypub' ) ) {
260 return;
261 }
262
263 // Check user permissions.
264 if ( ! \current_user_can( 'edit_post', $post_id ) ) {
265 return;
266 }
267
268 // Verify nonce is present and valid.
269 if ( ! isset( $_POST['activitypub_meta_box_nonce'] ) ) {
270 return;
271 }
272
273 if ( ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['activitypub_meta_box_nonce'] ) ), 'activitypub_meta_box' ) ) {
274 return;
275 }
276
277 // Save content warning.
278 if ( isset( $_POST['activitypub_content_warning'] ) ) {
279 $content_warning = \sanitize_text_field( \wp_unslash( $_POST['activitypub_content_warning'] ) );
280 if ( ! empty( $content_warning ) ) {
281 \update_post_meta( $post_id, 'activitypub_content_warning', $content_warning );
282 } else {
283 \delete_post_meta( $post_id, 'activitypub_content_warning' );
284 }
285 }
286
287 // Save max image attachments.
288 if ( isset( $_POST['activitypub_max_image_attachments'] ) ) {
289 $max_images = \absint( $_POST['activitypub_max_image_attachments'] );
290 \update_post_meta( $post_id, 'activitypub_max_image_attachments', $max_images );
291 }
292
293 // Save content visibility.
294 if ( isset( $_POST['activitypub_content_visibility'] ) ) {
295 $visibility = \sanitize_text_field( \wp_unslash( $_POST['activitypub_content_visibility'] ) );
296 \update_post_meta( $post_id, 'activitypub_content_visibility', $visibility );
297 }
298
299 // Save quote interaction policy.
300 if ( isset( $_POST['activitypub_interaction_policy_quote'] ) ) {
301 $quote_policy = \sanitize_text_field( \wp_unslash( $_POST['activitypub_interaction_policy_quote'] ) );
302 \update_post_meta( $post_id, 'activitypub_interaction_policy_quote', $quote_policy );
303 }
304 }
305 }
306