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