WP2D_Post::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * A diaspora* flavoured WP Post class.
4
 *
5
 * @package WP_To_Diaspora\Post
6
 * @since   1.5.0
7
 */
8
9
// Exit if accessed directly.
10
defined( 'ABSPATH' ) || exit;
11
12
use League\HTMLToMarkdown\HtmlConverter;
13
14
/**
15
 * Custom diaspora* post class to manage all post related things.
16
 *
17
 * @since 1.5.0
18
 */
19
class WP2D_Post {
20
21
	/**
22
	 * The original post object.
23
	 *
24
	 * @since 1.5.0
25
	 *
26
	 * @var WP_Post
27
	 */
28
	public $post;
29
30
	/**
31
	 * The original post ID.
32
	 *
33
	 * @since 1.5.0
34
	 *
35
	 * @var int
36
	 */
37
	public $ID;
38
39
	/**
40
	 * If this post should be shared on diaspora*.
41
	 *
42
	 * @since 1.5.0
43
	 *
44
	 * @var bool
45
	 */
46
	public $post_to_diaspora;
47
48
	/**
49
	 * If a link back to the original post should be added.
50
	 *
51
	 * @since 1.5.0
52
	 *
53
	 * @var bool
54
	 */
55
	public $fullentrylink;
56
57
	/**
58
	 * What content gets posted.
59
	 *
60
	 * @since 1.5.0
61
	 *
62
	 * @var string
63
	 */
64
	public $display;
65
66
	/**
67
	 * The types of tags to post. (global,custom,post)
68
	 *
69
	 * @since 1.5.0
70
	 *
71
	 * @var array
72
	 */
73
	public $tags_to_post;
74
75
	/**
76
	 * The post's custom tags.
77
	 *
78
	 * @since 1.5.0
79
	 *
80
	 * @var array
81
	 */
82
	public $custom_tags;
83
84
	/**
85
	 * Aspects this post gets posted to.
86
	 *
87
	 * @since 1.5.0
88
	 *
89
	 * @var array
90
	 */
91
	public $aspects;
92
93
	/**
94
	 * Services this post gets posted to.
95
	 *
96
	 * @since 1.5.0
97
	 *
98
	 * @var array
99
	 */
100
	public $services;
101
102
	/**
103
	 * The post's history of diaspora* posts.
104
	 *
105
	 * @since 1.5.0
106
	 *
107
	 * @var array
108
	 */
109
	public $post_history;
110
111
	/**
112
	 * If the post actions have all been set up already.
113
	 *
114
	 * @since 1.5.0
115
	 *
116
	 * @var boolean
117
	 */
118
	private static $is_set_up = false;
119
120
	/**
121
	 * Setup all the necessary WP callbacks.
122
	 *
123
	 * @since 1.5.0
124
	 */
125
	public static function setup() {
126
		if ( self::$is_set_up ) {
127
			return;
128
		}
129
130
		$instance = new WP2D_Post( null );
131
132
		// Notices when a post has been shared or if it has failed.
133
		add_action( 'admin_notices', [ $instance, 'admin_notices' ] );
134
		add_action( 'admin_init', [ $instance, 'ignore_post_error' ] );
135
136
		// Handle diaspora* posting when saving the post.
137
		add_action( 'save_post', [ $instance, 'post' ], 20, 2 );
138
		add_action( 'save_post', [ $instance, 'save_meta_box_data' ], 10 );
139
140
		// Add meta boxes.
141
		add_action( 'add_meta_boxes', [ $instance, 'add_meta_boxes' ] );
142
143
		// AJAX callback for diaspora* post history.
144
		add_action( 'wp_ajax_wp_to_diaspora_get_post_history', [ $instance, 'get_post_history_callback' ] );
145
146
		self::$is_set_up = true;
147
	}
148
149
	/**
150
	 * Constructor.
151
	 *
152
	 * @since 1.5.0
153
	 *
154
	 * @param int|WP_Post $post Post ID or the post itself.
155
	 */
156
	public function __construct( $post ) {
157
		$this->assign_wp_post( $post );
158
	}
159
160
	/**
161
	 * Assign the original WP_Post object and all the custom meta data.
162
	 *
163
	 * @since 1.5.0
164
	 *
165
	 * @param int|WP_Post $post Post ID or the post itself.
166
	 */
167
	private function assign_wp_post( $post ) {
168
		if ( $this->post = get_post( $post ) ) {
169
			$this->ID = $this->post->ID;
170
171
			$options = WP2D_Options::instance();
172
173
			// Assign all meta values, expanding non-existent ones with the defaults.
174
			$meta_current = get_post_meta( $this->ID, '_wp_to_diaspora', true );
175
			$meta         = wp_parse_args(
176
				$meta_current,
177
				$options->get_options()
178
			);
179
			if ( $meta ) {
180
				foreach ( $meta as $key => $value ) {
181
					$this->$key = $value;
182
				}
183
			}
184
185
			// If no WP2D meta data has been saved yet, this post shouldn't be published.
186
			// This can happen if existing posts (before WP2D) get updated externally, not through the post edit screen.
187
			// Check DiasPHPora/wp-to-diaspora#91 for reference.
188
			// Also, when we have a post scheduled for publishing, don't touch it.
189
			// This is important when modifying scheduled posts using Quick Edit.
190
			if ( ! $meta_current && ! in_array( $this->post->post_status, [ 'auto-draft', 'future' ], true ) ) {
191
				$this->post_to_diaspora = false;
192
			}
193
194
			$this->post_history = get_post_meta( $this->ID, '_wp_to_diaspora_post_history', true );
195
		}
196
	}
197
198
	/**
199
	 * Post to diaspora* when saving a post.
200
	 *
201
	 * @since 1.5.0
202
	 *
203
	 * @todo  Maybe somebody wants to share a password protected post to a closed aspect.
204
	 *
205
	 * @param integer $post_id ID of the post being saved.
206
	 * @param WP_Post $post    Post object being saved.
207
	 *
208
	 * @return bool If the post was posted successfully.
209
	 */
210
	public function post( $post_id, $post ) {
211
		// Ignore any revisions and auto-saves.
212
		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
213
			return false;
214
		}
215
216
		$this->assign_wp_post( $post );
217
218
		$options = WP2D_Options::instance();
219
220
		// Is this post type enabled for posting?
221
		if ( ! in_array( $post->post_type, $options->get_option( 'enabled_post_types' ), true ) ) {
222
			return false;
223
		}
224
225
		// Make sure we're posting to diaspora* and the post isn't password protected.
226
		if ( ! ( $this->post_to_diaspora && 'publish' === $post->post_status && '' === $post->post_password ) ) {
227
			return false;
228
		}
229
230
		// Unset post_to_diaspora meta field to prevent mistakenly republishing to diaspora*.
231
		$meta                     = get_post_meta( $post_id, '_wp_to_diaspora', true );
232
		$meta['post_to_diaspora'] = false;
233
		update_post_meta( $post_id, '_wp_to_diaspora', $meta );
234
235
		$status_message = $this->get_title_link();
236
237
		// Post the full post text, just the excerpt, or nothing at all?
238
		if ( 'full' === $this->display ) {
239
			$status_message .= $this->get_full_content();
240
		} elseif ( 'excerpt' === $this->display ) {
241
			$status_message .= $this->get_excerpt_content();
242
		}
243
244
		// Add the tags assigned to the post.
245
		$status_message .= $this->get_tags_to_add();
246
247
		// Add the original entry link to the post?
248
		$status_message .= $this->get_posted_at_link();
249
250
		$status_converter = new HtmlConverter( [ 'strip_tags' => true ] );
251
		$status_message   = $status_converter->convert( $status_message );
252
253
		// Set up the connection to diaspora*.
254
		$api = WP2D_Helpers::api_quick_connect();
255
		if ( empty( $status_message ) ) {
256
			return false;
257
		}
258
259
		if ( $api->has_last_error() ) {
260
			// Save the post error as post meta data, so we can display it to the user.
261
			update_post_meta( $post_id, '_wp_to_diaspora_post_error', $api->get_last_error() );
262
263
			return false;
264
		}
265
266
		// Add services to share to via diaspora*.
267
		$extra_data = [
268
			'services' => $this->services,
269
		];
270
271
		// Try to post to diaspora*.
272
		$response = $api->post( $status_message, $this->aspects, $extra_data );
273
		if ( ! $response ) {
274
			return false;
275
		}
276
277
		// Save certain diaspora* post data as meta data for future reference.
278
		$this->save_to_history( (object) $response );
279
280
		// If there is still a previous post error around, remove it.
281
		delete_post_meta( $post_id, '_wp_to_diaspora_post_error' );
282
283
		// Prevent any duplicate hook firing.
284
		remove_action( 'save_post', [ $this, 'post' ], 20, 2 );
285
286
		return true;
287
	}
288
289
	/**
290
	 * Get the title of the post linking to the post itself.
291
	 *
292
	 * @since 1.5.0
293
	 *
294
	 * @return string Post title as a link.
295
	 */
296
	private function get_title_link() {
297
		$title      = esc_html( $this->post->post_title );
298
		$permalink  = get_permalink( $this->ID );
299
		$title_link = sprintf( '<strong><a href="%2$s" title="%2$s">%1$s</a></strong>', $title, $permalink );
300
301
		/**
302
		 * Filter the title link at the top of the post.
303
		 *
304
		 * @since 1.5.4.1
305
		 *
306
		 * @param string    $default   The whole HTML of the title link to be outputted.
307
		 * @param WP2D_Post $wp2d_post This object, to allow total customisation of the title.
308
		 */
309
		return apply_filters( 'wp2d_title_filter', "<p>{$title_link}</p>", $this );
310
	}
311
312
	/**
313
	 * Get the full post content with only default filters applied.
314
	 *
315
	 * @since 1.5.0
316
	 *
317
	 * @return string The full post content.
318
	 */
319
	private function get_full_content() {
320
		// Only allow certain shortcodes.
321
		global $shortcode_tags;
322
		$shortcode_tags_bkp = [];
323
324
		foreach ( $shortcode_tags as $shortcode_tag => $shortcode_function ) {
325
			if ( ! in_array( $shortcode_tag, apply_filters( 'wp2d_shortcodes_filter', [ 'wp_caption', 'caption', 'gallery' ] ), true ) ) {
326
				$shortcode_tags_bkp[ $shortcode_tag ] = $shortcode_function;
327
				unset( $shortcode_tags[ $shortcode_tag ] );
328
			}
329
		}
330
331
		// Disable all filters and then enable only defaults. This prevents additional filters from being posted to diaspora*.
332
		remove_all_filters( 'the_content' );
333
334
		/** @var array $content_filters List of filters to apply to the content. */
335
		$content_filters = apply_filters( 'wp2d_content_filters_filter', [ 'do_shortcode', 'wptexturize', 'convert_smilies', 'convert_chars', 'wpautop', 'shortcode_unautop', 'prepend_attachment', [ $this, 'embed_remove' ] ] );
336
		foreach ( $content_filters as $filter ) {
337
			add_filter( 'the_content', $filter );
338
		}
339
340
		// Extract URLs from [embed] shortcodes.
341
		add_filter( 'embed_oembed_html', [ $this, 'embed_url' ], 10, 2 );
342
343
		// Add the pretty caption after the images.
344
		add_filter( 'img_caption_shortcode', [ $this, 'custom_img_caption' ], 10, 3 );
345
346
		// Overwrite the native shortcode handler to add pretty captions.
347
		// http://wordpress.stackexchange.com/a/74675/54456 for explanation.
348
		add_shortcode( 'gallery', [ $this, 'custom_gallery_shortcode' ] );
349
350
		$post_content = apply_filters( 'the_content', $this->post->post_content );
351
352
		// Put the removed shortcode tags back again.
353
		$shortcode_tags += $shortcode_tags_bkp; // phpcs:ignore
354
355
		/**
356
		 * Filter the full content of the post.
357
		 *
358
		 * @since 2.1.0
359
		 *
360
		 * @param string    $default   The whole HTML of the post to be outputted.
361
		 * @param WP2D_Post $wp2d_post This object, to allow total customisation of the post.
362
		 */
363
		return apply_filters( 'wp2d_post_filter', $post_content, $this );
364
	}
365
366
	/**
367
	 * Get the post's excerpt in a nice format.
368
	 *
369
	 * @since 1.5.0
370
	 *
371
	 * @return string Post's excerpt.
372
	 */
373
	private function get_excerpt_content() {
374
		// Look for the excerpt in the following order:
375
		// 1. Custom post excerpt.
376
		// 2. Text up to the <!--more--> tag.
377
		// 3. Manually trimmed content.
378
		$content = $this->post->post_content;
379
		$excerpt = $this->post->post_excerpt;
380
		if ( '' === $excerpt ) {
381
			if ( $more_pos = strpos( $content, '<!--more' ) ) {
382
				$excerpt = substr( $content, 0, $more_pos );
383
			} else {
384
				$excerpt = wp_trim_words( $content, 42, '[...]' );
385
			}
386
		}
387
388
		/**
389
		 * Filter the excerpt of the post.
390
		 *
391
		 * @since 2.1.0
392
		 *
393
		 * @param string    $default   The whole HTML of the excerpt to be outputted.
394
		 * @param WP2D_Post $wp2d_post This object, to allow total customisation of the excerpt.
395
		 */
396
		return apply_filters( 'wp2d_excerpt_filter', "<p>{$excerpt}</p>", $this );
397
	}
398
399
	/**
400
	 * Get a string of tags that have been added to the post.
401
	 *
402
	 * @since 1.5.0
403
	 *
404
	 * @return string Tags added to the post.
405
	 */
406
	private function get_tags_to_add() {
407
		$options       = WP2D_Options::instance();
408
		$tags_to_post  = $this->tags_to_post;
409
		$tags_to_add   = '';
410
		$diaspora_tags = [];
411
412
		// Add any diaspora* tags?
413
		if ( ! empty( $tags_to_post ) ) {
414
			// The diaspora* tags to add to the post.
415
			$diaspora_tags_tmp = [];
416
417
			// Add global tags?
418
			$global_tags = $options->get_option( 'global_tags' );
419
			if ( is_array( $global_tags ) && in_array( 'global', $tags_to_post, true ) ) {
420
				$diaspora_tags_tmp += array_flip( $global_tags );
421
			}
422
423
			// Add custom tags?
424
			if ( is_array( $this->custom_tags ) && in_array( 'custom', $tags_to_post, true ) ) {
425
				$diaspora_tags_tmp += array_flip( $this->custom_tags );
426
			}
427
428
			// Add post tags?
429
			$post_tags = wp_get_post_tags( $this->ID, [ 'fields' => 'slugs' ] );
430
			if ( is_array( $post_tags ) && in_array( 'post', $tags_to_post, true ) ) {
431
				$diaspora_tags_tmp += array_flip( $post_tags );
432
			}
433
434
			// Get an array of cleaned up tags.
435
			// NOTE: Validate method needs a variable, as it's passed by reference!
436
			$diaspora_tags_tmp = array_keys( $diaspora_tags_tmp );
437
			$options->validate_tags( $diaspora_tags_tmp );
438
439
			// Get all the tags and list them all nicely in a row.
440
			foreach ( $diaspora_tags_tmp as $tag ) {
0 ignored issues
show
Bug introduced by
The expression $diaspora_tags_tmp of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
441
				$diaspora_tags[] = '#' . $tag;
442
			}
443
444
			// Add all the found tags.
445
			if ( ! empty( $diaspora_tags ) ) {
446
				$tags_to_add = implode( ' ', $diaspora_tags ) . '<br/>';
447
			}
448
		}
449
450
		/**
451
		 * Filter the tags of the post.
452
		 *
453
		 * @since 2.1.0
454
		 *
455
		 * @param string    $default   The whole string of tags to be outputted.
456
		 * @param array     $tags      All tags that are assigned to this post.
457
		 * @param WP2D_Post $wp2d_post This object, to allow total customisation of the tags output.
458
		 */
459
		return apply_filters( 'wp2d_tags_filter', $tags_to_add, $diaspora_tags, $this );
460
	}
461
462
	/**
463
	 * Get the link to the original post.
464
	 *
465
	 * @since 1.5.0
466
	 *
467
	 * @return string Original post link.
468
	 */
469
	private function get_posted_at_link() {
470
		if ( $this->fullentrylink ) {
471
			$prefix         = esc_html__( 'Originally posted at:', 'wp-to-diaspora' );
472
			$permalink      = get_permalink( $this->ID );
473
			$title          = esc_html__( 'Permalink', 'wp-to-diaspora' );
474
			$posted_at_link = sprintf( '%1$s <a href="%2$s" title="%3$s">%2$s</a>', $prefix, $permalink, $title );
475
476
			/**
477
			 * Filter the "Originally posted at" link at the bottom of the post.
478
			 *
479
			 * @since 1.5.4.1
480
			 *
481
			 * @param string    $default   The whole HTML of the text and link to be outputted.
482
			 * @param WP2D_Post $wp2d_post This object, to allow total customisation of the title.
483
			 * @param string    $prefix    The "Originally posted at:" prefix before the link.
484
			 */
485
			return apply_filters( 'wp2d_posted_at_link_filter', "<p>{$posted_at_link}</p>", $this, $prefix );
486
		}
487
488
		return '';
489
	}
490
491
	/**
492
	 * Save the details of the new diaspora* post to this post's history.
493
	 *
494
	 * @since 1.5.0
495
	 *
496
	 * @param object $response Response from the API containing the diaspora* post details.
497
	 */
498
	private function save_to_history( $response ) {
499
		// Make sure the post history is an array.
500
		if ( empty( $this->post_history ) ) {
501
			$this->post_history = [];
502
		}
503
504
		// Add a new entry to the history.
505
		$this->post_history[] = [
506
			'id'         => $response->id,
507
			'guid'       => $response->guid,
508
			'created_at' => $this->post->post_modified,
509
			'aspects'    => $this->aspects,
510
			'nsfw'       => $response->nsfw,
511
			'post_url'   => $response->permalink,
512
		];
513
514
		update_post_meta( $this->ID, '_wp_to_diaspora_post_history', $this->post_history );
515
	}
516
517
	/**
518
	 * Return URL from [embed] shortcode instead of generated iframe.
519
	 *
520
	 * @since 1.5.0
521
	 * @see   WP_Embed::shortcode()
522
	 *
523
	 * @param mixed  $html The cached HTML result, stored in post meta.
524
	 * @param string $url  The attempted embed URL.
525
	 *
526
	 * @return string URL of the embed.
527
	 */
528
	public function embed_url( $html, $url ) {
529
		return $url;
530
	}
531
532
	/**
533
	 * Removes '[embed]' and '[/embed]' left by embed_url.
534
	 *
535
	 * @since 1.5.0
536
	 *
537
	 * @todo  It would be great to fix it using only one filter.
538
	 *       It's happening because embed filter is being removed by remove_all_filters('the_content') on WP2D_Post::post().
539
	 *
540
	 * @param string $content Content of the post.
541
	 *
542
	 * @return string The content with the embed tags removed.
543
	 */
544
	public function embed_remove( $content ) {
545
		return str_replace( [ '[embed]', '[/embed]' ], [ '<p>', '</p>' ], $content );
546
	}
547
548
	/**
549
	 * Prettify the image caption.
550
	 *
551
	 * @since 1.5.3
552
	 *
553
	 * @param string $caption Caption to be prettified.
554
	 *
555
	 * @return string Prettified image caption.
556
	 */
557
	public function get_img_caption( $caption ) {
558
		$caption = trim( $caption );
559
		if ( '' === $caption ) {
560
			return '';
561
		}
562
563
		/**
564
		 * Filter the image caption to be displayed after images with captions.
565
		 *
566
		 * @since 1.5.3
567
		 *
568
		 * @param string $default The whole HTML of the caption.
569
		 * @param string $caption The caption text.
570
		 */
571
		return apply_filters( 'wp2d_image_caption', "<blockquote>{$caption}</blockquote>", $caption );
572
	}
573
574
	/**
575
	 * Filter the default caption shortcode output.
576
	 *
577
	 * @since 1.5.3
578
	 *
579
	 * @see   img_caption_shortcode()
580
	 *
581
	 * @param string $empty   The caption output. Default empty.
582
	 * @param array  $attr    Attributes of the caption shortcode.
583
	 * @param string $content The image element, possibly wrapped in a hyperlink.
584
	 *
585
	 * @return string The caption shortcode output.
586
	 */
587
	public function custom_img_caption( $empty, $attr, $content ) {
588
		$content = do_shortcode( $content );
589
590
		// If a caption attribute is defined, we'll add it after the image.
591
		if ( isset( $attr['caption'] ) && '' !== $attr['caption'] ) {
592
			$content .= "\n" . $this->get_img_caption( $attr['caption'] );
593
		}
594
595
		return $content;
596
	}
597
598
	/**
599
	 * Create a custom gallery caption output.
600
	 *
601
	 * @since 1.5.3
602
	 *
603
	 * @param array $attr Gallery attributes.
604
	 *
605
	 * @return  string
606
	 */
607
	public function custom_gallery_shortcode( $attr ) {
608
		// Try user value and fall back to default value in WordPress.
609
		$captiontag = $attr['captiontag'] ?? ( current_theme_supports( 'html5', 'gallery' ) ? 'figcaption' : 'dd' );
610
611
		// Let WordPress create the regular gallery.
612
		$gallery = gallery_shortcode( $attr );
613
614
		// Change the content of the captions.
615
		$gallery = preg_replace_callback(
616
			'~(<' . $captiontag . '.*>)(.*)(</' . $captiontag . '>)~mUus',
617
			[ $this, 'custom_gallery_regex_callback' ],
618
			$gallery
619
		);
620
621
		return $gallery;
622
	}
623
624
	/**
625
	 * Change the result of the regex match from custom_gallery_shortcode.
626
	 *
627
	 * @param array $m Regex matches.
628
	 *
629
	 * @return string Prettified gallery image caption.
630
	 */
631
	public function custom_gallery_regex_callback( $m ) {
632
		return $this->get_img_caption( $m[2] );
633
	}
634
635
	/*
636
	 * META BOX
637
	 */
638
639
	/**
640
	 * Adds a meta box to the main column on the enabled Post Types' edit screens.
641
	 *
642
	 * @since 1.5.0
643
	 */
644
	public function add_meta_boxes() {
645
		$options = WP2D_Options::instance();
646
		foreach ( $options->get_option( 'enabled_post_types' ) as $post_type ) {
647
			add_meta_box(
648
				'wp_to_diaspora_meta_box',
649
				'WP to diaspora*',
650
				[ $this, 'meta_box_render' ],
651
				$post_type,
652
				'side',
653
				'high'
654
			);
655
		}
656
	}
657
658
	/**
659
	 * Prints the meta box content.
660
	 *
661
	 * @since 1.5.0
662
	 *
663
	 * @param WP_Post $post The object for the current post.
664
	 */
665
	public function meta_box_render( $post ) {
666
		$this->assign_wp_post( $post );
667
668
		// Add an nonce field so we can check for it later.
669
		wp_nonce_field( 'wp_to_diaspora_meta_box', 'wp_to_diaspora_meta_box_nonce' );
670
671
		// Get the default values to use, but give priority to the meta data already set.
672
		$options = WP2D_Options::instance();
673
674
		// Make sure we have some value for post meta fields.
675
		$this->custom_tags = $this->custom_tags ?: [];
676
677
		// If this post is already published, don't post again to diaspora* by default.
678
		$this->post_to_diaspora = ( $this->post_to_diaspora && 'publish' !== get_post_status( $this->ID ) );
679
		$this->aspects          = $this->aspects ?: [];
680
		$this->services         = $this->services ?: [];
681
682
		// Have we already posted on diaspora*?
683
		$diaspora_post_url = '#';
684
		if ( is_array( $this->post_history ) ) {
685
			$latest_post       = end( $this->post_history );
686
			$diaspora_post_url = $latest_post['post_url'];
687
		}
688
		?>
689
		<p<?php echo '#' === $diaspora_post_url ? ' style="display: none;"' : ''; ?>><a id="diaspora-post-url" href="<?php echo esc_attr( $diaspora_post_url ); ?>" target="_blank"><?php esc_html_e( 'Already posted to diaspora*.', 'wp-to-diaspora' ); ?></a></p>
690
691
		<p><?php $options->post_to_diaspora_render( $this->post_to_diaspora ); ?></p>
692
		<p><?php $options->fullentrylink_render( $this->fullentrylink ); ?></p>
693
		<p><?php $options->display_render( $this->display ); ?></p>
694
		<p><?php $options->tags_to_post_render( $this->tags_to_post ); ?></p>
695
		<p><?php $options->custom_tags_render( $this->custom_tags ); ?></p>
696
		<p><?php $options->aspects_services_render( [ 'aspects', $this->aspects ] ); ?></p>
697
		<p><?php $options->aspects_services_render( [ 'services', $this->services ] ); ?></p>
698
699
		<?php
700
	}
701
702
	/**
703
	 * When the post is saved, save our meta data.
704
	 *
705
	 * @since 1.5.0
706
	 *
707
	 * @param integer $post_id The ID of the post being saved.
708
	 */
709
	public function save_meta_box_data( $post_id ) {
710
		/*
711
		 * We need to verify this came from our screen and with proper authorization,
712
		 * because the save_post action can be triggered at other times.
713
		 */
714
		if ( ! $this->is_safe_to_save() ) {
715
			return;
716
		}
717
718
		/* OK, it's safe for us to save the data now. */
719
720
		// Meta data to save.
721
		$meta_to_save = $_POST['wp_to_diaspora_settings']; // phpcs:ignore
722
		$options      = WP2D_Options::instance();
723
724
		// Checkboxes.
725
		$options->validate_checkboxes( [ 'post_to_diaspora', 'fullentrylink' ], $meta_to_save );
726
727
		// Single Selects.
728
		$options->validate_single_selects( 'display', $meta_to_save );
729
730
		// Multiple Selects.
731
		$options->validate_multi_selects( 'tags_to_post', $meta_to_save );
732
733
		// Save custom tags as array.
734
		$options->validate_tags( $meta_to_save['custom_tags'] );
735
736
		// Clean up the list of aspects. If the list is empty, only use the 'Public' aspect.
737
		$options->validate_aspects_services( $meta_to_save['aspects'], [ 'public' ] );
738
739
		// Clean up the list of services.
740
		$options->validate_aspects_services( $meta_to_save['services'] );
741
742
		// Update the meta data for this post.
743
		update_post_meta( $post_id, '_wp_to_diaspora', $meta_to_save );
744
	}
745
746
	/**
747
	 * Perform all checks to see if we are allowed to save the meta data.
748
	 *
749
	 * @since 1.5.0
750
	 *
751
	 * @return bool If the verification checks have passed.
752
	 */
753
	private function is_safe_to_save() {
754
		// Verify that our nonce is set and  valid.
755
		if ( ! ( isset( $_POST['wp_to_diaspora_meta_box_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_to_diaspora_meta_box_nonce'] ), 'wp_to_diaspora_meta_box' ) ) ) {
756
			return false;
757
		}
758
759
		// If this is an autosave, our form has not been submitted, so we don't want to do anything.
760
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
761
			return false;
762
		}
763
764
		// Check the user's permissions.
765
		$permission = ( isset( $_POST['post_type'] ) && 'page' === $_POST['post_type'] ) ? 'edit_pages' : 'edit_posts';
766
		if ( ! current_user_can( $permission, $this->ID ) ) {
767
			return false;
768
		}
769
770
		// Make real sure that we have some meta data to save.
771
		if ( ! isset( $_POST['wp_to_diaspora_settings'] ) ) {
772
			return false;
773
		}
774
775
		return true;
776
	}
777
778
	/**
779
	 * Add admin notices when a post gets displayed.
780
	 *
781
	 * @since 1.5.0
782
	 *
783
	 * @todo  Ignore post error with AJAX.
784
	 */
785
	public function admin_notices() {
786
		global $post, $pagenow;
787
		if ( ! $post || 'post.php' !== $pagenow ) {
788
			return;
789
		}
790
791
		if ( ( $error = get_post_meta( $post->ID, '_wp_to_diaspora_post_error', true ) ) && is_wp_error( $error ) ) {
792
			// Are we adding a help tab link to this notice?
793
			$help_link = WP2D_Contextual_Help::get_help_tab_quick_link( $error );
794
795
			// This notice will only be shown if posting to diaspora* has failed.
796
			printf(
797
				'<div class="error notice is-dismissible"><p>%1$s %2$s %3$s <a href="%4$s">%5$s</a></p></div>',
798
				esc_html__( 'Failed to post to diaspora*.', 'wp-to-diaspora' ),
799
				esc_html( $error->get_error_message() ),
800
				$help_link,  // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
801
				esc_url( add_query_arg( 'wp2d_ignore_post_error', '' ) ),
802
				esc_html__( 'Ignore', 'wp-to-diaspora' )
803
			);
804
		} elseif ( ( $diaspora_post_history = get_post_meta( $post->ID, '_wp_to_diaspora_post_history', true ) ) && is_array( $diaspora_post_history ) ) {
805
			// Get the latest post from the history.
806
			$latest_post = end( $diaspora_post_history );
807
808
			// Only show if this post is showing a message and the post is a fresh share.
809
			if ( isset( $_GET['message'] ) && $post->post_modified === $latest_post['created_at'] ) { // phpcs:ignore
810
				printf(
811
					'<div class="updated notice is-dismissible"><p>%1$s <a href="%2$s" target="_blank">%3$s</a></p></div>',
812
					esc_html__( 'Successfully posted to diaspora*.', 'wp-to-diaspora' ),
813
					esc_url( $latest_post['post_url'] ),
814
					esc_html__( 'View Post', 'wp-to-diaspora' )
815
				);
816
			}
817
		}
818
	}
819
820
	/**
821
	 * Delete the error post meta data if it gets ignored.
822
	 *
823
	 * @since 1.5.0
824
	 */
825
	public function ignore_post_error() {
826
		// If "Ignore" link has been clicked, delete the post error meta data.
827
		if ( isset( $_GET['wp2d_ignore_post_error'], $_GET['post'] ) ) { // phpcs:ignore
828
			delete_post_meta( absint( $_GET['post'] ), '_wp_to_diaspora_post_error' ); // phpcs:ignore
829
		}
830
	}
831
832
	/**
833
	 * Get latest diaspora* share of this post.
834
	 *
835
	 * @since 3.0.0
836
	 */
837
	public function get_post_history_callback() {
838 View Code Duplication
		if ( ! check_ajax_referer( 'wp2d', 'nonce', false ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
839
			wp_send_json_error( [
840
				'message' => 'WP2D: ' . __( 'AJAX Nonce failure.', 'wp-to-diaspora' ),
841
			] );
842
		}
843
844
		$post_id = sanitize_key( $_REQUEST['post_id'] ?? '' );
845
		if ( ! is_numeric( $post_id ) ) {
846
			return;
847
		}
848
849
		if ( $error = get_post_meta( $post_id, '_wp_to_diaspora_post_error', true ) ) {
850
			// This notice will only be shown if posting to diaspora* has failed.
851
			wp_send_json_error( [
852
				'message' => esc_html__( 'Failed to post to diaspora*.', 'wp-to-diaspora' ) . ' - ' . esc_html( $error ),
853
			] );
854
		}
855
856
		if ( ( $diaspora_post_history = get_post_meta( $post_id, '_wp_to_diaspora_post_history', true ) ) && is_array( $diaspora_post_history ) ) {
857
			// Get the latest post from the history.
858
			$latest_post = end( $diaspora_post_history );
859
860
			// Only show if this post is a fresh share.
861
			if ( get_post( $post_id )->post_modified === $latest_post['created_at'] ) { // phpcs:ignore
862
				wp_send_json_success( [
863
					'message' => esc_html__( 'Successfully posted to diaspora*.', 'wp-to-diaspora' ),
864
					'action'  => [
865
						'label' => esc_html__( 'View Post', 'wp-to-diaspora' ),
866
						'url'   => esc_url( $latest_post['post_url'] ),
867
					],
868
				] );
869
			}
870
		}
871
	}
872
}
873