PluginProbe
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts / 2.7.7
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts v2.7.7
2.8.14 2.8.13 2.8.12 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.10 2.8.11 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 trunk 1.0.1 1.0.2 1.1.0 1.1.1 All 147 releases
content-blocks-builder / includes / copy-post.php

copy-post.php in Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts 2.7.7, at includes/copy-post.php

385 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Copy blocks, variations, and patterns
4 *
5 * @package BoldBlocks
6 * @author Phi Phan <mrphipv@gmail.com>
7 * @copyright Copyright (c) 2023, Phi Phan
8 */
9
10 namespace BoldBlocks;
11
12 // Exit if accessed directly.
13 defined( 'ABSPATH' ) || exit;
14
15 if ( ! class_exists( CopyPost::class ) ) :
16 /**
17 * Copy blocks, variations, or patterns
18 */
19 class CopyPost extends CoreComponent {
20 /**
21 * Run main hooks
22 *
23 * @return void
24 */
25 public function run() {
26 // Add the Copy action link the the list post screen.
27 add_action( 'admin_init', [ $this, 'copy_post_row_actions' ] );
28
29 // Add new rest api endpoint.
30 add_action( 'rest_api_init', [ $this, 'register_endpoint' ] );
31
32 // Handle copy action.
33 add_action( 'admin_action_cbb_copy_item', [ $this, 'hanlde_copy_item_action_link' ] );
34
35 // Add a notice for item copied successfully.
36 add_action( 'admin_notices', [ $this, 'copy_item_admin_notice' ] );
37
38 // Enqueue scripts.
39 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_block_editor_assets' ] );
40 }
41
42 /**
43 * Add the Copy action link the the list post screen.
44 *
45 * @return void
46 */
47 public function copy_post_row_actions() {
48 if ( 'edit.php' === $GLOBALS['pagenow'] ) {
49 add_filter( 'post_row_actions', [ $this, 'add_row_action' ], 10, 2 );
50 add_filter( 'page_row_actions', [ $this, 'add_row_action' ], 10, 2 );
51 return;
52 }
53 }
54
55 /**
56 * Add a "Copy" row action to blocks, variations, and patterns on list views.
57 *
58 * @param array $actions Existing actions.
59 * @param WP_Post $post Post object of current post in list.
60 * @return array Array of updated row actions.
61 */
62 public function add_row_action( $actions, $post ) {
63 // Bail if it's not a place to add the copy link.
64 if ( ! $this->user_can_access_post( $post->ID ) || ! $post instanceof \WP_Post || ! $this->validate_post_type( $post->post_type, $post ) ) {
65 return $actions;
66 }
67
68 $edit_url = wp_nonce_url(
69 add_query_arg(
70 array(
71 'post_type' => $post->post_type,
72 'action' => 'cbb_copy_item',
73 'cbb-copy-id' => $post->ID,
74 ),
75 admin_url( 'post-new.php' )
76 ),
77 BOLDBLOCKS_CBB_ROOT_FILE,
78 'cbb-copy-nonce'
79 );
80 $edit_action = array(
81 'cbb-copy' => sprintf(
82 '<a href="%s" aria-label="%s">%s</a>',
83 esc_url( $edit_url ),
84 esc_attr__( 'Copy this item.', 'content-blocks-builder' ),
85 esc_html__( 'Copy', 'content-blocks-builder' )
86 ),
87 );
88
89 // Insert the Copy action before the Trash action.
90 $edit_offset = max( 2, array_search( 'trash', array_keys( $actions ), true ) );
91 $updated_actions = array_merge(
92 array_slice( $actions, 0, $edit_offset ),
93 $edit_action,
94 array_slice( $actions, $edit_offset )
95 );
96
97 /**
98 * Fires after the new Copy action has been added to the row actions.
99 * Allows changes to the action presentation, or other final checks.
100 *
101 * @param array $updated_actions Updated row actions with the Copy Post action.
102 * @param array $actions Original row actions passed to this filter.
103 * @param WP_Post $post Post object of current post in listing.
104 */
105 return apply_filters( 'cbb_copy_item_row_actions', $updated_actions, $actions, $post );
106 }
107
108 /**
109 * Function creates post duplicate as a draft and redirects then to the edit post screen
110 */
111 public function hanlde_copy_item_action_link() {
112 // Bail if there is no post to copy.
113 if ( empty( $_GET['cbb-copy-id'] ) ) {
114 return;
115 }
116
117 // Nonce verification.
118 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
119 if ( ! isset( $_GET['cbb-copy-nonce'] ) || ! wp_verify_nonce( $_GET['cbb-copy-nonce'], BOLDBLOCKS_CBB_ROOT_FILE ) ) {
120 return;
121 }
122
123 // Get the original post id.
124 $post_id = absint( $_GET['cbb-copy-id'] );
125
126 // Clone it.
127 $new_post = $this->copy_item( $post_id );
128
129 // On error.
130 if ( ! $new_post || ! $new_post instanceof \WP_Post ) {
131 die( is_string( $new_post ) ? $new_post : '' );
132 }
133
134 // Redirect to the post list screen.
135 wp_safe_redirect(
136 add_query_arg(
137 array(
138 'post_type' => $new_post->post_type,
139 'cbb_copied_id' => $new_post->ID,
140 ),
141 admin_url( 'edit.php' )
142 )
143 );
144
145 exit;
146 }
147
148 /**
149 * Copy a post to a new one
150 *
151 * @param int $post_id
152 * @return WP_Post|string
153 */
154 private function copy_item( $post_id ) {
155 // Get the post data.
156 $post = get_post( $post_id );
157 if ( ! $post || ! $post instanceof \WP_Post ) {
158 return __( 'Copy post failed, could not find the original post.', 'content-blocks-builder' );
159 }
160
161 // Not has permission and invalid post type.
162 if ( ! $this->user_can_access_post( $post->ID ) || ! $this->validate_post_type( $post->post_type, $post ) ) {
163 return __( 'You are not allowed to copy the original post', 'content-blocks-builder' );
164 }
165
166 // New author id.
167 $current_user = wp_get_current_user();
168 $new_author_id = $current_user->ID;
169
170 // New post data.
171 $args = array(
172 'post_title' => $post->post_title . __( ' new copy', 'content-blocks-builder' ),
173 'post_content' => wp_slash( $post->post_content ),
174 'post_author' => $new_author_id,
175 'post_status' => 'draft',
176 'post_type' => $post->post_type,
177 'menu_order' => $post->menu_order,
178 );
179
180 // Insert the post.
181 $new_post_id = wp_insert_post( $args );
182
183 // Copy taxonomies.
184 $taxonomies = get_object_taxonomies( $post->post_type );
185 if ( $taxonomies ) {
186 foreach ( $taxonomies as $taxonomy ) {
187 $post_terms = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'slugs' ) );
188 wp_set_object_terms( $new_post_id, $post_terms, $taxonomy, false );
189 }
190 }
191
192 // Copy post meta.
193 $post_meta = get_post_custom( $post_id );
194 foreach ( $post_meta as $key => $values ) {
195 foreach ( $values as $value ) {
196 if ( 'boldblocks_variation_name' === $key ) {
197 $strlen = strlen( $value );
198 if ( $strlen > 10 ) {
199 $id = uniqid();
200 if ( strlen( $id ) > 10 ) {
201 $id = substr( $id, -10 );
202 }
203 $prefix = substr( $value, 0, $strlen - 10 );
204 $value = $prefix . $id;
205 }
206 }
207 update_post_meta( $new_post_id, $key, maybe_unserialize( $value ) );
208 }
209 }
210
211 return get_post( $new_post_id );
212 }
213
214 /**
215 * Register a custom endpoint to copy post.
216 *
217 * @return void
218 */
219 public function register_endpoint() {
220 register_rest_route(
221 'boldblocks/v1',
222 '/copyPost/(?P<id>[\d]+)',
223 array(
224 'methods' => 'POST',
225 'callback' => [ $this, 'handle_copy_post_request' ],
226 'permission_callback' => function ( $request ) {
227 return $this->user_can_access_post( $request['id'] );
228 },
229 )
230 );
231 }
232
233 /**
234 * Handle copy post from REST api.
235 *
236 * @param WP_Request $request
237 * @return void
238 */
239 public function handle_copy_post_request( $request ) {
240 $post_id = $request['id'];
241
242 // Clone it.
243 $post = $this->copy_item( $post_id );
244
245 // On error.
246 if ( ! $post || ! $post instanceof \WP_Post ) {
247 wp_send_json_error( $post );
248 }
249
250 wp_send_json_success(
251 [
252 'post' => $post,
253 'notice' => $this->get_success_response(
254 $post->ID,
255 $post->post_type
256 ),
257 ]
258 );
259 }
260
261 /**
262 * Add the admin notice
263 *
264 * @return void
265 */
266 public function copy_item_admin_notice() {
267 // Get the current screen.
268 $screen = get_current_screen();
269
270 if ( 'edit' !== $screen->base ) {
271 return;
272 }
273
274 if ( ! $this->validate_post_type( $screen->post_type ) ) {
275 return;
276 }
277
278 // Checks if settings updated.
279 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
280 if ( isset( $_GET['cbb_copied_id'] ) ) {
281 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
282 $res = $this->get_success_response( absint( $_GET['cbb_copied_id'] ), $screen->post_type );
283 $edit_link = sprintf(
284 '<a href="%s" aria-label="%s">%s</a>',
285 esc_url( $res['edit_url'] ),
286 $res['edit_label'],
287 $res['edit_label']
288 );
289 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
290 echo '<div class="notice notice-success is-dismissible"><p>' . $res['message'] . ' ' . $edit_link . '.</p></div>';
291 }
292 }
293
294 /**
295 * Build the success massage
296 *
297 * @param int $post_id
298 * @param string $post_type
299 * @return array
300 */
301 private function get_success_response( $post_id, $post_type ) {
302 $post_type_object = get_post_type_object( $post_type );
303 $post_type_label = $post_type_object->labels->singular_name;
304
305 $edit_url = add_query_arg(
306 array(
307 'action' => 'edit',
308 'post' => $post_id,
309 ),
310 admin_url( 'post.php' )
311 );
312
313 return [
314 'message' => sprintf( __( 'A new copied %s is created.', 'content-blocks-builder' ), strtolower( $post_type_label ) ),
315 'edit_url' => $edit_url,
316 'edit_label' => esc_attr__( 'Edit' ),
317 ];
318 }
319
320 /**
321 * Enqueue editor assets
322 *
323 * @return void
324 */
325 public function enqueue_block_editor_assets() {
326 $screen = get_current_screen();
327 if ( $this->validate_post_type( $screen->post_type ) ) {
328 // Access file.
329 $copy_post_asset = $this->the_plugin_instance->include_file( 'build/copy-post.asset.php' );
330
331 // Scripts.
332 wp_enqueue_script(
333 'cbb-copy-post',
334 $this->the_plugin_instance->get_file_uri( '/build/copy-post.js' ),
335 $copy_post_asset['dependencies'] ?? [],
336 $this->the_plugin_instance->get_script_version( $copy_post_asset ),
337 [ 'in_footer' => true ]
338 );
339
340 // Add translation.
341 wp_set_script_translations( 'cbb-copy-post', 'content-blocks-builder' );
342 }
343 }
344
345 /**
346 * Determine if the current user has edit access to the source post.
347 *
348 * @param int $post_id Source post ID (the post being copied).
349 * @return bool True if user has the meta cap of `edit_post` for the given post ID, false otherwise.
350 */
351 protected function user_can_access_post( $post_id ) {
352 return current_user_can( 'edit_post', $post_id );
353 }
354
355 /**
356 * Validate the post type to be used for the target post.
357 *
358 * @param WP_Post $post Post object of current post in listing.
359 * @return bool True if the post type is in a list of supported psot types; false otherwise.
360 */
361 protected function validate_post_type( $post_type, $post = null ) {
362 /**
363 * Fires when determining if the "Copy" row action should be made available.
364 * Allows overriding supported post types.
365 *
366 * @param array Post types supported by default.
367 * @param WP_Post $post Post object of current post in listing.
368 */
369 $valid_post_types = apply_filters(
370 'cbb_copy_item_post_types',
371 [
372 'boldblocks_block',
373 'boldblocks_variation',
374 'boldblocks_pattern',
375 'post',
376 'page',
377 ],
378 $post
379 );
380
381 return in_array( $post_type, $valid_post_types, true );
382 }
383 }
384 endif;
385