Completed
Push — add/copy-a-post ( 259415...f9307d )
by Kirk
24:11 queued 17:30
created

Jetpack_Copy_Post   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 203
rs 10
c 0
b 0
f 0
wmc 20
lcom 1
cbo 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 5
A update_post_data() 0 25 5
A update_content() 0 14 1
A update_featured_image() 0 4 1
A update_post_format() 0 4 1
A update_likes_sharing() 0 10 1
A filter_title() 0 3 1
A filter_content() 0 3 1
A filter_excerpt() 0 3 1
A user_can_access_post() 0 3 1
A add_row_action() 0 32 2
1
<?php
2
/**
3
 * Module Name: Copy Post
4
 * Module Description: Copy an existing post's content into a new post.
5
 * Jumpstart Description: Copy an existing post's content into a new post.
6
 * Sort Order: 15
7
 * First Introduced: 7.0
8
 * Requires Connection: No
9
 * Auto Activate: No
10
 * Module Tags: Writing
11
 * Feature: Writing
12
 * Additional Search Queries: copy, duplicate
13
 */
14
15
/**
16
 * Copy Post class.
17
 */
18
class Jetpack_Copy_Post {
19
	/**
20
	 * Jetpack_Copy_Post_By_Param constructor.
21
	 * Add row actions to post/page/CPT listing screens.
22
	 * Process any `?copy` param if on a create new post/page/CPT screen.
23
	 *
24
	 * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
25
	 */
26
	public function __construct() {
27
		if ( 'edit.php' === $GLOBALS['pagenow'] ) {
28
			add_filter( 'post_row_actions', array( $this, 'add_row_action' ), 10, 2 );
29
			add_filter( 'page_row_actions', array( $this, 'add_row_action' ), 10, 2 );
30
			return;
31
		}
32
33
		if ( ! empty( $_GET['jetpack-copy'] ) &&
34
			wp_verify_nonce( $_GET['_wpnonce'], 'jetpack-copy-post' ) &&
35
			'post-new.php' === $GLOBALS['pagenow'] ) {
36
			add_action( 'wp_insert_post', array( $this, 'update_post_data' ), 10, 3 );
37
		}
38
	}
39
40
	/**
41
	 * Update the new (target) post data with the source post data.
42
	 *
43
	 * @param int     $target_post_id Target post ID.
44
	 * @param WP_Post $post           Target post object (not used).
45
	 * @param bool    $update         Whether this is an existing post being updated or not.
46
	 * @return void
47
	 */
48
	public function update_post_data( $target_post_id, $post, $update ) {
49
		// This `$update` check avoids infinite loops of trying to update our updated post.
50
		if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'jetpack-copy-post' ) || $update ) {
51
			return;
52
		}
53
54
		$source_post = get_post( $_GET['jetpack-copy'] );
55
		if ( ! $source_post || ! $this->user_can_access_post( $source_post->ID ) ) {
56
			return;
57
		}
58
59
		$update_results = array(
60
			'update_content'        => $this->update_content( $source_post, $target_post_id ),
61
			'update_featured_image' => $this->update_featured_image( $source_post, $target_post_id ),
62
			'update_post_format'    => $this->update_post_format( $source_post, $target_post_id ),
63
			'update_likes_sharing'  => $this->update_likes_sharing( $source_post, $target_post_id ),
64
		);
65
66
		// Required to satify get_default_post_to_edit(), which has these filters after post creation.
67
		add_filter( 'default_title', array( $this, 'filter_title' ), 10, 2 );
68
		add_filter( 'default_content', array( $this, 'filter_content' ), 10, 2 );
69
		add_filter( 'default_excerpt', array( $this, 'filter_excerpt' ), 10, 2 );
70
71
		do_action( 'jetpack_copy_post', $source_post, $target_post_id, $update_results );
72
	}
73
74
	/**
75
	 * Determine if the current user has access to the source post.
76
	 *
77
	 * @param int $post_id Source post ID (the post being copied).
78
	 * @return bool True if user has the meta cap of `read_post` for the given post ID, false otherwise.
79
	 */
80
	protected function user_can_access_post( $post_id ) {
81
		return current_user_can( 'read_post', $post_id );
82
	}
83
84
	/**
85
	 * Update the target post's title, content, excerpt, categories, and tags.
86
	 *
87
	 * @param WP_Post $source_post Post object to be copied.
88
	 * @param int     $target_post_id Target post ID.
89
	 * @return int    0 on failure, or the updated post ID on success.
90
	 */
91
	protected function update_content( $source_post, $target_post_id ) {
92
		$data = array(
93
			'ID'             => $target_post_id,
94
			'post_title'     => $source_post->post_title,
95
			'post_content'   => $source_post->post_content,
96
			'post_excerpt'   => $source_post->post_excerpt,
97
			'comment_status' => $source_post->comment_status,
98
			'ping_status'    => $source_post->ping_status,
99
			'post_category'  => $source_post->post_category,
100
			'tags_input'     => $source_post->tags_input,
101
		);
102
		$data = apply_filters( 'jetpack_copy_post_data', $data, $source_post, $target_post_id );
103
		return wp_update_post( $data );
104
	}
105
106
	/**
107
	 * Update the target post's featured image.
108
	 *
109
	 * @param WP_Post $source_post Post object to be copied.
110
	 * @param int     $target_post_id Target post ID.
111
	 * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
112
	 */
113
	protected function update_featured_image( $source_post, $target_post_id ) {
114
		$featured_image_id = get_post_thumbnail_id( $source_post );
115
		return update_post_meta( $target_post_id, '_thumbnail_id', $featured_image_id );
116
	}
117
118
	/**
119
	 * Update the target post's post format.
120
	 *
121
	 * @param WP_Post $source_post Post object to be copied.
122
	 * @param int     $target_post_id Target post ID.
123
	 * @return array|WP_Error|false WP_Error on error, array of affected term IDs on success.
124
	 */
125
	protected function update_post_format( $source_post, $target_post_id ) {
126
		$post_format = get_post_format( $source_post );
127
		return set_post_format( $target_post_id, $post_format );
128
	}
129
130
	/**
131
	 * Update the target post's Likes and Sharing statuses.
132
	 *
133
	 * @param WP_Post $source_post Post object to be copied.
134
	 * @param int     $target_post_id Target post ID.
135
	 * @return array Array with the results of each update action.
136
	 */
137
	protected function update_likes_sharing( $source_post, $target_post_id ) {
138
		$likes          = get_post_meta( $source_post->ID, 'switch_like_status', true );
139
		$sharing        = get_post_meta( $source_post->ID, 'sharing_disabled', false );
140
		$likes_result   = update_post_meta( $target_post_id, 'switch_like_status', $likes );
141
		$sharing_result = update_post_meta( $target_post_id, 'sharing_disabled', $sharing );
142
		return array(
143
			'likes'   => $likes_result,
144
			'sharing' => $sharing_result,
145
		);
146
	}
147
148
	/**
149
	 * Update the target post's title.
150
	 *
151
	 * @param string  $post_title Post title determined by `get_default_post_to_edit()`.
152
	 * @param WP_Post $post       Post object of newly-inserted post.
153
	 * @return string             Updated post title from source post.
154
	 */
155
	public function filter_title( $post_title, $post ) {
156
		return $post->post_title;
157
	}
158
159
	/**
160
	 * Update the target post's content (`post_content`).
161
	 *
162
	 * @param string  $post_content Post content determined by `get_default_post_to_edit()`.
163
	 * @param WP_Post $post         Post object of newly-inserted post.
164
	 * @return string               Updated post content from source post.
165
	 */
166
	public function filter_content( $post_content, $post ) {
167
		return $post->post_content;
168
	}
169
170
	/**
171
	 * Update the target post's excerpt.
172
	 *
173
	 * @param string  $post_excerpt Post excerpt determined by `get_default_post_to_edit()`.
174
	 * @param WP_Post $post         Post object of newly-inserted post.
175
	 * @return string               Updated post excerpt from source post.
176
	 */
177
	public function filter_excerpt( $post_excerpt, $post ) {
178
		return $post->post_excerpt;
179
	}
180
181
	/**
182
	 * Add a "Copy" row action to posts/pages/CPTs on list views.
183
	 *
184
	 * @param array   $actions Existing actions.
185
	 * @param WP_Post $post    Post object of current post in list.
186
	 * @return array           Array of updated row actions.
187
	 */
188
	public function add_row_action( $actions, $post ) {
189
		if ( ! $this->user_can_access_post( $post->ID ) ) {
190
			return $actions;
191
		}
192
193
		$edit_url    = add_query_arg(
194
			array(
195
				'post_type'    => $post->post_type,
196
				'jetpack-copy' => $post->ID,
197
				'_wpnonce'     => wp_create_nonce( 'jetpack-copy-post' ),
198
			),
199
			admin_url( 'post-new.php' )
200
		);
201
		$edit_action = array(
202
			'jetpack-copy' => sprintf(
203
				'<a href="%s" aria-label="%s">%s</a>',
204
				esc_url( $edit_url ),
205
				esc_attr__( 'Copy this post.', 'jetpack' ),
206
				esc_html__( 'Copy', 'jetpack' )
207
			),
208
		);
209
210
		// Insert the Copy action before the Trash action.
211
		$edit_offset = array_search( 'trash', array_keys( $actions ), true );
212
		$actions     = array_merge(
213
			array_slice( $actions, 0, $edit_offset ),
214
			$edit_action,
215
			array_slice( $actions, $edit_offset )
216
		);
217
218
		return $actions;
219
	}
220
}
221
222
/**
223
 * Instantiate an instance of Jetpack_Copy_Post on the `admin_init` hook.
224
 */
225
function jetpack_copy_post_init() {
226
	new Jetpack_Copy_Post();
227
}
228
add_action( 'admin_init', 'jetpack_copy_post_init' );
229