PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 6.0.4
Admin and Site Enhancements (ASE) v6.0.4
9.1.0 9.0.2 9.0.1 9.0.0 8.9.2 8.9.1 8.9.0 8.8.8 8.8.7 8.8.6 8.8.5 8.8.4 8.8.3 8.8.2 8.8.1 8.8.0 8.7.3 8.7.2 8.7.1 8.2.1 8.2.2 8.2.3 8.3.0 8.3.1 8.3.2 8.4.0 8.4.1 8.4.2 8.5.0 8.5.1 8.5.2 8.6.0 8.6.1 8.6.2 8.7.0 5.0.1 5.0.2 5.0.2.1 5.0.2.2 5.0.2.3 5.0.2.4 5.1.0 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.8.0 5.8.1 6.0.0 6.0.3 6.0.4 6.0.5 6.0.5.1 6.0.6 6.0.7 6.0.8.1 6.1.0 6.1.3 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.3.0 6.3.1 6.3.2 6.4.0 6.5.0 6.5.1 6.6.0 6.7.0 6.8.0 6.8.2 6.8.3 6.9.0 6.9.1 6.9.10 6.9.11 6.9.12 6.9.13.1 6.9.13.2 6.9.2 6.9.3 6.9.4 6.9.5 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.2 7.0.2.1 7.0.2.2 7.0.2.3 7.0.3 7.1.0 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2.0 7.2.1 7.3.0 7.3.1 7.3.2 7.3.3 7.4.0 7.4.2 7.4.4 7.4.5 7.4.6 7.4.7 7.4.8 7.5.0 7.5.1 7.5.2 7.5.3 7.5.4 7.6.0 7.6.1 7.6.1.1 7.6.10 7.6.11 7.6.2 7.6.3 7.6.4 7.6.5 7.6.6 7.6.7 7.6.7.1 7.6.8 7.6.9 7.7.0 7.8.0 7.8.1 7.8.10 7.8.11 7.8.12 7.8.13 7.8.14 7.8.15 7.8.16 7.8.17 7.8.18 7.8.2 7.8.3 7.8.4 7.8.5 7.8.5.1 7.8.6 7.8.7 7.8.8 7.8.9 7.9.0 7.9.1 7.9.10 7.9.11 7.9.2 7.9.3 7.9.4 7.9.5 7.9.6 7.9.7 7.9.8 7.9.9 8.0.0 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.0.6 8.0.7 8.0.8 8.1.0 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 trunk 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 1.9.0 2.0.0 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.6.0 2.7.0 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 3.6.1 3.7.0 3.8.0 3.9.0 3.9.1 3.9.2 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.9.0 4.9.1 4.9.2 4.9.3 5.0.0
admin-site-enhancements / classes / class-content-management.php
admin-site-enhancements / classes Last commit date
class-activation.php 2 years ago class-admin-interface.php 2 years ago class-common-methods.php 2 years ago class-content-management.php 2 years ago class-custom-code.php 2 years ago class-deactivation.php 2 years ago class-disable-components.php 2 years ago class-login-logout.php 2 years ago class-optimizations.php 2 years ago class-security.php 2 years ago class-settings-fields-render.php 2 years ago class-settings-sanitization.php 2 years ago class-settings-sections-fields.php 2 years ago class-utilities.php 2 years ago
class-content-management.php
1268 lines
1 <?php
2
3 namespace ASENHA\Classes;
4
5 use enshrined\svgSanitize\Sanitizer ;
6 // For Enable SVG Upload
7 use WP_Query ;
8 use WC_Admin_Duplicate_Product ;
9 /**
10 * Class related to Content Management features
11 *
12 * @since 1.0.0
13 */
14 class Content_Management
15 {
16 /**
17 * Enable duplication of pages, posts and custom posts
18 *
19 * @since 1.0.0
20 */
21 public function asenha_enable_duplication()
22 {
23 $original_post_id = intval( sanitize_text_field( $_REQUEST['post'] ) );
24 $nonce = sanitize_text_field( $_REQUEST['nonce'] );
25
26 if ( wp_verify_nonce( $nonce, 'asenha-duplicate-' . $original_post_id ) && current_user_can( 'edit_posts' ) ) {
27 $original_post = get_post( $original_post_id );
28 $post_type = $original_post->post_type;
29 // Not WooCommerce product
30
31 if ( 'product' != $post_type ) {
32 // Set some attributes for the duplicate post
33 $new_post_title_suffix = ' (DUPLICATE)';
34 $new_post_status = 'draft';
35 $current_user = wp_get_current_user();
36 $new_post_author_id = $current_user->ID;
37 // Create the duplicate post and store the ID
38 $args = array(
39 'comment_status' => $original_post->comment_status,
40 'ping_status' => $original_post->ping_status,
41 'post_author' => $new_post_author_id,
42 'post_content' => $original_post->post_content,
43 'post_excerpt' => $original_post->post_excerpt,
44 'post_parent' => $original_post->post_parent,
45 'post_password' => $original_post->post_password,
46 'post_status' => $new_post_status,
47 'post_title' => $original_post->post_title . $new_post_title_suffix,
48 'post_type' => $original_post->post_type,
49 'to_ping' => $original_post->to_ping,
50 'menu_order' => $original_post->menu_order,
51 );
52 $new_post_id = wp_insert_post( $args );
53 // Copy over the taxonomies
54 $original_taxonomies = get_object_taxonomies( $original_post->post_type );
55 if ( !empty($original_taxonomies) && is_array( $original_taxonomies ) ) {
56 foreach ( $original_taxonomies as $taxonomy ) {
57 $original_post_terms = wp_get_object_terms( $original_post_id, $taxonomy, array(
58 'fields' => 'slugs',
59 ) );
60 wp_set_object_terms(
61 $new_post_id,
62 $original_post_terms,
63 $taxonomy,
64 false
65 );
66 }
67 }
68 // Copy over the post meta
69 $original_post_metas = get_post_meta( $original_post_id );
70 // all meta keys and the corresponding values
71 if ( !empty($original_post_metas) ) {
72 foreach ( $original_post_metas as $meta_key => $meta_values ) {
73 foreach ( $meta_values as $meta_value ) {
74 update_post_meta( $new_post_id, $meta_key, wp_slash( maybe_unserialize( $meta_value ) ) );
75 }
76 }
77 }
78 }
79
80 // Redirect to list table of the corresponding post type of original post
81 // $post_type = get_post_type( $original_post_id );
82
83 if ( 'post' == $post_type ) {
84 wp_redirect( admin_url( 'edit.php' ) );
85 } else {
86 wp_redirect( admin_url( 'edit.php?post_type=' . $post_type ) );
87 }
88
89 } else {
90 wp_die( 'You do not have permission to perform this action.' );
91 }
92
93 }
94
95 /**
96 * Add row action link to perform duplication in page/post list tables
97 *
98 * @since 1.0.0
99 */
100 public function add_duplication_action_link( $actions, $post )
101 {
102 $post_type = $post->post_type;
103 if ( current_user_can( 'edit_posts' ) ) {
104 // Not WooCommerce product
105 if ( 'product' != $post_type ) {
106 $actions['asenha-duplicate'] = '<a href="admin.php?action=asenha_enable_duplication&amp;post=' . $post->ID . '&amp;nonce=' . wp_create_nonce( 'asenha-duplicate-' . $post->ID ) . '" title="Duplicate this as draft">Duplicate</a>';
107 }
108 }
109 return $actions;
110 }
111
112 /**
113 * Modify the 'Edit' link to be 'Edit or Replace'
114 *
115 */
116 public function modify_media_list_table_edit_link( $actions, $post )
117 {
118 $new_actions = array();
119 foreach ( $actions as $key => $value ) {
120
121 if ( $key == 'edit' ) {
122 $new_actions['edit'] = '<a href="' . get_edit_post_link( $post ) . '" aria-label="Edit or Replace">Edit or Replace</a>';
123 } else {
124 $new_actions[$key] = $value;
125 }
126
127 }
128 return $new_actions;
129 }
130
131 /**
132 * Add "Custom Order" sub-menu for post types
133 *
134 * @since 5.0.0
135 */
136 public function add_content_order_submenu( $context )
137 {
138 $options = get_option( ASENHA_SLUG_U, array() );
139 $content_order_for = $options['content_order_for'];
140 $content_order_enabled_post_types = array();
141 foreach ( $options['content_order_for'] as $post_type_slug => $is_custom_order_enabled ) {
142
143 if ( $is_custom_order_enabled ) {
144 $post_type_object = get_post_type_object( $post_type_slug );
145
146 if ( is_object( $post_type_object ) && property_exists( $post_type_object, 'labels' ) ) {
147 $post_type_name_plural = $post_type_object->labels->name;
148
149 if ( 'post' == $post_type_slug ) {
150 $hook_suffix = add_posts_page(
151 $post_type_name_plural . ' Order',
152 // Page title
153 'Order',
154 // Menu title
155 'edit_pages',
156 // Capability required
157 'custom-order-posts',
158 // Menu and page slug
159 [ $this, 'custom_order_page_output' ]
160 );
161 } else {
162 $hook_suffix = add_submenu_page(
163 'edit.php?post_type=' . $post_type_slug,
164 // Parent (menu) slug. Ref: https://developer.wordpress.org/reference/functions/add_submenu_page/#comment-1404
165 $post_type_name_plural . ' Order',
166 // Page title
167 'Order',
168 // Menu title
169 'edit_pages',
170 // Capability required
171 'custom-order-' . $post_type_slug,
172 // Menu and page slug
173 [ $this, 'custom_order_page_output' ]
174 );
175 }
176
177 add_action( 'admin_print_styles-' . $hook_suffix, [ $this, 'enqueue_content_order_styles' ] );
178 add_action( 'admin_print_scripts-' . $hook_suffix, [ $this, 'enqueue_content_order_scripts' ] );
179 }
180
181 }
182
183 }
184 }
185
186 /**
187 * Output content for the custom order page for each enabled post types
188 * Not using settings API because all done via AJAX
189 *
190 * @since 5.0.0
191 */
192 public function custom_order_page_output()
193 {
194 $parent_slug = get_admin_page_parent();
195
196 if ( 'edit.php' == $parent_slug ) {
197 $post_type_slug = 'post';
198 } else {
199 $post_type_slug = str_replace( 'edit.php?post_type=', '', $parent_slug );
200 }
201
202 // Object with properties for each post status and the count of posts for each status
203 // $post_count_object = wp_count_posts( $post_type_slug );
204 // Number of items with the status 'publish(ed)', 'future' (scheduled), 'draft', 'pending' and 'private'
205 // $post_count = absint( $post_count_object->publish )
206 // + absint( $post_count_object->future )
207 // + absint( $post_count_object->draft )
208 // + absint( $post_count_object->pending )
209 // + absint( $post_count_object->private );
210 ?>
211 <div class="wrap">
212 <div class="page-header">
213 <h2>
214 <?php
215 echo get_admin_page_title() ;
216 ?>
217 </h2>
218 <div id="toggles" style="display:none;">
219 <input type="checkbox" id="toggle-taxonomy-terms" name="terms" value="" /><label for="toggle-taxonomy-terms">Show taxonomy terms</label>
220 <input type="checkbox" id="toggle-excerpt" name="excerpt" value="" /><label for="toggle-excerpt">Show excerpt</label>
221 </div>
222 </div>
223 <?php
224 // Get posts
225 $query = new WP_Query( array(
226 'post_type' => $post_type_slug,
227 'posts_per_page' => -1,
228 'orderby' => 'menu_order title',
229 'order' => 'ASC',
230 'post_status' => array(
231 'publish',
232 'future',
233 'draft',
234 'pending',
235 'private'
236 ),
237 'post_parent' => 0,
238 ) );
239
240 if ( $query->have_posts() ) {
241 ?>
242 <ul id="item-list">
243 <?php
244 while ( $query->have_posts() ) {
245 $query->the_post();
246 $post = get_post( get_the_ID() );
247 $this->custom_order_single_item_output( $post );
248 }
249 ?>
250 </ul>
251 <div id="updating-order-notice" class="updating-order-notice" style="display: none;"><img src="<?php
252 echo ASENHA_URL . 'assets/img/oval.svg' ;
253 ?>" id="spinner-img" class="spinner-img" /><span class="dashicons dashicons-saved" style="display:none;"></span>Updating order...</div>
254 <?php
255 } else {
256 ?>
257 <h3>There is nothing to sort for this post type.</h3>
258 <?php
259 }
260
261 ?>
262 </div> <!-- End of div.wrap -->
263 <?php
264 wp_reset_postdata();
265 }
266
267 /**
268 * Output single item sortable for custom content order
269 *
270 * @since 5.0.0
271 */
272 private function custom_order_single_item_output( $post )
273 {
274
275 if ( is_post_type_hierarchical( $post->post_type ) ) {
276 $post_type_object = get_post_type_object( $post->post_type );
277 $children = get_pages( array(
278 'child_of' => $post->ID,
279 'post_type' => $post->post_type,
280 ) );
281
282 if ( count( $children ) > 0 ) {
283 $has_child_label = '<span class="has-child-label"> <span class="dashicons dashicons-arrow-right"></span> Has child ' . strtolower( $post_type_object->label ) . '</span>';
284 $has_child = 'true';
285 } else {
286 $has_child_label = '';
287 $has_child = 'false';
288 }
289
290 } else {
291 $has_child_label = '';
292 $has_child = 'false';
293 }
294
295 $post_status_label_class = ( $post->post_status == 'publish' ? ' item-status-hidden' : '' );
296 $post_status_object = get_post_status_object( $post->post_status );
297
298 if ( empty(wp_trim_excerpt( '', $post )) ) {
299 $short_excerpt = '';
300 } else {
301 $excerpt_trimmed = implode( " ", array_slice( explode( " ", wp_trim_excerpt( '', $post ) ), 0, 30 ) );
302 $short_excerpt = '<span class="item-excerpt"> | ' . $excerpt_trimmed . '</span>';
303 }
304
305 $taxonomies = get_object_taxonomies( $post->post_type, 'objects' );
306 // vi( $taxonomies );
307 $taxonomies_and_terms = '';
308 foreach ( $taxonomies as $taxonomy ) {
309 $terms = array();
310
311 if ( $taxonomy->hierarchical ) {
312 $taxonomy_terms = get_the_terms( $post->ID, $taxonomy->name );
313 foreach ( $taxonomy_terms as $term ) {
314 $terms[] = $term->name;
315 }
316 }
317
318 $terms = implode( ', ', $terms );
319 $taxonomies_and_terms .= ' | ' . $taxonomy->label . ': ' . $terms;
320 }
321 if ( !empty($taxonomies_and_terms) ) {
322 $taxonomies_and_terms = '<span class="item-taxonomy-terms">' . $taxonomies_and_terms . '</span>';
323 }
324 ?>
325 <li id="list_<?php
326 echo $post->ID ;
327 ?>" data-id="<?php
328 echo $post->ID ;
329 ?>" data-menu-order="<?php
330 echo $post->menu_order ;
331 ?>" data-parent="<?php
332 echo $post->post_parent ;
333 ?>" data-has-child="<?php
334 echo $has_child ;
335 ?>" data-post-type="<?php
336 echo $post->post_type ;
337 ?>">
338 <div class="row">
339 <div class="row-content">
340 <?php
341 echo '<div class="content-main">
342 <span class="dashicons dashicons-menu"></span><a href="' . get_edit_post_link( $post->ID ) . '" class="item-title">' . $post->post_title . '</a><span class="item-status' . $post_status_label_class . '"> — ' . $post_status_object->label . '</span>' . $has_child_label . wp_kses_post( $taxonomies_and_terms ) . wp_kses_post( $short_excerpt ) . '<div class="fader"></div>
343 </div>
344 <div class="content-additional">
345 <a href="' . get_the_permalink( $post->ID ) . '" target="_blank" class="button item-view-link">View</a>
346 </div>' ;
347 ?>
348 </div>
349 </div>
350 </li>
351 <?php
352 }
353
354 /**
355 * Enqueue styles for content order pages
356 *
357 * @since 5.0.0
358 */
359 public function enqueue_content_order_styles()
360 {
361 wp_enqueue_style(
362 'content-order-style',
363 ASENHA_URL . 'assets/css/content-order.css',
364 array(),
365 ASENHA_VERSION
366 );
367 }
368
369 /**
370 * Enqueue scripts for content order pages
371 *
372 * @since 5.0.0
373 */
374 public function enqueue_content_order_scripts()
375 {
376 global $typenow ;
377 wp_enqueue_script(
378 'content-order-jquery-ui-touch-punch',
379 ASENHA_URL . 'assets/js/jquery.ui.touch-punch.min.js',
380 array( 'jquery-ui-sortable' ),
381 '0.2.3',
382 true
383 );
384 wp_register_script(
385 'content-order-nested-sortable',
386 ASENHA_URL . 'assets/js/jquery.mjs.nestedSortable.js',
387 array( 'content-order-jquery-ui-touch-punch' ),
388 '2.0.0',
389 true
390 );
391 wp_enqueue_script(
392 'content-order-sort',
393 ASENHA_URL . 'assets/js/content-order-sort.js',
394 array( 'content-order-nested-sortable' ),
395 ASENHA_VERSION,
396 true
397 );
398 wp_localize_script( 'content-order-sort', 'contentOrderSort', array(
399 'action' => 'save_custom_order',
400 'nonce' => wp_create_nonce( 'order_sorting_nonce' ),
401 'hirarchical' => ( is_post_type_hierarchical( $typenow ) ? 'true' : 'false' ),
402 ) );
403 }
404
405 /**
406 * Save custom content order coming from ajax call
407 *
408 * @since 5.0.0
409 */
410 public function save_custom_content_order()
411 {
412 global $wpdb ;
413 // Check user capabilities
414 if ( !current_user_can( 'edit_pages' ) ) {
415 wp_send_json( 'Something went wrong.' );
416 }
417 // Verify nonce
418 if ( !wp_verify_nonce( $_POST['nonce'], 'order_sorting_nonce' ) ) {
419 wp_send_json( 'Something went wrong.' );
420 }
421 // Get ajax variables
422 $action = ( isset( $_POST['action'] ) ? $_POST['action'] : '' );
423 $item_parent = ( isset( $_POST['item_parent'] ) ? absint( $_POST['item_parent'] ) : 0 );
424 $menu_order_start = ( isset( $_POST['start'] ) ? absint( $_POST['start'] ) : 0 );
425 $post_id = ( isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0 );
426 $item_menu_order = ( isset( $_POST['menu_order'] ) ? absint( $_POST['menu_order'] ) : 0 );
427 $items_to_exclude = ( isset( $_POST['excluded_items'] ) ? absint( $_POST['excluded_items'] ) : array() );
428 $post_type = ( isset( $_POST['post_type'] ) ? $_POST['post_type'] : false );
429 // Make processing faster by removing certain actions
430 remove_action( 'pre_post_update', 'wp_save_post_revision' );
431 // $response array for ajax response
432 $response = array();
433 // Update the item whose order/position was moved
434
435 if ( $post_id > 0 && !isset( $_POST['more_posts'] ) ) {
436 // https://developer.wordpress.org/reference/classes/wpdb/update/
437 $wpdb->update(
438 $wpdb->posts,
439 // The table
440 array(
441 'menu_order' => $item_menu_order,
442 ),
443 array(
444 'ID' => $post_id,
445 )
446 );
447 clean_post_cache( $post_id );
448 $items_to_exclude[] = $post_id;
449 }
450
451 // Get all posts from the post type related to ajax request
452 $query_args = array(
453 'post_type' => $post_type,
454 'orderby' => 'menu_order title',
455 'order' => 'ASC',
456 'posts_per_page' => -1,
457 'suppress_filters' => true,
458 'ignore_sticky_posts' => true,
459 'post_status' => array(
460 'publish',
461 'future',
462 'draft',
463 'pending',
464 'private'
465 ),
466 'post_parent' => $item_parent,
467 'post__not_in' => $items_to_exclude,
468 'update_post_term_cache' => false,
469 'update_post_meta_cache' => false,
470 );
471 $posts = new WP_Query( $query_args );
472
473 if ( $posts->have_posts() ) {
474 // Iterate through posts and update menu order and post parent
475 foreach ( $posts->posts as $post ) {
476 // If the $post is the one being displaced (shited downward) by the moved item, increment it's menu_order by one
477 if ( $menu_order_start == $item_menu_order && $post_id > 0 ) {
478 $menu_order_start++;
479 }
480 // Only process posts other than the moved item, which has been processed earlier outside this loop
481
482 if ( $post_id != $post->ID ) {
483 // Update menu_order
484 $wpdb->update( $wpdb->posts, array(
485 'menu_order' => $menu_order_start,
486 ), array(
487 'ID' => $post->ID,
488 ) );
489 clean_post_cache( $post->ID );
490 }
491
492 $items_to_exclude[] = $post->ID;
493 $menu_order_start++;
494 }
495 die( json_encode( $response ) );
496 } else {
497 die( json_encode( $response ) );
498 }
499
500 }
501
502 /**
503 * Set default ordering of list tables of sortable post types in wp-admin by 'menu_order title'
504 *
505 * @since 5.0.0
506 */
507 public function orderby_menu_order_in_admin_lists( $wp_query )
508 {
509 global $pagenow, $typenow ;
510 $options = get_option( ASENHA_SLUG_U, array() );
511 $content_order_for = $options['content_order_for'];
512 $content_order_enabled_post_types = array();
513 foreach ( $options['content_order_for'] as $post_type_slug => $is_custom_order_enabled ) {
514 if ( $is_custom_order_enabled ) {
515 $content_order_enabled_post_types[] = $post_type_slug;
516 }
517 }
518 if ( is_admin() && 'edit.php' == $pagenow && !isset( $_GET['orderby'] ) ) {
519
520 if ( in_array( $typenow, $content_order_enabled_post_types ) && (post_type_supports( $typenow, 'page-attributes' ) || is_post_type_hierarchical( $typenow )) ) {
521 $wp_query->set( 'orderby', 'menu_order title' );
522 $wp_query->set( 'order', 'ASC' );
523 }
524
525 }
526 }
527
528 /**
529 * Add media replacement button in the edit screen of media/attachment
530 *
531 * @since 1.1.0
532 */
533 public function add_media_replacement_button( $fields, $post )
534 {
535 // Enqueues all scripts, styles, settings, and templates necessary to use all media JS APIs.
536 // Reference: https://codex.wordpress.org/Javascript_Reference/wp.media
537 wp_enqueue_media();
538 // Add new field to attachment fields for the media replace functionality
539 $fields['asenha-media-replace'] = array();
540 $fields['asenha-media-replace']['label'] = '';
541 $fields['asenha-media-replace']['input'] = 'html';
542 $fields['asenha-media-replace']['html'] = '
543 <div id="media-replace-div" class="postbox">
544 <div class="postbox-header">
545 <h2 class="hndle ui-sortable-handle">Replace Media</h2>
546 </div>
547 <div class="inside">
548 <button type="button" id="asenha-media-replace" class="button-secondary button-large asenha-media-replace-button" onclick="asenha_replace_media();">Select New Media File</button>
549 <input type="hidden" id="new-attachment-id" name="new-attachment-id" />
550 <div class="asenha-media-replace-notes"><p>The current file will be replaced with the uploaded and/or selected file while retaining the current ID, publish date and file name. Thus, no existing links will break.</p><p>Note that you can only replace with the same file type, e.g. JPG can only be replaced by JPG.</p></div>
551 </div>
552 </div>
553 ';
554 return $fields;
555 }
556
557 /**
558 * Replace existing media with the newly updated file
559 *
560 * @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L55
561 * @since 1.1.0
562 */
563 public function replace_media( $old_attachment_id )
564 {
565 $old_post_meta = get_post( $old_attachment_id, ARRAY_A );
566 $old_post_mime = $old_post_meta['post_mime_type'];
567 // e.g. 'image/jpeg'
568 // Get the new attachment/media ID, meta and mime type
569
570 if ( isset( $_POST['new-attachment-id'] ) && !empty($_POST['new-attachment-id']) ) {
571 $new_attachment_id = intval( sanitize_text_field( $_POST['new-attachment-id'] ) );
572 $new_post_meta = get_post( $new_attachment_id, ARRAY_A );
573 $new_post_mime = $new_post_meta['post_mime_type'];
574 // e.g. 'image/jpeg'
575 }
576
577 // Check if the media file ID selected via the media frame and passed on to the #new-attachment-id hidden field
578 // Ensure the mime type matches too
579
580 if ( !empty($new_attachment_id) && is_numeric( $new_attachment_id ) && $old_post_mime == $new_post_mime ) {
581 $new_attachment_meta = wp_get_attachment_metadata( $new_attachment_id );
582 // If original file is larger than 2560 pixel
583 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
584
585 if ( array_key_exists( 'original_image', $new_attachment_meta ) ) {
586 // Get the original media file path
587 $new_media_file_path = wp_get_original_image_path( $new_attachment_id );
588 } else {
589 // Get the path to newly uploaded media file. An image file name may end with '-scaled'.
590 $new_attachment_file = get_post_meta( $new_attachment_id, '_wp_attached_file', true );
591 $upload_dir = wp_upload_dir();
592 $new_media_file_path = $upload_dir['basedir'] . '/' . $new_attachment_file;
593 }
594
595 // Check if the new media file exist / was successfully uploaded
596 if ( !is_file( $new_media_file_path ) ) {
597 return false;
598 }
599 // Delete existing/old media files. Post and post meta entries for it are still there in the database.
600 $this->delete_media_files( $old_attachment_id );
601 // If original file is larger than 2560 pixel
602 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
603
604 if ( array_key_exists( 'original_image', $new_attachment_meta ) ) {
605 // Get the original media file path
606 $old_media_file_path = wp_get_original_image_path( $old_attachment_id );
607 } else {
608 // Get the path to the old/existing media file that will be replaced and deleted. An image file name may end with '-scaled'.
609 $old_attachment_file = get_post_meta( $old_attachment_id, '_wp_attached_file', true );
610 $old_media_file_path = $upload_dir['basedir'] . '/' . $old_attachment_file;
611 }
612
613 // Check if the directory path to the old media file is still intact
614 if ( !file_exists( dirname( $old_media_file_path ) ) ) {
615 // Recreate the directory path
616 mkdir( dirname( $old_media_file_path ), 0755, true );
617 }
618 // Copy the new media file into the old media file's path
619 copy( $new_media_file_path, $old_media_file_path );
620 // Regenerate attachment meta data and image sub-sizes from the new media file that was just copied to the old path
621 $old_media_post_meta_updated = wp_generate_attachment_metadata( $old_attachment_id, $old_media_file_path );
622 // Update new media file's meta data with the ones from the old media. i.e. new media file will carry over the post ID and post meta of the old media file. i.e. only the files are replaced for the old media's ID and post meta in the database.
623 wp_update_attachment_metadata( $old_attachment_id, $old_media_post_meta_updated );
624 // Delete the newly uploaded media file and it's sub-sizes, and also delete post and post meta entries for it in the database.
625 wp_delete_attachment( $new_attachment_id, true );
626 // Add old attachment ID to recently replaced media option. This will be used for cache busting to ensure the new replacement images are immediately loaded in the browser / wp-admin
627 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
628 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
629 if ( count( $recently_replaced_media ) >= 5 ) {
630 // Remove first/oldest attachment ID
631 array_shift( $recently_replaced_media );
632 }
633 $recently_replaced_media[] = $old_attachment_id;
634 $recently_replaced_media = array_unique( $recently_replaced_media );
635 $extra_options['recently_replaced_media'] = $recently_replaced_media;
636 update_option( 'admin_site_enhancements_extra', $extra_options );
637 }
638
639 }
640
641 /**
642 * Delete the existing/old media files when performing media replacement
643 *
644 * @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L80
645 * @since 1.1.0
646 */
647 public function delete_media_files( $post_id )
648 {
649 $attachment_meta = wp_get_attachment_metadata( $post_id );
650 // Will get '-scaled' version if it exists, e.g. /path/to/uploads/year/month/file-name.jpg
651 $attachment_file_path = get_attached_file( $post_id );
652 // e.g. file-name.jpg
653 $attachment_file_basename = basename( $attachment_file_path );
654 // Delete intermediate images if there are any
655 if ( isset( $attachment_meta['sizes'] ) && is_array( $attachment_meta['sizes'] ) ) {
656 foreach ( $attachment_meta['sizes'] as $size => $size_info ) {
657 // /path/to/uploads/year/month/file-name.jpg --> /path/to/uploads/year/month/file-name-1024x768.jpg
658 $intermediate_file_path = str_replace( $attachment_file_basename, $size_info['file'], $attachment_file_path );
659 wp_delete_file( $intermediate_file_path );
660 }
661 }
662 // Delete the attachment file, which maybe the '-scaled' version
663 wp_delete_file( $attachment_file_path );
664 // If original file is larger than 2560 pixel
665 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
666
667 if ( array_key_exists( 'original_image', $attachment_meta ) ) {
668 $attachment_original_file_path = wp_get_original_image_path( $post_id );
669 // Delete the original file
670 wp_delete_file( $attachment_original_file_path );
671 }
672
673 }
674
675 /**
676 * Customize the attachment updated message
677 *
678 * @link https://github.com/WordPress/wordpress-develop/blob/6.0.2/src/wp-admin/edit-form-advanced.php#L180
679 * @since 1.1.0
680 */
681 public function attachment_updated_custom_message( $messages )
682 {
683 $new_messages = array();
684 foreach ( $messages as $post_type => $messages_array ) {
685 if ( $post_type == 'attachment' ) {
686 // Message ID for successful edit/update of an attachment is 4. e.g. /wp-admin/post.php?post=a&action=edit&classic-editor&message=4 Customize it here.
687 $messages_array[4] = 'Media file updated. You may need to <a href="https://fabricdigital.co.nz/blog/how-to-hard-refresh-your-browser-and-clear-cache" target="_blank">hard refresh</a> your browser to see the updated media preview image below.';
688 }
689 $new_messages[$post_type] = $messages_array;
690 }
691 return $new_messages;
692 }
693
694 /**
695 * Append cache busting parameter to the end of image srcset
696 *
697 * @since 5.7.0
698 */
699 public function append_cache_busting_param_to_image_srcset(
700 $sources,
701 $size_array,
702 $image_src,
703 $image_meta,
704 $attachment_id
705 )
706 {
707 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
708 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
709 if ( is_admin() && in_array( $attachment_id, $recently_replaced_media ) ) {
710 foreach ( $sources as $size => $source ) {
711 $source['url'] .= (( false === strpos( $source['url'], '?' ) ? '?' : '&' )) . 't=' . time();
712 $sources[$size] = $source;
713 }
714 }
715 return $sources;
716 }
717
718 /**
719 * Append cache busting parameter to the end of image src
720 *
721 * @since 5.7.0
722 */
723 public function append_cache_busting_param_to_attachment_image_src( $image, $attachment_id )
724 {
725 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
726 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
727 if ( is_admin() && !empty($image[0]) && in_array( $attachment_id, $recently_replaced_media ) ) {
728 $image[0] .= (( false === strpos( $image[0], '?' ) ? '?' : '&' )) . 't=' . time();
729 }
730 return $image;
731 }
732
733 /**
734 * Append cache busting parameter to image src for js
735 *
736 * @since 5.7.0
737 */
738 public function append_cache_busting_param_to_attachment_for_js( $response, $attachment )
739 {
740 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
741 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
742
743 if ( is_admin() && in_array( $attachment->ID, $recently_replaced_media ) ) {
744 if ( false !== strpos( $response['url'], '?' ) ) {
745 $response['url'] .= (( false === strpos( $response['url'], '?' ) ? '?' : '&' )) . 't=' . time();
746 }
747 if ( isset( $response['sizes'] ) ) {
748 foreach ( $response['sizes'] as $size_name => $size ) {
749 $response['sizes'][$size_name]['url'] .= (( false === strpos( $size['url'], '?' ) ? '?' : '&' )) . 't=' . time();
750 }
751 }
752 }
753
754 return $response;
755 }
756
757 /**
758 * Add SVG mime type for media library uploads
759 *
760 * @link https://developer.wordpress.org/reference/hooks/upload_mimes/
761 * @since 2.6.0
762 */
763 public function add_svg_mime( $mimes )
764 {
765 global $roles_svg_upload_enabled ;
766 $current_user = wp_get_current_user();
767 $current_user_roles = (array) $current_user->roles;
768 // single dimensional array of role slugs
769 if ( count( $roles_svg_upload_enabled ) > 0 ) {
770 // Add mime type for user roles set to enable SVG upload
771 foreach ( $current_user_roles as $role ) {
772 if ( in_array( $role, $roles_svg_upload_enabled ) ) {
773 $mimes['svg'] = 'image/svg+xml';
774 }
775 }
776 }
777 return $mimes;
778 }
779
780 /**
781 * Check and confirm if the real file type is indeed SVG
782 *
783 * @link https://developer.wordpress.org/reference/functions/wp_check_filetype_and_ext/
784 * @since 2.6.0
785 */
786 public function confirm_file_type_is_svg(
787 $filetypes_extensions,
788 $file,
789 $filename,
790 $mimes
791 )
792 {
793 global $roles_svg_upload_enabled ;
794 $current_user = wp_get_current_user();
795 $current_user_roles = (array) $current_user->roles;
796 // single dimensional array of role slugs
797 if ( count( $roles_svg_upload_enabled ) > 0 ) {
798 // Check file extension
799 if ( substr( $filename, -4 ) == '.svg' ) {
800 // Add mime type for user roles set to enable SVG upload
801 foreach ( $current_user_roles as $role ) {
802
803 if ( in_array( $role, $roles_svg_upload_enabled ) ) {
804 $filetypes_extensions['type'] = 'image/svg+xml';
805 $filetypes_extensions['ext'] = 'svg';
806 }
807
808 }
809 }
810 }
811 return $filetypes_extensions;
812 }
813
814 /**
815 * Sanitize the SVG file and maybe allow upload based on the result
816 *
817 * @since 2.6.0
818 */
819 public function sanitize_and_maybe_allow_svg_upload( $file )
820 {
821 if ( !isset( $file['tmp_name'] ) ) {
822 return $file;
823 }
824 $file_tmp_name = $file['tmp_name'];
825 // full path
826 $file_name = ( isset( $file['name'] ) ? $file['name'] : '' );
827 $file_type_ext = wp_check_filetype_and_ext( $file_tmp_name, $file_name );
828 $file_type = ( !empty($file_type_ext['type']) ? $file_type_ext['type'] : '' );
829 // Load sanitizer library - https://github.com/darylldoyle/svg-sanitizer
830 $sanitizer = new Sanitizer();
831
832 if ( 'image/svg+xml' === $file_type ) {
833 $original_svg = file_get_contents( $file_tmp_name );
834 $sanitized_svg = $sanitizer->sanitize( $original_svg );
835 // boolean
836 if ( false === $sanitized_svg ) {
837 $file['error'] = 'This SVG file could not be sanitized, so, was not uploaded for security reasons.';
838 }
839 file_put_contents( $file_tmp_name, $sanitized_svg );
840 }
841
842 return $file;
843 }
844
845 /**
846 * Generate metadata for the svg attachment
847 *
848 * @link https://developer.wordpress.org/reference/functions/wp_generate_attachment_metadata/
849 * @since 2.6.0
850 */
851 public function generate_svg_metadata( $metadata, $attachment_id, $context )
852 {
853
854 if ( get_post_mime_type( $attachment_id ) == 'image/svg+xml' ) {
855 // Get SVG dimensions
856 $svg_path = get_attached_file( $attachment_id );
857 $svg = simplexml_load_file( $svg_path );
858 $width = 0;
859 $height = 0;
860
861 if ( $svg ) {
862 $attributes = $svg->attributes();
863
864 if ( isset( $attributes->width, $attributes->height ) ) {
865 $width = intval( floatval( $attributes->width ) );
866 $height = intval( floatval( $attributes->height ) );
867 } elseif ( isset( $attributes->viewBox ) ) {
868 $sizes = explode( ' ', $attributes->viewBox );
869
870 if ( isset( $sizes[2], $sizes[3] ) ) {
871 $width = intval( floatval( $sizes[2] ) );
872 $height = intval( floatval( $sizes[3] ) );
873 }
874
875 }
876
877 }
878
879 $metadata['width'] = $width;
880 $metadata['height'] = $height;
881 // Get SVG filename
882 $svg_url = wp_get_original_image_url( $attachment_id );
883 $svg_url_path = str_replace( wp_upload_dir()['baseurl'] . '/', '', $svg_url );
884 $metadata['file'] = $svg_url_path;
885 }
886
887 return $metadata;
888 }
889
890 /**
891 * Return svg file URL to show preview in media library
892 *
893 * @link https://developer.wordpress.org/reference/hooks/wp_ajax_action/
894 * @link https://developer.wordpress.org/reference/functions/wp_get_attachment_url/
895 * @since 2.6.0
896 */
897 public function get_svg_attachment_url()
898 {
899 $attachment_url = '';
900 $attachment_id = ( isset( $_REQUEST['attachmentID'] ) ? $_REQUEST['attachmentID'] : '' );
901 // Check response mime type
902
903 if ( $attachment_id ) {
904 $attachment_url = wp_get_attachment_url( $attachment_id );
905 echo $attachment_url ;
906 die;
907 }
908
909 }
910
911 /**
912 * Return svg file URL to show preview in media library
913 *
914 * @link https://developer.wordpress.org/reference/functions/wp_prepare_attachment_for_js/
915 * @since 2.6.0
916 */
917 public function get_svg_url_in_media_library( $response )
918 {
919 // Check response mime type
920 if ( $response['mime'] === 'image/svg+xml' ) {
921 $response['image'] = array(
922 'src' => $response['url'],
923 );
924 }
925 return $response;
926 }
927
928 /**
929 * Add external permalink meta box for enabled post types
930 *
931 * @since 3.9.0
932 */
933 public function add_external_permalink_meta_box( $post_type, $post )
934 {
935 $options = get_option( ASENHA_SLUG_U, array() );
936 $enable_external_permalinks_for = $options['enable_external_permalinks_for'];
937 foreach ( $enable_external_permalinks_for as $post_type_slug => $is_external_permalink_enabled ) {
938 if ( get_post_type() == $post_type_slug && $is_external_permalink_enabled ) {
939 // Skip adding meta box for post types where Gutenberg is enabled
940 // if (
941 // function_exists( 'use_block_editor_for_post_type' )
942 // && use_block_editor_for_post_type( $post_type_slug )
943 // ) {
944 // continue; // go to the beginning of next iteration
945 // }
946 add_meta_box(
947 'asenha-external-permalink',
948 // ID of meta box
949 'External Permalink',
950 // Title of meta box
951 [ $this, 'output_external_permalink_meta_box' ],
952 // Callback function
953 $post_type_slug,
954 // The screen on which the meta box should be output to
955 'normal',
956 // context
957 'high'
958 );
959 }
960 }
961 }
962
963 /**
964 * Render External Permalink meta box
965 *
966 * @since 3.9.0
967 */
968 public function output_external_permalink_meta_box( $post )
969 {
970 ?>
971 <div class="external-permalink-input">
972 <input name="<?php
973 echo esc_attr( 'external_permalink' ) ;
974 ?>" class="large-text" id="<?php
975 echo esc_attr( 'external_permalink' ) ;
976 ?>" type="text" value="<?php
977 echo esc_url( get_post_meta( $post->ID, '_links_to', true ) ) ;
978 ?>" placeholder="https://" />
979 <div class="external-permalink-input-description">Keep empty to use the default WordPress permalink. External permalink will open in a new browser tab.</div>
980 <?php
981 wp_nonce_field(
982 'external_permalink_' . $post->ID,
983 'external_permalink_nonce',
984 false,
985 true
986 );
987 ?>
988 </div>
989 <?php
990 }
991
992 /**
993 * Save external permalink input
994 *
995 * @since 3.9.0
996 */
997 public function save_external_permalink( $post_id )
998 {
999 // Only proceed if nonce is verified
1000
1001 if ( isset( $_POST['external_permalink_nonce'] ) && wp_verify_nonce( $_POST['external_permalink_nonce'], 'external_permalink_' . $post_id ) ) {
1002 // Get the value of external permalink from input field
1003 $external_permalink = ( isset( $_POST['external_permalink'] ) ? esc_url_raw( trim( $_POST['external_permalink'] ) ) : '' );
1004 // Update or delete external permalink post meta
1005
1006 if ( !empty($external_permalink) ) {
1007 update_post_meta( $post_id, '_links_to', $external_permalink );
1008 } else {
1009 delete_post_meta( $post_id, '_links_to' );
1010 }
1011
1012 }
1013
1014 }
1015
1016 /**
1017 * Change WordPress default permalink into external permalink for pages
1018 *
1019 * @since 3.9.0
1020 */
1021 public function use_external_permalink_for_pages( $permalink, $post_id )
1022 {
1023 $external_permalink = get_post_meta( $post_id, '_links_to', true );
1024 if ( !empty($external_permalink) ) {
1025 $permalink = $external_permalink;
1026 }
1027 return $permalink;
1028 }
1029
1030 /**
1031 * Change WordPress default permalink into external permalink for posts and custom post types
1032 *
1033 * @since 3.9.0
1034 */
1035 public function use_external_permalink_for_posts( $permalink, $post )
1036 {
1037 $external_permalink = get_post_meta( $post->ID, '_links_to', true );
1038
1039 if ( !empty($external_permalink) ) {
1040 $permalink = $external_permalink;
1041 if ( !is_admin() ) {
1042 $permalink = $permalink . '#new_tab';
1043 }
1044 }
1045
1046 return $permalink;
1047 }
1048
1049 /**
1050 * Redirect page/post to external permalink if it's loaded directly from the WP default permalink
1051 *
1052 * @since 3.9.0
1053 */
1054 public function redirect_to_external_permalink()
1055 {
1056 global $post ;
1057 // If not on/loading the single page/post URL, do nothing
1058 if ( !is_singular() ) {
1059 return;
1060 }
1061 $external_permalink = get_post_meta( $post->ID, '_links_to', true );
1062
1063 if ( !empty($external_permalink) ) {
1064 wp_redirect( $external_permalink, 302 );
1065 // temporary redirect
1066 exit;
1067 }
1068
1069 }
1070
1071 /**
1072 * Parse links in content to add target="_blank" rel="noopener noreferrer nofollow" attributes
1073 *
1074 * @since 4.9.0
1075 */
1076 public function add_target_and_rel_atts_to_content_links( $content )
1077 {
1078
1079 if ( !empty($content) ) {
1080 // regex pattern for "a href"
1081 $regexp = "<a\\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>";
1082
1083 if ( preg_match_all(
1084 "/{$regexp}/siU",
1085 $content,
1086 $matches,
1087 PREG_SET_ORDER
1088 ) ) {
1089 // $matches might contain parts of $content that has links (a href)
1090 preg_match_all(
1091 "/{$regexp}/siU",
1092 $content,
1093 $matches,
1094 PREG_SET_ORDER
1095 );
1096
1097 if ( is_array( $matches ) ) {
1098 $i = 0;
1099 foreach ( $matches as $match ) {
1100 $original_tag = $match[0];
1101 // e.g. <a title="Link Title" href="http://www.example.com/sit-quaerat">
1102 $tag = $match[0];
1103 // Same value as $original_tag but for further processing
1104 $url = $match[2];
1105 // e.g. http://www.example.com/sit-quaerat
1106
1107 if ( false !== strpos( $url, get_site_url() ) ) {
1108 // Internal link. Do nothing.
1109 } elseif ( false === strpos( $url, 'http' ) ) {
1110 // Relative link to internal URL. Do nothing.
1111 } else {
1112 // External link. Let's do something.
1113 // Regex pattern for target="_blank|parent|self|top"
1114 $pattern = '/target\\s*=\\s*"\\s*_(blank|parent|self|top)\\s*"/';
1115 // If there's no 'target="_blank|parent|self|top"' in $tag, add target="blank"
1116 if ( 0 === preg_match( $pattern, $tag ) ) {
1117 // Replace closing > with ' target="_blank">'
1118 $tag = substr_replace( $tag, ' target="_blank">', -1 );
1119 }
1120 // If there's no 'rel' attribute in $tag, add rel="noopener noreferrer nofollow"
1121 $pattern = '/rel\\s*=\\s*\\"[a-zA-Z0-9_\\s]*\\"/';
1122
1123 if ( 0 === preg_match( $pattern, $tag ) ) {
1124 // Replace closing > with ' rel="noopener noreferrer nofollow">'
1125 $tag = substr_replace( $tag, ' rel="noopener noreferrer nofollow">', -1 );
1126 } else {
1127 // replace rel="noopener" with rel="noopener noreferrer nofollow"
1128 if ( false !== strpos( $tag, 'noopener' ) && false === strpos( $tag, 'noreferrer' ) && false === strpos( $tag, 'nofollow' ) ) {
1129 $tag = str_replace( 'noopener', 'noopener noreferrer nofollow', $tag );
1130 }
1131 }
1132
1133 // Replace original a href tag with one containing target and rel attributes above
1134 $content = str_replace( $original_tag, $tag, $content );
1135 }
1136
1137 $i++;
1138 }
1139 }
1140
1141 }
1142
1143 }
1144
1145 return $content;
1146 }
1147
1148 /**
1149 * Add "open in new tab" checkbox in custom nav menu item settings
1150 *
1151 * @since 5.4.0
1152 */
1153 public function add_custom_nav_menu_open_in_new_tab_field(
1154 $item_id,
1155 $menu_item,
1156 $depth,
1157 $args
1158 )
1159 {
1160 $target_blank = get_post_meta( $item_id, '_menu_item_target_blank', true );
1161
1162 if ( 'custom' == $menu_item->object ) {
1163 ?>
1164 <p class="field-target_blank description-wide">
1165 <label for="edit-menu-item-target-blank-<?php
1166 echo esc_attr( $item_id ) ;
1167 ?>">
1168 <input type="checkbox" id="edit-menu-item-target-blank-<?php
1169 echo esc_attr( $item_id ) ;
1170 ?>" name="menu-item-target-blank[<?php
1171 echo esc_attr( $item_id ) ;
1172 ?>]" value="1" <?php
1173 checked( $target_blank, '1' );
1174 ?> />
1175 Open link in new tab and add rel="noopener noreferrer nofollow" attribute.
1176 </label>
1177 </p>
1178 <?php
1179 }
1180
1181 }
1182
1183 /**
1184 * Save status of "open in new tab" checkbox in custom nav menu item settings
1185 *
1186 * @since 5.4.0
1187 */
1188 public function save_custom_nav_menu_open_in_new_tab_status( $menu_id, $menu_item_db_id, $args )
1189 {
1190
1191 if ( isset( $_POST['menu-item-target-blank'][$menu_item_db_id] ) ) {
1192 update_post_meta( $menu_item_db_id, '_menu_item_target_blank', '1' );
1193 } else {
1194 delete_post_meta( $menu_item_db_id, '_menu_item_target_blank' );
1195 }
1196
1197 }
1198
1199 /**
1200 * Add attributes to custom nav menu item on the frontend
1201 *
1202 * @since 5.4.0
1203 */
1204 public function add_attributes_to_custom_nav_menu_item( $atts, $menu_item, $args )
1205 {
1206 $target_blank = get_post_meta( $menu_item->ID, '_menu_item_target_blank', true );
1207
1208 if ( $target_blank ) {
1209 $atts['target'] = '_blank';
1210 $atts['rel'] = 'noopener noreferrer nofollow';
1211 }
1212
1213 return $atts;
1214 }
1215
1216 /**
1217 * Publish posts of any type with missed schedule.
1218 * We use the Transients API to reduce straining the site with DB queries on busy sites.
1219 * So, this function will only query the DB once every 15 minutes at most.
1220 *
1221 * @since 3.1.0
1222 */
1223 public function publish_missed_schedule_posts()
1224 {
1225
1226 if ( is_front_page() || is_home() || is_page() || is_single() || is_singular() || is_archive() || is_admin() || is_blog_admin() || is_robots() || is_ssl() ) {
1227 // Get missed schedule posts data from cache
1228 $missed_schedule_posts = get_transient( 'asenha_missed_schedule_posts' );
1229 // Nothing found in cache
1230
1231 if ( false === $missed_schedule_posts ) {
1232 global $wpdb ;
1233 $current_gmt_datetime = gmdate( 'Y-m-d H:i:00' );
1234 $args = array(
1235 'public' => true,
1236 '_builtin' => false,
1237 );
1238 $custom_post_types = get_post_types( $args, 'names' );
1239 // array, e.g. array( 'project', 'book', 'staff' )
1240
1241 if ( count( $custom_post_types ) > 0 ) {
1242 $custom_post_types = "'" . implode( "','", $custom_post_types ) . "'";
1243 // string, e.g. 'project','book','staff'
1244 $post_types = "'page','post'," . $custom_post_types;
1245 // 'page','post','project','book','staff'
1246 } else {
1247 $post_types = "'page','post'";
1248 }
1249
1250 $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ({$post_types}) AND post_status='future' AND post_date_gmt<'{$current_gmt_datetime}'";
1251 // The following does not work as backslashes are inserted before single quotes in $post_types
1252 // $sql = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type IN (%s) AND post_status='future' AND post_date_gmt<'%s'", array( $post_types, $current_gmt_datetime ) );
1253 $missed_schedule_posts = $wpdb->get_results( $sql, ARRAY_A );
1254 // Save query results as a transient with expiry of 15 minutes
1255 set_transient( 'asenha_missed_schedule_posts', $missed_schedule_posts, 15 * MINUTE_IN_SECONDS );
1256 }
1257
1258 if ( empty($missed_schedule_posts) || !is_array( $missed_schedule_posts ) ) {
1259 return;
1260 }
1261 foreach ( $missed_schedule_posts as $post ) {
1262 wp_publish_post( $post['ID'] );
1263 }
1264 }
1265
1266 }
1267
1268 }