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.

hm-post-repeat.php ➔ admin_table_views_links()   B
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 18
nc 3
nop 1
dl 0
loc 34
rs 8.8571
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
		'fortnightly' => array( 'interval' => '2 weeks',  'display' => __( 'Fortnightly',  'hm-post-repeat' ) ),
330
		'monthly'     => array( 'interval' => '1 month', 'display'  => __( 'Monthly',      'hm-post-repeat' ) ),
331
	) );
332
333
	foreach ( $schedules as $slug => &$schedule ) {
334
		$schedule['slug'] = $slug;
335
	}
336
337
	return $schedules;
338
339
}
340
341
/**
342
 * Get the repeating schedule of the given post_id.
343
 *
344
 * @param int $post_id The id of the post you want to check.
345
 * @return array|null The schedule to repeat by, or null if invalid.
346
 */
347
function get_repeating_schedule( $post_id ) {
348
349
	if ( ! is_repeating_post( $post_id ) ) {
350
		return;
351
	}
352
353
	$repeating_schedule = get_post_meta( $post_id, 'hm-post-repeat', true );
354
	$schedules = get_repeating_schedules();
355
356
	// Backwards compatibility with 0.3 when we only supported weekly
357
	if ( '1' === $repeating_schedule ) {
358
		$repeating_schedule = 'weekly';
359
	}
360
361
	if ( array_key_exists( $repeating_schedule, $schedules ) ) {
362
		return $schedules[ $repeating_schedule ];
363
	}
364
365
}
366
367
/**
368
 * Check whether a given post_id is a repeating post.
369
 *
370
 * A repeating post is defined as the original post that was set to repeat.
371
 *
372
 * @param int $post_id The id of the post you want to check.
373
 * @return bool Whether the passed post_id is a repeating post or not.
374
 */
375
function is_repeating_post( $post_id ) {
376
377
	// We check $_POST data so that this function works inside a `save_post` hook when the post_meta hasn't yet been saved
378
	if ( isset( $_POST['hm-post-repeat'] ) && isset( $_POST['ID'] ) && $_POST['ID'] === $post_id ) {
379
		return true;
380
	}
381
382
	if ( get_post_meta( $post_id, 'hm-post-repeat', true ) ) {
383
		return true;
384
	}
385
386
	return false;
387
388
}
389
390
/**
391
 * Check whether a given post_id is a repeat post.
392
 *
393
 * A repeat post is defined as any post which is a repeat of the original repeating post.
394
 *
395
 * @param int $post_id The id of the post you want to check.
396
 * @return bool Whether the passed post_id is a repeat post or not.
397
 */
398
function is_repeat_post( $post_id ) {
399
400
	$post_parent = get_post_field( 'post_parent', $post_id );
401
402
	if ( $post_parent && get_post_meta( $post_parent, 'hm-post-repeat', true ) ) {
403
		return true;
404
	}
405
406
	return false;
407
408
}
409
410
/**
411
 * Get the next scheduled repeat post
412
 *
413
 * @param int $post_id The id of a repeat or repeating post
414
 * @return Int|Bool Return the ID of the next repeat post_id or false if it can't find one
415
 */
416
function get_next_scheduled_repeat_post( $post_id ) {
417
418
	$post = get_post( get_repeating_post( $post_id ) );
419
420
	$repeat_posts = get_posts( array( 'post_status' => 'future', 'post_parent' => $post->ID ) );
421
422
	if ( isset( $repeat_posts[0] ) ) {
423
	 	return $repeat_posts[0];
424
	}
425
426
	return false;
427
428
}
429
430
/**
431
 * Get the next scheduled repeat post
432
 *
433
 * @param int $post_id The id of a repeat or repeating post
434
 * @return Int|Bool Return the original repeating post_id or false if it can't find it
435
 */
436
function get_repeating_post( $post_id ) {
437
438
	$original_post_id = false;
439
440
	// Are we publishing a repeat post
441
	if ( is_repeat_post( $post_id ) ) {
442
		$original_post_id = get_post( $post_id )->post_parent;
443
	}
444
445
	// Or the original
446
	elseif ( is_repeating_post( $post_id ) ) {
447
		$original_post_id = $post_id;
448
	}
449
450
	return $original_post_id;
451
452
}
453
454
/**
455
 * Adds admin table view link per available repeat type.
456
 * So that only all posts of specific repeat type are displayed.
457
 *
458
 * Added at the end of link list for All | Mine | Published | Scheduled | Drafts
459
 *
460
 * @param array $views An array of available list table views.
461
 *
462
 * @return array Available list of table views with custom added views per repeat type.
463
 */
464
function admin_table_views_links( $views ) {
465
466
	// Add status link for each repeat type.
467
	foreach ( get_available_repeat_types() as $repeat_type => $repeat_desc ) {
468
469
		$url_args = array(
470
			'post_type'      => get_current_screen()->post_type,
471
			'hm-post-repeat' => $repeat_type,
472
		);
473
474
		// Custom WP_Query to get posts count of repeat type.
475
		$repeat_type_query = new \WP_Query( get_repeat_type_query_params( $repeat_type ) );
476
477
		$link_label = sprintf(
478
				'%s <span class="count">(%s)</span>',
479
			esc_html( $repeat_desc ),
480
			esc_html( number_format_i18n( $repeat_type_query->post_count ) )
481
		);
482
483
		// Add current class to the link to highlight it when it's selected.
484
		$class_html = ( get_repeat_type_url_param() === $repeat_type ) ? ' class="current"' : '';
485
486
		$link_html = sprintf(
487
			'<a href="%s"%s>%s</a>',
488
			esc_url( add_query_arg( $url_args, 'edit.php' ) ),
489
			$class_html, // html - hardcoded, no need to escape.
490
			$link_label  // html - escaped earlier in the code.
491
		);
492
493
		$views[ $repeat_type ] = $link_html;
494
	}
495
496
	return $views;
497
}
498
499
/**
500
 * Customizes main admin query to get posts of specified repeat type
501
 * to be displayed in the admin table.
502
 *
503
 * @param \WP_Query $wp_query Main admin query.
504
 *
505
 * @return mixed Main admin query with edited params to get posts of specified repeat type.
506
 */
507
function admin_table_repeat_type_posts_query( $wp_query ) {
508
509
	// Stop - if not admin or not main query (there are secondary WP_Query for counting posts for view links, etc).
510
	if ( ! is_admin() || ! $wp_query->is_main_query() ) {
511
		return $wp_query;
512
	}
513
514
	// Get URL query param for repeat type and check it's valid.
515
	$repeat_type = get_repeat_type_url_param();
516
517
	if ( ! $repeat_type || ! is_allowed_repeat_type( $repeat_type ) ) {
518
		return $wp_query;
519
	}
520
521
	// Add a table view link per each repeat type.
522
	foreach ( get_repeat_type_query_params( $repeat_type ) as $key => $value ) {
523
		$wp_query->set( $key, $value );
524
	}
525
526
	return $wp_query;
527
}
528
529
/**
530
 * Returns array of custom WP_Query params to get posts of specified repeat type.
531
 * Works for all CPT that support Repeatable Posts.
532
 *
533
 * @param string $repeat_type Repeat type of posts to get.
534
 *
535
 * @return array Array of custom WP_Query params.
536
 */
537
function get_repeat_type_query_params( $repeat_type ) {
538
539
	$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...
540
541
	// Construct custom query to get posts of specified repeat type.
542
	$query['meta_query'] = array(
543
			array(
544
					'key'     => 'hm-post-repeat',
545
					'compare' => 'EXISTS',
546
			),
547
	);
548
549
	if ( $repeat_type === 'repeat' ) {
550
		$query['post_parent__not_in'] = array( 0 );
551
552
	} elseif ( $repeat_type === 'repeating' ) {
553
		$query['post_parent__in'] = array( 0 );
554
	}
555
556
	return $query;
557
}
558
559
/**
560
 * Get URL query param for the repeat type of posts being displayed
561
 * in the admin post table.
562
 *
563
 * @return string Sanitized string of repeat type being displayed.
564
 */
565
function get_repeat_type_url_param() {
566
	return isset( $_GET['hm-post-repeat'] ) ? sanitize_text_field( $_GET['hm-post-repeat'] ) : '';
567
}
568
569
/**
570
 * Return available repeat types, i.e. repeating or repeat.
571
 *
572
 * @return array Available repeat types.
573
 */
574
function get_available_repeat_types() {
575
	return array(
576
		'repeating' => __( 'Repeating', 'hm-post-repeat' ),
577
		'repeat'    => __( 'Repeat', 'hm-post-repeat' ),
578
	);
579
}
580
581
/**
582
 * Check if a repeat type is valid.
583
 *
584
 * @param string $repeat_type Repeat type to check.
585
 *
586
 * @return bool True if repeat type is valid,
587
 *              False otherwise.
588
 */
589
function is_allowed_repeat_type( $repeat_type ) {
590
	return in_array( $repeat_type, array_keys( get_available_repeat_types() ) );
591
}
592