GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#40)
by
unknown
01:38
created

hm-post-repeat.php ➔ save_post_repeating_status()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 9
nc 12
nop 2
dl 0
loc 20
rs 8.2222
c 0
b 0
f 0
1
<?php
2
3
/*
4
Plugin Name: Repeatable Posts
5
Description: Designate a post as repeatable and it'll be copied and re-published on your chosen interval.
6
Author: Human Made Limited
7
Author URI: http://hmn.md/
8
Version: 0.4
9
License: GPL-2.0+
10
License URI: http://www.gnu.org/licenses/gpl-2.0.txt
11
Text Domain: hm-post-repeat
12
Domain Path: /languages
13
*/
14
15
/*
16
Copyright Human Made Limited  (email : [email protected])
17
18
This program is free software; you can redistribute it and/or modify
19
it under the terms of the GNU General Public License as published by
20
the Free Software Foundation; either version 2 of the License, or
21
(at your option) any later version.
22
23
This program is distributed in the hope that it will be useful,
24
but WITHOUT ANY WARRANTY; without even the implied warranty of
25
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
GNU General Public License for more details.
27
28
You should have received a copy of the GNU General Public License
29
along with this program; if not, write to the Free Software
30
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
31
*/
32
33
namespace HM\Post_Repeat;
34
35
/**
36
 * Setup the actions and filters required by this class.
37
 */
38
add_action( 'post_submitbox_misc_actions', __NAMESPACE__ . '\publish_box_ui' );
39
add_action( 'save_post',                   __NAMESPACE__ . '\save_post_repeating_status', 10 );
40
add_action( 'save_post',                   __NAMESPACE__ . '\create_next_repeat_post', 11 );
41
add_action( 'admin_enqueue_scripts',       __NAMESPACE__ . '\enqueue_scripts' );
42
add_filter( 'display_post_states',         __NAMESPACE__ . '\admin_table_row_post_states', 10, 2 );
43
44
// Add repeat type table view links to admin screen for each CPT that supports Repeatable Posts.
45
add_action( 'init', function() {
46
	foreach ( repeating_post_types() as $post_type ) {
47
		add_filter( "views_edit-{$post_type}", __NAMESPACE__ . '\admin_table_views_links' );
48
	}
49
} );
50
51
// Display only Repeatable Posts in admin table view for registered view links.
52
add_filter( 'pre_get_posts', __NAMESPACE__ . '\admin_table_repeat_type_posts_query' );
53
54
/**
55
 * Enqueue the scripts and styles that are needed by this plugin.
56
 */
57
function enqueue_scripts( $hook ) {
58
59
	// Ensure we only load them on the edit post and add new post admin screens
60
	if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ) ) ) {
61
		return;
62
	}
63
64
	$plugin_data = get_plugin_data( __FILE__ );
65
	$plugin_dir_url = plugin_dir_url( __FILE__ );
66
67
	wp_enqueue_script( 'hm-post-repeat', $plugin_dir_url . 'hm-post-repeat.js', 'jquery', $plugin_data['Version'], true );
68
	wp_enqueue_style( 'hm-post-repeat', $plugin_dir_url . 'hm-post-repeat.css', array(), $plugin_data['Version'] );
69
70
}
71
72
/**
73
 * Output the Post Repeat UI that is shown in the Publish post meta box.
74
 *
75
 * The UI varies depending on whether the post is the original repeating post
76
 * or itself a repeat.
77
 */
78
function publish_box_ui() {
79
80
	if ( ! in_array( get_post_type(), repeating_post_types() ) ) {
81
		return;
82
	} ?>
83
84
	<div class="misc-pub-section misc-pub-hm-post-repeat">
85
86
		<span class="dashicons dashicons-controls-repeat"></span>
87
88
		<?php esc_html_e( 'Repeat:', 'hm-post-repeat' ); ?>
89
90
		<?php if ( is_repeat_post( get_the_id() ) ) : ?>
91
92
			<strong><?php printf( esc_html__( 'Repeat of %s', 'hm-post-repeat' ), '<a href="' . esc_url( get_edit_post_link( get_post()->post_parent ) ) . '">' . esc_html( get_the_title( get_post_field( 'post_parent', get_the_id() ) ) ) . '</a>' ); ?></strong>
93
94
		<?php else : ?>
95
96
			<?php $repeating_schedule = get_repeating_schedule( get_the_id() ); ?>
97
			<?php $is_repeating_post = is_repeating_post( get_the_id() ) && isset( $repeating_schedule ); ?>
98
99
			<strong><?php echo ! $is_repeating_post ? esc_html__( 'No', 'hm-post-repeat' ) : esc_html( $repeating_schedule['display'] ); ?></strong>
100
101
			<a href="#hm-post-repeat" class="edit-hm-post-repeat hide-if-no-js"><span aria-hidden="true"><?php esc_html_e( 'Edit', 'hm-post-repeat' ); ?></span> <span class="screen-reader-text"><?php esc_html_e( 'Edit Repeat Settings', 'hm-post-repeat' ); ?></span></a>
102
103
			<span class="hide-if-js" id="hm-post-repeat">
104
105
				<select name="hm-post-repeat">
106
					<option<?php selected( ! $is_repeating_post ); ?> value="no"><?php esc_html_e( 'No', 'hm-post-repeat' ); ?></option>
107
					<?php foreach ( get_repeating_schedules() as $schedule_slug => $schedule ) : ?>
108
						<option<?php selected( $is_repeating_post && $schedule_slug === $repeating_schedule['slug'] ); ?> value="<?php echo esc_attr( $schedule_slug ); ?>"><?php echo esc_html( $schedule['display'] ); ?></option>
109
					<?php endforeach; ?>
110
				</select>
111
112
				<a href="#hm-post-repeat" class="save-post-hm-post-repeat hide-if-no-js button"><?php esc_html_e( 'OK', 'hm-post-repeat' ); ?></a>
113
114
			</span>
115
116
		<?php endif; ?>
117
118
	</div>
119
120
<?php }
121
122
/**
123
 * Add some custom post states to cover repeat and repeating posts.
124
 *
125
 * By default post states are displayed on the Edit Post screen in bold after the post title.
126
 *
127
 * @param array   $post_states The original array of post states.
128
 * @param WP_Post $post        The post object to get / return the states.
129
 * @return array The array of post states with ours added.
130
 */
131
function admin_table_row_post_states( $post_states, $post ) {
132
133
	if ( is_repeating_post( $post->ID ) ) {
134
135
		// If the schedule has been removed since publishing, let the user know.
136
		if ( get_repeating_schedule( $post->ID ) ) {
137
			$post_states['hm-post-repeat'] = __( 'Repeating', 'hm-post-repeat' );
138
		} else {
139
			$post_states['hm-post-repeat'] = __( 'Invalid Repeating Schedule', 'hm-post-repeat' );
140
		}
141
142
	}
143
144
	if ( is_repeat_post( $post->ID ) ) {
145
		$post_states['hm-post-repeat'] = __( 'Repeat', 'hm-post-repeat' );
146
	}
147
148
	return $post_states;
149
}
150
151
/**
152
 * Save the repeating status to post meta.
153
 *
154
 * Hooked into `save_post`. When saving a post that has been set to repeat we save a post meta entry.
155
 *
156
 * @param int    $post_id             The ID of the post.
157
 * @param string $post_repeat_setting Used to manually set the repeating schedule from tests.
158
 */
159
function save_post_repeating_status( $post_id = null, $post_repeat_setting = null ) {
160
161
	if ( is_null( $post_repeat_setting ) ) {
162
		$post_repeat_setting = isset( $_POST['hm-post-repeat'] ) ? sanitize_text_field( $_POST['hm-post-repeat'] ) : '';
163
	}
164
165
	if ( ! in_array( get_post_type( $post_id ), repeating_post_types() ) || empty( $post_repeat_setting ) ) {
166
		return;
167
	}
168
169
	if ( 'no' === $post_repeat_setting ) {
170
		delete_post_meta( $post_id, 'hm-post-repeat' );
171
	}
172
173
	// Make sure we have a valid schedule.
174
	elseif ( in_array( $post_repeat_setting, array_keys( get_repeating_schedules() ) ) ) {
175
		update_post_meta( $post_id, 'hm-post-repeat', $post_repeat_setting );
176
	}
177
178
}
179
180
181
/**
182
 * Create the next repeat post when the last one is published.
183
 *
184
 * When a repeat post (or the original) is published we copy and schedule a new post
185
 * to publish on the correct interval. That way the next repeat post is always ready to go.
186
 * This is hooked into publish_post so that the repeat post is only created when the original
187
 * is published.
188
 *
189
 * @param int $post_id The ID of the post.
190
 */
191
function create_next_repeat_post( $post_id ) {
192
193
	if ( ! in_array( get_post_type( $post_id ), repeating_post_types() ) ) {
194
		return false;
195
	}
196
197
	if ( 'publish' !== get_post_status( $post_id ) ) {
198
		return false;
199
	}
200
201
	$original_post_id = get_repeating_post( $post_id );
202
203
	// Bail if we're not publishing a repeat(ing) post
204
	if ( ! $original_post_id ) {
205
		return false;
206
	}
207
208
	$original_post = get_post( $original_post_id, ARRAY_A );
209
210
	// If there is already a repeat post scheduled don't create another one
211
	if ( get_next_scheduled_repeat_post( $original_post['ID'] ) ) {
212
		return false;
213
	}
214
215
	// Bail if the saved schedule doesn't exist
216
	$repeating_schedule = get_repeating_schedule( $original_post['ID'] );
217
218
	if ( ! $repeating_schedule ) {
219
		return false;
220
	}
221
222
	// Bail if the original post isn't already published
223
	if ( 'publish' !== $original_post['post_status'] ) {
224
		return false;
225
	}
226
227
	$next_post = $original_post;
228
229
	// Create the repeat post as a copy of the original, but ignore some fields
230
	unset( $next_post['ID'] );
231
	unset( $next_post['guid'] );
232
	unset( $next_post['post_date_gmt'] );
233
	unset( $next_post['post_modified'] );
234
	unset( $next_post['post_modified_gmt'] );
235
236
	// We set the post_parent to the original post_id, so they're related
237
	$next_post['post_parent'] = $original_post['ID'];
238
239
	// Set the next post to publish in the future
240
	$next_post['post_status'] = 'future';
241
242
	// Use the date of the current post being saved as the base
243
	$next_post['post_date'] = date( 'Y-m-d H:i:s', strtotime( get_post_field( 'post_date', $post_id ) . ' + ' . $repeating_schedule['interval'] ) );
244
245
	// Make sure the next post will be in the future
246
	if ( strtotime( $next_post['post_date'] ) <= time() ) {
247
		return false;
248
	}
249
250
	/**
251
	 * Use this filter to modify the scheduled post before it gets stored.
252
	 *
253
	 * @param array $next_post          The post data about to be saved.
254
	 * @param array $repeating_schedule Repeating schedule info.
255
	 * @param array $original_post      The original repeating post.
256
	 */
257
	$next_post = apply_filters( 'hm_post_repeat_edit_repeat_post', $next_post, $repeating_schedule, $original_post );
258
259
	// All checks done, get that post scheduled!
260
	$next_post_id = wp_insert_post( wp_slash( $next_post ), true );
261
262
	if ( is_wp_error( $next_post_id ) ) {
263
		return false;
264
	}
265
266
	// Mirror any post_meta
267
	$post_meta = get_post_meta( $original_post['ID'] );
268
269
	if ( $post_meta  ) {
270
271
		// Ignore some internal meta fields
272
		unset( $post_meta['_edit_lock'] );
273
		unset( $post_meta['_edit_last'] );
274
275
		// Don't copy the post repeat meta as only the original post should have that
276
		unset( $post_meta['hm-post-repeat'] );
277
278
		foreach ( $post_meta as $key => $values ) {
279
			foreach ( $values as $value ) {
280
				add_post_meta( $next_post_id, $key, maybe_unserialize( $value ) );
281
			}
282
		}
283
	}
284
285
	// Mirror any term relationships
286
	$taxonomies = get_object_taxonomies( $original_post['post_type'] );
287
288
	foreach ( $taxonomies as $taxonomy ) {
289
		wp_set_object_terms( $next_post_id, wp_list_pluck( wp_get_object_terms( $original_post['ID'], $taxonomy ), 'slug' ), $taxonomy );
290
	}
291
292
	return $next_post_id;
293
294
}
295
296
/**
297
 * The post types the feature is enabled on
298
 *
299
 * By default only posts have the feature enabled but others can be added with the `hm_post_repeat_post_types` filter.
300
 *
301
 * @return array An array of post types
302
 */
303
function repeating_post_types() {
304
305
	/**
306
	 * Enable support for additional post types.
307
	 *
308
	 * @param string[] $post_types Post type slugs.
309
	 */
310
	return apply_filters( 'hm_post_repeat_post_types', array( 'post' ) );
311
312
}
313
314
/**
315
 * All available repeat schedules.
316
 *
317
 * @return array An array of all available repeat schedules
318
 */
319
function get_repeating_schedules() {
320
321
	/**
322
	 * Enable support for additional schedules.
323
	 *
324
	 * @param array[] $schedules Schedule array items.
325
	 */
326
	$schedules = apply_filters( 'hm_post_repeat_schedules', array(
327
		'daily'   => array( 'interval' => '1 day',   'display' => __( 'Daily',   'hm-post-repeat' ) ),
328
		'weekly'  => array( 'interval' => '1 week',  'display' => __( 'Weekly',  'hm-post-repeat' ) ),
329
		'monthly' => array( 'interval' => '1 month', 'display' => __( 'Monthly', 'hm-post-repeat' ) ),
330
	) );
331
332
	foreach ( $schedules as $slug => &$schedule ) {
333
		$schedule['slug'] = $slug;
334
	}
335
336
	return $schedules;
337
338
}
339
340
/**
341
 * Get the repeating schedule of the given post_id.
342
 *
343
 * @param int $post_id The id of the post you want to check.
344
 * @return array|null The schedule to repeat by, or null if invalid.
345
 */
346
function get_repeating_schedule( $post_id ) {
347
348
	if ( ! is_repeating_post( $post_id ) ) {
349
		return;
350
	}
351
352
	$repeating_schedule = get_post_meta( $post_id, 'hm-post-repeat', true );
353
	$schedules = get_repeating_schedules();
354
355
	// Backwards compatibility with 0.3 when we only supported weekly
356
	if ( '1' === $repeating_schedule ) {
357
		$repeating_schedule = 'weekly';
358
	}
359
360
	if ( array_key_exists( $repeating_schedule, $schedules ) ) {
361
		return $schedules[ $repeating_schedule ];
362
	}
363
364
}
365
366
/**
367
 * Check whether a given post_id is a repeating post.
368
 *
369
 * A repeating post is defined as the original post that was set to repeat.
370
 *
371
 * @param int $post_id The id of the post you want to check.
372
 * @return bool Whether the passed post_id is a repeating post or not.
373
 */
374
function is_repeating_post( $post_id ) {
375
376
	// We check $_POST data so that this function works inside a `save_post` hook when the post_meta hasn't yet been saved
377
	if ( isset( $_POST['hm-post-repeat'] ) && isset( $_POST['ID'] ) && $_POST['ID'] === $post_id ) {
378
		return true;
379
	}
380
381
	if ( get_post_meta( $post_id, 'hm-post-repeat', true ) ) {
382
		return true;
383
	}
384
385
	return false;
386
387
}
388
389
/**
390
 * Check whether a given post_id is a repeat post.
391
 *
392
 * A repeat post is defined as any post which is a repeat of the original repeating post.
393
 *
394
 * @param int $post_id The id of the post you want to check.
395
 * @return bool Whether the passed post_id is a repeat post or not.
396
 */
397
function is_repeat_post( $post_id ) {
398
399
	$post_parent = get_post_field( 'post_parent', $post_id );
400
401
	if ( $post_parent && get_post_meta( $post_parent, 'hm-post-repeat', true ) ) {
402
		return true;
403
	}
404
405
	return false;
406
407
}
408
409
/**
410
 * Get the next scheduled repeat post
411
 *
412
 * @param int $post_id The id of a repeat or repeating post
413
 * @return Int|Bool Return the ID of the next repeat post_id or false if it can't find one
414
 */
415
function get_next_scheduled_repeat_post( $post_id ) {
416
417
	$post = get_post( get_repeating_post( $post_id ) );
418
419
	$repeat_posts = get_posts( array( 'post_status' => 'future', 'post_parent' => $post->ID ) );
420
421
	if ( isset( $repeat_posts[0] ) ) {
422
	 	return $repeat_posts[0];
423
	}
424
425
	return false;
426
427
}
428
429
/**
430
 * Get the next scheduled repeat post
431
 *
432
 * @param int $post_id The id of a repeat or repeating post
433
 * @return Int|Bool Return the original repeating post_id or false if it can't find it
434
 */
435
function get_repeating_post( $post_id ) {
436
437
	$original_post_id = false;
438
439
	// Are we publishing a repeat post
440
	if ( is_repeat_post( $post_id ) ) {
441
		$original_post_id = get_post( $post_id )->post_parent;
442
	}
443
444
	// Or the original
445
	elseif ( is_repeating_post( $post_id ) ) {
446
		$original_post_id = $post_id;
447
	}
448
449
	return $original_post_id;
450
451
}
452
453
/**
454
 * Adds admin table view link per available repeat type.
455
 * So that only all posts of specific repeat type are displayed.
456
 *
457
 * Added at the end of link list for All | Mine | Published | Scheduled | Drafts
458
 *
459
 * @param array $views An array of available list table views.
460
 *
461
 * @return array Available list of table views with custom added views per repeat type.
462
 */
463
function admin_table_views_links( $views ) {
464
465
	// Add status link for each repeat type.
466
	foreach ( get_available_repeat_types() as $repeat_type => $repeat_desc ) {
467
468
		$url_args = array(
469
			'post_type'      => get_current_screen()->post_type,
470
			'hm-post-repeat' => $repeat_type,
471
		);
472
473
		// Custom WP_Query to get posts count of repeat type.
474
		$repeat_type_query = new \WP_Query( get_repeat_type_query_params( $repeat_type ) );
475
476
		$link_label = sprintf(
477
				'%s <span class="count">(%s)</span>',
478
			esc_html( $repeat_desc ),
479
			esc_html( number_format_i18n( $repeat_type_query->post_count ) )
480
		);
481
482
		// Add current class to the link to highlight it when it's selected.
483
		$class_html = ( get_repeat_type_url_param() === $repeat_type ) ? ' class="current"' : '';
484
485
		$link_html = sprintf(
486
			'<a href="%s"%s>%s</a>',
487
			esc_url( add_query_arg( $url_args, 'edit.php' ) ),
488
			$class_html, // html - hardcoded, no need to escape.
489
			$link_label  // html - escaped earlier in the code.
490
		);
491
492
		$views[ $repeat_type ] = $link_html;
493
	}
494
495
	return $views;
496
}
497
498
/**
499
 * Customizes main admin query to get posts of specified repeat type
500
 * to be displayed in the admin table.
501
 *
502
 * @param \WP_Query $wp_query Main admin query.
503
 *
504
 * @return mixed Main admin query with edited params to get posts of specified repeat type.
505
 */
506
function admin_table_repeat_type_posts_query( $wp_query ) {
507
508
	// Stop - if not admin or not main query (there are secondary WP_Query for counting posts for view links, etc).
509
	if ( ! is_admin() || ! $wp_query->is_main_query() ) {
510
		return $wp_query;
511
	}
512
513
	// Get URL query param for repeat type and check it's valid.
514
	$repeat_type = get_repeat_type_url_param();
515
516
	if ( ! $repeat_type || ! is_allowed_repeat_type( $repeat_type ) ) {
517
		return $wp_query;
518
	}
519
520
	// Add a table view link per each repeat type.
521
	foreach ( get_repeat_type_query_params( $repeat_type ) as $key => $value ) {
522
		$wp_query->set( $key, $value );
523
	}
524
525
	return $wp_query;
526
}
527
528
/**
529
 * Returns array of custom WP_Query params to get posts of specified repeat type.
530
 * Works for all CPT that support Repeatable Posts.
531
 *
532
 * @param string $repeat_type Repeat type of posts to get.
533
 *
534
 * @return array Array of custom WP_Query params.
535
 */
536
function get_repeat_type_query_params( $repeat_type ) {
537
538
	$query['post_type'] = get_current_screen()->post_type;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
539
540
	// Construct custom query to get posts of specified repeat type.
541
	$query['meta_query'] = array(
542
			array(
543
					'key'     => 'hm-post-repeat',
544
					'compare' => 'EXISTS',
545
			),
546
	);
547
548
	if ( $repeat_type === 'repeat' ) {
549
		$query['post_parent__not_in'] = array( 0 );
550
551
	} elseif ( $repeat_type === 'repeating' ) {
552
		$query['post_parent__in'] = array( 0 );
553
	}
554
555
	return $query;
556
}
557
558
/**
559
 * Get URL query param for the repeat type of posts being displayed
560
 * in the admin post table.
561
 *
562
 * @return string Sanitized string of repeat type being displayed.
563
 */
564
function get_repeat_type_url_param() {
565
	return isset( $_GET['hm-post-repeat'] ) ? sanitize_text_field( $_GET['hm-post-repeat'] ) : '';
566
}
567
568
/**
569
 * Return available repeat types, i.e. repeating or repeat.
570
 *
571
 * @return array Available repeat types.
572
 */
573
function get_available_repeat_types() {
574
	return array(
575
		'repeating' => __( 'Repeating', 'hm-post-repeat' ),
576
		'repeat'    => __( 'Repeat', 'hm-post-repeat' ),
577
	);
578
}
579
580
/**
581
 * Check if a repeat type is valid.
582
 *
583
 * @param string $repeat_type Repeat type to check.
584
 *
585
 * @return bool True if repeat type is valid,
586
 *              False otherwise.
587
 */
588
function is_allowed_repeat_type( $repeat_type ) {
589
	return in_array( $repeat_type, array_keys( get_available_repeat_types() ) );
590
}
591