PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 6.1.0
Admin and Site Enhancements (ASE) v6.1.0
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
1272 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' => str_replace( '\\', "\\\\", $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 if ( is_array( $taxonomy_terms ) && !empty($taxonomy_terms) ) {
314 foreach ( $taxonomy_terms as $term ) {
315 $terms[] = $term->name;
316 }
317 }
318 }
319
320 $terms = implode( ', ', $terms );
321 $taxonomies_and_terms .= ' | ' . $taxonomy->label . ': ' . $terms;
322 }
323 if ( !empty($taxonomies_and_terms) ) {
324 $taxonomies_and_terms = '<span class="item-taxonomy-terms">' . $taxonomies_and_terms . '</span>';
325 }
326 ?>
327 <li id="list_<?php
328 echo $post->ID ;
329 ?>" data-id="<?php
330 echo $post->ID ;
331 ?>" data-menu-order="<?php
332 echo $post->menu_order ;
333 ?>" data-parent="<?php
334 echo $post->post_parent ;
335 ?>" data-has-child="<?php
336 echo $has_child ;
337 ?>" data-post-type="<?php
338 echo $post->post_type ;
339 ?>">
340 <div class="row">
341 <div class="row-content">
342 <?php
343 echo '<div class="content-main">
344 <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>
345 </div>
346 <div class="content-additional">
347 <a href="' . get_the_permalink( $post->ID ) . '" target="_blank" class="button item-view-link">View</a>
348 </div>' ;
349 ?>
350 </div>
351 </div>
352 </li>
353 <?php
354 }
355
356 /**
357 * Enqueue styles for content order pages
358 *
359 * @since 5.0.0
360 */
361 public function enqueue_content_order_styles()
362 {
363 wp_enqueue_style(
364 'content-order-style',
365 ASENHA_URL . 'assets/css/content-order.css',
366 array(),
367 ASENHA_VERSION
368 );
369 }
370
371 /**
372 * Enqueue scripts for content order pages
373 *
374 * @since 5.0.0
375 */
376 public function enqueue_content_order_scripts()
377 {
378 global $typenow ;
379 wp_enqueue_script(
380 'content-order-jquery-ui-touch-punch',
381 ASENHA_URL . 'assets/js/jquery.ui.touch-punch.min.js',
382 array( 'jquery-ui-sortable' ),
383 '0.2.3',
384 true
385 );
386 wp_register_script(
387 'content-order-nested-sortable',
388 ASENHA_URL . 'assets/js/jquery.mjs.nestedSortable.js',
389 array( 'content-order-jquery-ui-touch-punch' ),
390 '2.0.0',
391 true
392 );
393 wp_enqueue_script(
394 'content-order-sort',
395 ASENHA_URL . 'assets/js/content-order-sort.js',
396 array( 'content-order-nested-sortable' ),
397 ASENHA_VERSION,
398 true
399 );
400 wp_localize_script( 'content-order-sort', 'contentOrderSort', array(
401 'action' => 'save_custom_order',
402 'nonce' => wp_create_nonce( 'order_sorting_nonce' ),
403 'hirarchical' => ( is_post_type_hierarchical( $typenow ) ? 'true' : 'false' ),
404 ) );
405 }
406
407 /**
408 * Save custom content order coming from ajax call
409 *
410 * @since 5.0.0
411 */
412 public function save_custom_content_order()
413 {
414 global $wpdb ;
415 // Check user capabilities
416 if ( !current_user_can( 'edit_pages' ) ) {
417 wp_send_json( 'Something went wrong.' );
418 }
419 // Verify nonce
420 if ( !wp_verify_nonce( $_POST['nonce'], 'order_sorting_nonce' ) ) {
421 wp_send_json( 'Something went wrong.' );
422 }
423 // Get ajax variables
424 $action = ( isset( $_POST['action'] ) ? $_POST['action'] : '' );
425 $item_parent = ( isset( $_POST['item_parent'] ) ? absint( $_POST['item_parent'] ) : 0 );
426 $menu_order_start = ( isset( $_POST['start'] ) ? absint( $_POST['start'] ) : 0 );
427 $post_id = ( isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0 );
428 $item_menu_order = ( isset( $_POST['menu_order'] ) ? absint( $_POST['menu_order'] ) : 0 );
429 $items_to_exclude = ( isset( $_POST['excluded_items'] ) ? absint( $_POST['excluded_items'] ) : array() );
430 $post_type = ( isset( $_POST['post_type'] ) ? $_POST['post_type'] : false );
431 // Make processing faster by removing certain actions
432 remove_action( 'pre_post_update', 'wp_save_post_revision' );
433 // $response array for ajax response
434 $response = array();
435 // Update the item whose order/position was moved
436
437 if ( $post_id > 0 && !isset( $_POST['more_posts'] ) ) {
438 // https://developer.wordpress.org/reference/classes/wpdb/update/
439 $wpdb->update(
440 $wpdb->posts,
441 // The table
442 array(
443 'menu_order' => $item_menu_order,
444 ),
445 array(
446 'ID' => $post_id,
447 )
448 );
449 clean_post_cache( $post_id );
450 $items_to_exclude[] = $post_id;
451 }
452
453 // Get all posts from the post type related to ajax request
454 $query_args = array(
455 'post_type' => $post_type,
456 'orderby' => 'menu_order title',
457 'order' => 'ASC',
458 'posts_per_page' => -1,
459 'suppress_filters' => true,
460 'ignore_sticky_posts' => true,
461 'post_status' => array(
462 'publish',
463 'future',
464 'draft',
465 'pending',
466 'private'
467 ),
468 'post_parent' => $item_parent,
469 'post__not_in' => $items_to_exclude,
470 'update_post_term_cache' => false,
471 'update_post_meta_cache' => false,
472 );
473 $posts = new WP_Query( $query_args );
474
475 if ( $posts->have_posts() ) {
476 // Iterate through posts and update menu order and post parent
477 foreach ( $posts->posts as $post ) {
478 // If the $post is the one being displaced (shited downward) by the moved item, increment it's menu_order by one
479 if ( $menu_order_start == $item_menu_order && $post_id > 0 ) {
480 $menu_order_start++;
481 }
482 // Only process posts other than the moved item, which has been processed earlier outside this loop
483
484 if ( $post_id != $post->ID ) {
485 // Update menu_order
486 $wpdb->update( $wpdb->posts, array(
487 'menu_order' => $menu_order_start,
488 ), array(
489 'ID' => $post->ID,
490 ) );
491 clean_post_cache( $post->ID );
492 }
493
494 $items_to_exclude[] = $post->ID;
495 $menu_order_start++;
496 }
497 die( json_encode( $response ) );
498 } else {
499 die( json_encode( $response ) );
500 }
501
502 }
503
504 /**
505 * Set default ordering of list tables of sortable post types by 'menu_order'
506 *
507 * @link https://developer.wordpress.org/reference/classes/wp_query/#methods
508 * @since 5.0.0
509 */
510 public function orderby_menu_order( $query )
511 {
512 global $pagenow, $typenow ;
513 $options = get_option( ASENHA_SLUG_U, array() );
514 $content_order_for = $options['content_order_for'];
515 $content_order_enabled_post_types = array();
516 foreach ( $options['content_order_for'] as $post_type_slug => $is_custom_order_enabled ) {
517 if ( $is_custom_order_enabled ) {
518 $content_order_enabled_post_types[] = $post_type_slug;
519 }
520 }
521 // Use custom order in wp-admin listing pages/tables for enabled post types
522 if ( is_admin() && 'edit.php' == $pagenow && !isset( $_GET['orderby'] ) ) {
523
524 if ( in_array( $typenow, $content_order_enabled_post_types ) && (post_type_supports( $typenow, 'page-attributes' ) || is_post_type_hierarchical( $typenow )) ) {
525 $query->set( 'orderby', 'menu_order title' );
526 $query->set( 'order', 'ASC' );
527 }
528
529 }
530 }
531
532 /**
533 * Add media replacement button in the edit screen of media/attachment
534 *
535 * @since 1.1.0
536 */
537 public function add_media_replacement_button( $fields, $post )
538 {
539 // Enqueues all scripts, styles, settings, and templates necessary to use all media JS APIs.
540 // Reference: https://codex.wordpress.org/Javascript_Reference/wp.media
541 wp_enqueue_media();
542 // Add new field to attachment fields for the media replace functionality
543 $fields['asenha-media-replace'] = array();
544 $fields['asenha-media-replace']['label'] = '';
545 $fields['asenha-media-replace']['input'] = 'html';
546 $fields['asenha-media-replace']['html'] = '
547 <div id="media-replace-div" class="postbox">
548 <div class="postbox-header">
549 <h2 class="hndle ui-sortable-handle">Replace Media</h2>
550 </div>
551 <div class="inside">
552 <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>
553 <input type="hidden" id="new-attachment-id" name="new-attachment-id" />
554 <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>
555 </div>
556 </div>
557 ';
558 return $fields;
559 }
560
561 /**
562 * Replace existing media with the newly updated file
563 *
564 * @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L55
565 * @since 1.1.0
566 */
567 public function replace_media( $old_attachment_id )
568 {
569 $old_post_meta = get_post( $old_attachment_id, ARRAY_A );
570 $old_post_mime = $old_post_meta['post_mime_type'];
571 // e.g. 'image/jpeg'
572 // Get the new attachment/media ID, meta and mime type
573
574 if ( isset( $_POST['new-attachment-id'] ) && !empty($_POST['new-attachment-id']) ) {
575 $new_attachment_id = intval( sanitize_text_field( $_POST['new-attachment-id'] ) );
576 $new_post_meta = get_post( $new_attachment_id, ARRAY_A );
577 $new_post_mime = $new_post_meta['post_mime_type'];
578 // e.g. 'image/jpeg'
579 }
580
581 // Check if the media file ID selected via the media frame and passed on to the #new-attachment-id hidden field
582 // Ensure the mime type matches too
583
584 if ( !empty($new_attachment_id) && is_numeric( $new_attachment_id ) && $old_post_mime == $new_post_mime ) {
585 $new_attachment_meta = wp_get_attachment_metadata( $new_attachment_id );
586 // If original file is larger than 2560 pixel
587 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
588
589 if ( array_key_exists( 'original_image', $new_attachment_meta ) ) {
590 // Get the original media file path
591 $new_media_file_path = wp_get_original_image_path( $new_attachment_id );
592 } else {
593 // Get the path to newly uploaded media file. An image file name may end with '-scaled'.
594 $new_attachment_file = get_post_meta( $new_attachment_id, '_wp_attached_file', true );
595 $upload_dir = wp_upload_dir();
596 $new_media_file_path = $upload_dir['basedir'] . '/' . $new_attachment_file;
597 }
598
599 // Check if the new media file exist / was successfully uploaded
600 if ( !is_file( $new_media_file_path ) ) {
601 return false;
602 }
603 // Delete existing/old media files. Post and post meta entries for it are still there in the database.
604 $this->delete_media_files( $old_attachment_id );
605 // If original file is larger than 2560 pixel
606 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
607
608 if ( array_key_exists( 'original_image', $new_attachment_meta ) ) {
609 // Get the original media file path
610 $old_media_file_path = wp_get_original_image_path( $old_attachment_id );
611 } else {
612 // Get the path to the old/existing media file that will be replaced and deleted. An image file name may end with '-scaled'.
613 $old_attachment_file = get_post_meta( $old_attachment_id, '_wp_attached_file', true );
614 $old_media_file_path = $upload_dir['basedir'] . '/' . $old_attachment_file;
615 }
616
617 // Check if the directory path to the old media file is still intact
618 if ( !file_exists( dirname( $old_media_file_path ) ) ) {
619 // Recreate the directory path
620 mkdir( dirname( $old_media_file_path ), 0755, true );
621 }
622 // Copy the new media file into the old media file's path
623 copy( $new_media_file_path, $old_media_file_path );
624 // Regenerate attachment meta data and image sub-sizes from the new media file that was just copied to the old path
625 $old_media_post_meta_updated = wp_generate_attachment_metadata( $old_attachment_id, $old_media_file_path );
626 // 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.
627 wp_update_attachment_metadata( $old_attachment_id, $old_media_post_meta_updated );
628 // Delete the newly uploaded media file and it's sub-sizes, and also delete post and post meta entries for it in the database.
629 wp_delete_attachment( $new_attachment_id, true );
630 // 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
631 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
632 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
633 if ( count( $recently_replaced_media ) >= 5 ) {
634 // Remove first/oldest attachment ID
635 array_shift( $recently_replaced_media );
636 }
637 $recently_replaced_media[] = $old_attachment_id;
638 $recently_replaced_media = array_unique( $recently_replaced_media );
639 $extra_options['recently_replaced_media'] = $recently_replaced_media;
640 update_option( 'admin_site_enhancements_extra', $extra_options );
641 }
642
643 }
644
645 /**
646 * Delete the existing/old media files when performing media replacement
647 *
648 * @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L80
649 * @since 1.1.0
650 */
651 public function delete_media_files( $post_id )
652 {
653 $attachment_meta = wp_get_attachment_metadata( $post_id );
654 // Will get '-scaled' version if it exists, e.g. /path/to/uploads/year/month/file-name.jpg
655 $attachment_file_path = get_attached_file( $post_id );
656 // e.g. file-name.jpg
657 $attachment_file_basename = basename( $attachment_file_path );
658 // Delete intermediate images if there are any
659 if ( isset( $attachment_meta['sizes'] ) && is_array( $attachment_meta['sizes'] ) ) {
660 foreach ( $attachment_meta['sizes'] as $size => $size_info ) {
661 // /path/to/uploads/year/month/file-name.jpg --> /path/to/uploads/year/month/file-name-1024x768.jpg
662 $intermediate_file_path = str_replace( $attachment_file_basename, $size_info['file'], $attachment_file_path );
663 wp_delete_file( $intermediate_file_path );
664 }
665 }
666 // Delete the attachment file, which maybe the '-scaled' version
667 wp_delete_file( $attachment_file_path );
668 // If original file is larger than 2560 pixel
669 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
670
671 if ( array_key_exists( 'original_image', $attachment_meta ) ) {
672 $attachment_original_file_path = wp_get_original_image_path( $post_id );
673 // Delete the original file
674 wp_delete_file( $attachment_original_file_path );
675 }
676
677 }
678
679 /**
680 * Customize the attachment updated message
681 *
682 * @link https://github.com/WordPress/wordpress-develop/blob/6.0.2/src/wp-admin/edit-form-advanced.php#L180
683 * @since 1.1.0
684 */
685 public function attachment_updated_custom_message( $messages )
686 {
687 $new_messages = array();
688 foreach ( $messages as $post_type => $messages_array ) {
689 if ( $post_type == 'attachment' ) {
690 // 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.
691 $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.';
692 }
693 $new_messages[$post_type] = $messages_array;
694 }
695 return $new_messages;
696 }
697
698 /**
699 * Append cache busting parameter to the end of image srcset
700 *
701 * @since 5.7.0
702 */
703 public function append_cache_busting_param_to_image_srcset(
704 $sources,
705 $size_array,
706 $image_src,
707 $image_meta,
708 $attachment_id
709 )
710 {
711 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
712 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
713 if ( is_admin() && in_array( $attachment_id, $recently_replaced_media ) ) {
714 foreach ( $sources as $size => $source ) {
715 $source['url'] .= (( false === strpos( $source['url'], '?' ) ? '?' : '&' )) . 't=' . time();
716 $sources[$size] = $source;
717 }
718 }
719 return $sources;
720 }
721
722 /**
723 * Append cache busting parameter to the end of image src
724 *
725 * @since 5.7.0
726 */
727 public function append_cache_busting_param_to_attachment_image_src( $image, $attachment_id )
728 {
729 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
730 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
731 if ( is_admin() && !empty($image[0]) && in_array( $attachment_id, $recently_replaced_media ) ) {
732 $image[0] .= (( false === strpos( $image[0], '?' ) ? '?' : '&' )) . 't=' . time();
733 }
734 return $image;
735 }
736
737 /**
738 * Append cache busting parameter to image src for js
739 *
740 * @since 5.7.0
741 */
742 public function append_cache_busting_param_to_attachment_for_js( $response, $attachment )
743 {
744 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
745 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
746
747 if ( is_admin() && in_array( $attachment->ID, $recently_replaced_media ) ) {
748 if ( false !== strpos( $response['url'], '?' ) ) {
749 $response['url'] .= (( false === strpos( $response['url'], '?' ) ? '?' : '&' )) . 't=' . time();
750 }
751 if ( isset( $response['sizes'] ) ) {
752 foreach ( $response['sizes'] as $size_name => $size ) {
753 $response['sizes'][$size_name]['url'] .= (( false === strpos( $size['url'], '?' ) ? '?' : '&' )) . 't=' . time();
754 }
755 }
756 }
757
758 return $response;
759 }
760
761 /**
762 * Add SVG mime type for media library uploads
763 *
764 * @link https://developer.wordpress.org/reference/hooks/upload_mimes/
765 * @since 2.6.0
766 */
767 public function add_svg_mime( $mimes )
768 {
769 global $roles_svg_upload_enabled ;
770 $current_user = wp_get_current_user();
771 $current_user_roles = (array) $current_user->roles;
772 // single dimensional array of role slugs
773 if ( count( $roles_svg_upload_enabled ) > 0 ) {
774 // Add mime type for user roles set to enable SVG upload
775 foreach ( $current_user_roles as $role ) {
776 if ( in_array( $role, $roles_svg_upload_enabled ) ) {
777 $mimes['svg'] = 'image/svg+xml';
778 }
779 }
780 }
781 return $mimes;
782 }
783
784 /**
785 * Check and confirm if the real file type is indeed SVG
786 *
787 * @link https://developer.wordpress.org/reference/functions/wp_check_filetype_and_ext/
788 * @since 2.6.0
789 */
790 public function confirm_file_type_is_svg(
791 $filetypes_extensions,
792 $file,
793 $filename,
794 $mimes
795 )
796 {
797 global $roles_svg_upload_enabled ;
798 $current_user = wp_get_current_user();
799 $current_user_roles = (array) $current_user->roles;
800 // single dimensional array of role slugs
801 if ( count( $roles_svg_upload_enabled ) > 0 ) {
802 // Check file extension
803 if ( substr( $filename, -4 ) == '.svg' ) {
804 // Add mime type for user roles set to enable SVG upload
805 foreach ( $current_user_roles as $role ) {
806
807 if ( in_array( $role, $roles_svg_upload_enabled ) ) {
808 $filetypes_extensions['type'] = 'image/svg+xml';
809 $filetypes_extensions['ext'] = 'svg';
810 }
811
812 }
813 }
814 }
815 return $filetypes_extensions;
816 }
817
818 /**
819 * Sanitize the SVG file and maybe allow upload based on the result
820 *
821 * @since 2.6.0
822 */
823 public function sanitize_and_maybe_allow_svg_upload( $file )
824 {
825 if ( !isset( $file['tmp_name'] ) ) {
826 return $file;
827 }
828 $file_tmp_name = $file['tmp_name'];
829 // full path
830 $file_name = ( isset( $file['name'] ) ? $file['name'] : '' );
831 $file_type_ext = wp_check_filetype_and_ext( $file_tmp_name, $file_name );
832 $file_type = ( !empty($file_type_ext['type']) ? $file_type_ext['type'] : '' );
833 // Load sanitizer library - https://github.com/darylldoyle/svg-sanitizer
834 $sanitizer = new Sanitizer();
835
836 if ( 'image/svg+xml' === $file_type ) {
837 $original_svg = file_get_contents( $file_tmp_name );
838 $sanitized_svg = $sanitizer->sanitize( $original_svg );
839 // boolean
840 if ( false === $sanitized_svg ) {
841 $file['error'] = 'This SVG file could not be sanitized, so, was not uploaded for security reasons.';
842 }
843 file_put_contents( $file_tmp_name, $sanitized_svg );
844 }
845
846 return $file;
847 }
848
849 /**
850 * Generate metadata for the svg attachment
851 *
852 * @link https://developer.wordpress.org/reference/functions/wp_generate_attachment_metadata/
853 * @since 2.6.0
854 */
855 public function generate_svg_metadata( $metadata, $attachment_id, $context )
856 {
857
858 if ( get_post_mime_type( $attachment_id ) == 'image/svg+xml' ) {
859 // Get SVG dimensions
860 $svg_path = get_attached_file( $attachment_id );
861 $svg = simplexml_load_file( $svg_path );
862 $width = 0;
863 $height = 0;
864
865 if ( $svg ) {
866 $attributes = $svg->attributes();
867
868 if ( isset( $attributes->width, $attributes->height ) ) {
869 $width = intval( floatval( $attributes->width ) );
870 $height = intval( floatval( $attributes->height ) );
871 } elseif ( isset( $attributes->viewBox ) ) {
872 $sizes = explode( ' ', $attributes->viewBox );
873
874 if ( isset( $sizes[2], $sizes[3] ) ) {
875 $width = intval( floatval( $sizes[2] ) );
876 $height = intval( floatval( $sizes[3] ) );
877 }
878
879 }
880
881 }
882
883 $metadata['width'] = $width;
884 $metadata['height'] = $height;
885 // Get SVG filename
886 $svg_url = wp_get_original_image_url( $attachment_id );
887 $svg_url_path = str_replace( wp_upload_dir()['baseurl'] . '/', '', $svg_url );
888 $metadata['file'] = $svg_url_path;
889 }
890
891 return $metadata;
892 }
893
894 /**
895 * Return svg file URL to show preview in media library
896 *
897 * @link https://developer.wordpress.org/reference/hooks/wp_ajax_action/
898 * @link https://developer.wordpress.org/reference/functions/wp_get_attachment_url/
899 * @since 2.6.0
900 */
901 public function get_svg_attachment_url()
902 {
903 $attachment_url = '';
904 $attachment_id = ( isset( $_REQUEST['attachmentID'] ) ? $_REQUEST['attachmentID'] : '' );
905 // Check response mime type
906
907 if ( $attachment_id ) {
908 $attachment_url = wp_get_attachment_url( $attachment_id );
909 echo $attachment_url ;
910 die;
911 }
912
913 }
914
915 /**
916 * Return svg file URL to show preview in media library
917 *
918 * @link https://developer.wordpress.org/reference/functions/wp_prepare_attachment_for_js/
919 * @since 2.6.0
920 */
921 public function get_svg_url_in_media_library( $response )
922 {
923 // Check response mime type
924 if ( $response['mime'] === 'image/svg+xml' ) {
925 $response['image'] = array(
926 'src' => $response['url'],
927 );
928 }
929 return $response;
930 }
931
932 /**
933 * Add external permalink meta box for enabled post types
934 *
935 * @since 3.9.0
936 */
937 public function add_external_permalink_meta_box( $post_type, $post )
938 {
939 $options = get_option( ASENHA_SLUG_U, array() );
940 $enable_external_permalinks_for = $options['enable_external_permalinks_for'];
941 foreach ( $enable_external_permalinks_for as $post_type_slug => $is_external_permalink_enabled ) {
942 if ( get_post_type() == $post_type_slug && $is_external_permalink_enabled ) {
943 // Skip adding meta box for post types where Gutenberg is enabled
944 // if (
945 // function_exists( 'use_block_editor_for_post_type' )
946 // && use_block_editor_for_post_type( $post_type_slug )
947 // ) {
948 // continue; // go to the beginning of next iteration
949 // }
950 add_meta_box(
951 'asenha-external-permalink',
952 // ID of meta box
953 'External Permalink',
954 // Title of meta box
955 [ $this, 'output_external_permalink_meta_box' ],
956 // Callback function
957 $post_type_slug,
958 // The screen on which the meta box should be output to
959 'normal',
960 // context
961 'high'
962 );
963 }
964 }
965 }
966
967 /**
968 * Render External Permalink meta box
969 *
970 * @since 3.9.0
971 */
972 public function output_external_permalink_meta_box( $post )
973 {
974 ?>
975 <div class="external-permalink-input">
976 <input name="<?php
977 echo esc_attr( 'external_permalink' ) ;
978 ?>" class="large-text" id="<?php
979 echo esc_attr( 'external_permalink' ) ;
980 ?>" type="text" value="<?php
981 echo esc_url( get_post_meta( $post->ID, '_links_to', true ) ) ;
982 ?>" placeholder="https://" />
983 <div class="external-permalink-input-description">Keep empty to use the default WordPress permalink. External permalink will open in a new browser tab.</div>
984 <?php
985 wp_nonce_field(
986 'external_permalink_' . $post->ID,
987 'external_permalink_nonce',
988 false,
989 true
990 );
991 ?>
992 </div>
993 <?php
994 }
995
996 /**
997 * Save external permalink input
998 *
999 * @since 3.9.0
1000 */
1001 public function save_external_permalink( $post_id )
1002 {
1003 // Only proceed if nonce is verified
1004
1005 if ( isset( $_POST['external_permalink_nonce'] ) && wp_verify_nonce( $_POST['external_permalink_nonce'], 'external_permalink_' . $post_id ) ) {
1006 // Get the value of external permalink from input field
1007 $external_permalink = ( isset( $_POST['external_permalink'] ) ? esc_url_raw( trim( $_POST['external_permalink'] ) ) : '' );
1008 // Update or delete external permalink post meta
1009
1010 if ( !empty($external_permalink) ) {
1011 update_post_meta( $post_id, '_links_to', $external_permalink );
1012 } else {
1013 delete_post_meta( $post_id, '_links_to' );
1014 }
1015
1016 }
1017
1018 }
1019
1020 /**
1021 * Change WordPress default permalink into external permalink for pages
1022 *
1023 * @since 3.9.0
1024 */
1025 public function use_external_permalink_for_pages( $permalink, $post_id )
1026 {
1027 $external_permalink = get_post_meta( $post_id, '_links_to', true );
1028 if ( !empty($external_permalink) ) {
1029 $permalink = $external_permalink;
1030 }
1031 return $permalink;
1032 }
1033
1034 /**
1035 * Change WordPress default permalink into external permalink for posts and custom post types
1036 *
1037 * @since 3.9.0
1038 */
1039 public function use_external_permalink_for_posts( $permalink, $post )
1040 {
1041 $external_permalink = get_post_meta( $post->ID, '_links_to', true );
1042
1043 if ( !empty($external_permalink) ) {
1044 $permalink = $external_permalink;
1045 if ( !is_admin() ) {
1046 $permalink = $permalink . '#new_tab';
1047 }
1048 }
1049
1050 return $permalink;
1051 }
1052
1053 /**
1054 * Redirect page/post to external permalink if it's loaded directly from the WP default permalink
1055 *
1056 * @since 3.9.0
1057 */
1058 public function redirect_to_external_permalink()
1059 {
1060 global $post ;
1061 // If not on/loading the single page/post URL, do nothing
1062 if ( !is_singular() ) {
1063 return;
1064 }
1065 $external_permalink = get_post_meta( $post->ID, '_links_to', true );
1066
1067 if ( !empty($external_permalink) ) {
1068 wp_redirect( $external_permalink, 302 );
1069 // temporary redirect
1070 exit;
1071 }
1072
1073 }
1074
1075 /**
1076 * Parse links in content to add target="_blank" rel="noopener noreferrer nofollow" attributes
1077 *
1078 * @since 4.9.0
1079 */
1080 public function add_target_and_rel_atts_to_content_links( $content )
1081 {
1082
1083 if ( !empty($content) ) {
1084 // regex pattern for "a href"
1085 $regexp = "<a\\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>";
1086
1087 if ( preg_match_all(
1088 "/{$regexp}/siU",
1089 $content,
1090 $matches,
1091 PREG_SET_ORDER
1092 ) ) {
1093 // $matches might contain parts of $content that has links (a href)
1094 preg_match_all(
1095 "/{$regexp}/siU",
1096 $content,
1097 $matches,
1098 PREG_SET_ORDER
1099 );
1100
1101 if ( is_array( $matches ) ) {
1102 $i = 0;
1103 foreach ( $matches as $match ) {
1104 $original_tag = $match[0];
1105 // e.g. <a title="Link Title" href="http://www.example.com/sit-quaerat">
1106 $tag = $match[0];
1107 // Same value as $original_tag but for further processing
1108 $url = $match[2];
1109 // e.g. http://www.example.com/sit-quaerat
1110
1111 if ( false !== strpos( $url, get_site_url() ) ) {
1112 // Internal link. Do nothing.
1113 } elseif ( false === strpos( $url, 'http' ) ) {
1114 // Relative link to internal URL. Do nothing.
1115 } else {
1116 // External link. Let's do something.
1117 // Regex pattern for target="_blank|parent|self|top"
1118 $pattern = '/target\\s*=\\s*"\\s*_(blank|parent|self|top)\\s*"/';
1119 // If there's no 'target="_blank|parent|self|top"' in $tag, add target="blank"
1120 if ( 0 === preg_match( $pattern, $tag ) ) {
1121 // Replace closing > with ' target="_blank">'
1122 $tag = substr_replace( $tag, ' target="_blank">', -1 );
1123 }
1124 // If there's no 'rel' attribute in $tag, add rel="noopener noreferrer nofollow"
1125 $pattern = '/rel\\s*=\\s*\\"[a-zA-Z0-9_\\s]*\\"/';
1126
1127 if ( 0 === preg_match( $pattern, $tag ) ) {
1128 // Replace closing > with ' rel="noopener noreferrer nofollow">'
1129 $tag = substr_replace( $tag, ' rel="noopener noreferrer nofollow">', -1 );
1130 } else {
1131 // replace rel="noopener" with rel="noopener noreferrer nofollow"
1132 if ( false !== strpos( $tag, 'noopener' ) && false === strpos( $tag, 'noreferrer' ) && false === strpos( $tag, 'nofollow' ) ) {
1133 $tag = str_replace( 'noopener', 'noopener noreferrer nofollow', $tag );
1134 }
1135 }
1136
1137 // Replace original a href tag with one containing target and rel attributes above
1138 $content = str_replace( $original_tag, $tag, $content );
1139 }
1140
1141 $i++;
1142 }
1143 }
1144
1145 }
1146
1147 }
1148
1149 return $content;
1150 }
1151
1152 /**
1153 * Add "open in new tab" checkbox in custom nav menu item settings
1154 *
1155 * @since 5.4.0
1156 */
1157 public function add_custom_nav_menu_open_in_new_tab_field(
1158 $item_id,
1159 $menu_item,
1160 $depth,
1161 $args
1162 )
1163 {
1164 $target_blank = get_post_meta( $item_id, '_menu_item_target_blank', true );
1165
1166 if ( 'custom' == $menu_item->object ) {
1167 ?>
1168 <p class="field-target_blank description-wide">
1169 <label for="edit-menu-item-target-blank-<?php
1170 echo esc_attr( $item_id ) ;
1171 ?>">
1172 <input type="checkbox" id="edit-menu-item-target-blank-<?php
1173 echo esc_attr( $item_id ) ;
1174 ?>" name="menu-item-target-blank[<?php
1175 echo esc_attr( $item_id ) ;
1176 ?>]" value="1" <?php
1177 checked( $target_blank, '1' );
1178 ?> />
1179 Open link in new tab and add rel="noopener noreferrer nofollow" attribute.
1180 </label>
1181 </p>
1182 <?php
1183 }
1184
1185 }
1186
1187 /**
1188 * Save status of "open in new tab" checkbox in custom nav menu item settings
1189 *
1190 * @since 5.4.0
1191 */
1192 public function save_custom_nav_menu_open_in_new_tab_status( $menu_id, $menu_item_db_id, $args )
1193 {
1194
1195 if ( isset( $_POST['menu-item-target-blank'][$menu_item_db_id] ) ) {
1196 update_post_meta( $menu_item_db_id, '_menu_item_target_blank', '1' );
1197 } else {
1198 delete_post_meta( $menu_item_db_id, '_menu_item_target_blank' );
1199 }
1200
1201 }
1202
1203 /**
1204 * Add attributes to custom nav menu item on the frontend
1205 *
1206 * @since 5.4.0
1207 */
1208 public function add_attributes_to_custom_nav_menu_item( $atts, $menu_item, $args )
1209 {
1210 $target_blank = get_post_meta( $menu_item->ID, '_menu_item_target_blank', true );
1211
1212 if ( $target_blank ) {
1213 $atts['target'] = '_blank';
1214 $atts['rel'] = 'noopener noreferrer nofollow';
1215 }
1216
1217 return $atts;
1218 }
1219
1220 /**
1221 * Publish posts of any type with missed schedule.
1222 * We use the Transients API to reduce straining the site with DB queries on busy sites.
1223 * So, this function will only query the DB once every 15 minutes at most.
1224 *
1225 * @since 3.1.0
1226 */
1227 public function publish_missed_schedule_posts()
1228 {
1229
1230 if ( is_front_page() || is_home() || is_page() || is_single() || is_singular() || is_archive() || is_admin() || is_blog_admin() || is_robots() || is_ssl() ) {
1231 // Get missed schedule posts data from cache
1232 $missed_schedule_posts = get_transient( 'asenha_missed_schedule_posts' );
1233 // Nothing found in cache
1234
1235 if ( false === $missed_schedule_posts ) {
1236 global $wpdb ;
1237 $current_gmt_datetime = gmdate( 'Y-m-d H:i:00' );
1238 $args = array(
1239 'public' => true,
1240 '_builtin' => false,
1241 );
1242 $custom_post_types = get_post_types( $args, 'names' );
1243 // array, e.g. array( 'project', 'book', 'staff' )
1244
1245 if ( count( $custom_post_types ) > 0 ) {
1246 $custom_post_types = "'" . implode( "','", $custom_post_types ) . "'";
1247 // string, e.g. 'project','book','staff'
1248 $post_types = "'page','post'," . $custom_post_types;
1249 // 'page','post','project','book','staff'
1250 } else {
1251 $post_types = "'page','post'";
1252 }
1253
1254 $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ({$post_types}) AND post_status='future' AND post_date_gmt<'{$current_gmt_datetime}'";
1255 // The following does not work as backslashes are inserted before single quotes in $post_types
1256 // $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 ) );
1257 $missed_schedule_posts = $wpdb->get_results( $sql, ARRAY_A );
1258 // Save query results as a transient with expiry of 15 minutes
1259 set_transient( 'asenha_missed_schedule_posts', $missed_schedule_posts, 15 * MINUTE_IN_SECONDS );
1260 }
1261
1262 if ( empty($missed_schedule_posts) || !is_array( $missed_schedule_posts ) ) {
1263 return;
1264 }
1265 foreach ( $missed_schedule_posts as $post ) {
1266 wp_publish_post( $post['ID'] );
1267 }
1268 }
1269
1270 }
1271
1272 }