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