PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 6.2.0
Admin and Site Enhancements (ASE) v6.2.0
9.1.1 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
1274 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 * Add media replacement button in the edit screen of media/attachment
536 *
537 * @since 1.1.0
538 */
539 public function add_media_replacement_button( $fields, $post )
540 {
541 // Enqueues all scripts, styles, settings, and templates necessary to use all media JS APIs.
542 // Reference: https://codex.wordpress.org/Javascript_Reference/wp.media
543 wp_enqueue_media();
544 // Add new field to attachment fields for the media replace functionality
545 $fields['asenha-media-replace'] = array();
546 $fields['asenha-media-replace']['label'] = '';
547 $fields['asenha-media-replace']['input'] = 'html';
548 $fields['asenha-media-replace']['html'] = '
549 <div id="media-replace-div" class="postbox">
550 <div class="postbox-header">
551 <h2 class="hndle ui-sortable-handle">Replace Media</h2>
552 </div>
553 <div class="inside">
554 <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>
555 <input type="hidden" id="new-attachment-id" name="new-attachment-id" />
556 <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>
557 </div>
558 </div>
559 ';
560 return $fields;
561 }
562
563 /**
564 * Replace existing media with the newly updated file
565 *
566 * @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L55
567 * @since 1.1.0
568 */
569 public function replace_media( $old_attachment_id )
570 {
571 $old_post_meta = get_post( $old_attachment_id, ARRAY_A );
572 $old_post_mime = $old_post_meta['post_mime_type'];
573 // e.g. 'image/jpeg'
574 // Get the new attachment/media ID, meta and mime type
575
576 if ( isset( $_POST['new-attachment-id'] ) && !empty($_POST['new-attachment-id']) ) {
577 $new_attachment_id = intval( sanitize_text_field( $_POST['new-attachment-id'] ) );
578 $new_post_meta = get_post( $new_attachment_id, ARRAY_A );
579 $new_post_mime = $new_post_meta['post_mime_type'];
580 // e.g. 'image/jpeg'
581 }
582
583 // Check if the media file ID selected via the media frame and passed on to the #new-attachment-id hidden field
584 // Ensure the mime type matches too
585
586 if ( !empty($new_attachment_id) && is_numeric( $new_attachment_id ) && $old_post_mime == $new_post_mime ) {
587 $new_attachment_meta = wp_get_attachment_metadata( $new_attachment_id );
588 // If original file is larger than 2560 pixel
589 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
590
591 if ( array_key_exists( 'original_image', $new_attachment_meta ) ) {
592 // Get the original media file path
593 $new_media_file_path = wp_get_original_image_path( $new_attachment_id );
594 } else {
595 // Get the path to newly uploaded media file. An image file name may end with '-scaled'.
596 $new_attachment_file = get_post_meta( $new_attachment_id, '_wp_attached_file', true );
597 $upload_dir = wp_upload_dir();
598 $new_media_file_path = $upload_dir['basedir'] . '/' . $new_attachment_file;
599 }
600
601 // Check if the new media file exist / was successfully uploaded
602 if ( !is_file( $new_media_file_path ) ) {
603 return false;
604 }
605 // Delete existing/old media files. Post and post meta entries for it are still there in the database.
606 $this->delete_media_files( $old_attachment_id );
607 // If original file is larger than 2560 pixel
608 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
609
610 if ( array_key_exists( 'original_image', $new_attachment_meta ) ) {
611 // Get the original media file path
612 $old_media_file_path = wp_get_original_image_path( $old_attachment_id );
613 } else {
614 // Get the path to the old/existing media file that will be replaced and deleted. An image file name may end with '-scaled'.
615 $old_attachment_file = get_post_meta( $old_attachment_id, '_wp_attached_file', true );
616 $old_media_file_path = $upload_dir['basedir'] . '/' . $old_attachment_file;
617 }
618
619 // Check if the directory path to the old media file is still intact
620 if ( !file_exists( dirname( $old_media_file_path ) ) ) {
621 // Recreate the directory path
622 mkdir( dirname( $old_media_file_path ), 0755, true );
623 }
624 // Copy the new media file into the old media file's path
625 copy( $new_media_file_path, $old_media_file_path );
626 // Regenerate attachment meta data and image sub-sizes from the new media file that was just copied to the old path
627 $old_media_post_meta_updated = wp_generate_attachment_metadata( $old_attachment_id, $old_media_file_path );
628 // 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.
629 wp_update_attachment_metadata( $old_attachment_id, $old_media_post_meta_updated );
630 // Delete the newly uploaded media file and it's sub-sizes, and also delete post and post meta entries for it in the database.
631 wp_delete_attachment( $new_attachment_id, true );
632 // 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
633 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
634 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
635 if ( count( $recently_replaced_media ) >= 5 ) {
636 // Remove first/oldest attachment ID
637 array_shift( $recently_replaced_media );
638 }
639 $recently_replaced_media[] = $old_attachment_id;
640 $recently_replaced_media = array_unique( $recently_replaced_media );
641 $extra_options['recently_replaced_media'] = $recently_replaced_media;
642 update_option( 'admin_site_enhancements_extra', $extra_options );
643 }
644
645 }
646
647 /**
648 * Delete the existing/old media files when performing media replacement
649 *
650 * @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L80
651 * @since 1.1.0
652 */
653 public function delete_media_files( $post_id )
654 {
655 $attachment_meta = wp_get_attachment_metadata( $post_id );
656 // Will get '-scaled' version if it exists, e.g. /path/to/uploads/year/month/file-name.jpg
657 $attachment_file_path = get_attached_file( $post_id );
658 // e.g. file-name.jpg
659 $attachment_file_basename = basename( $attachment_file_path );
660 // Delete intermediate images if there are any
661 if ( isset( $attachment_meta['sizes'] ) && is_array( $attachment_meta['sizes'] ) ) {
662 foreach ( $attachment_meta['sizes'] as $size => $size_info ) {
663 // /path/to/uploads/year/month/file-name.jpg --> /path/to/uploads/year/month/file-name-1024x768.jpg
664 $intermediate_file_path = str_replace( $attachment_file_basename, $size_info['file'], $attachment_file_path );
665 wp_delete_file( $intermediate_file_path );
666 }
667 }
668 // Delete the attachment file, which maybe the '-scaled' version
669 wp_delete_file( $attachment_file_path );
670 // If original file is larger than 2560 pixel
671 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
672
673 if ( array_key_exists( 'original_image', $attachment_meta ) ) {
674 $attachment_original_file_path = wp_get_original_image_path( $post_id );
675 // Delete the original file
676 wp_delete_file( $attachment_original_file_path );
677 }
678
679 }
680
681 /**
682 * Customize the attachment updated message
683 *
684 * @link https://github.com/WordPress/wordpress-develop/blob/6.0.2/src/wp-admin/edit-form-advanced.php#L180
685 * @since 1.1.0
686 */
687 public function attachment_updated_custom_message( $messages )
688 {
689 $new_messages = array();
690 foreach ( $messages as $post_type => $messages_array ) {
691 if ( $post_type == 'attachment' ) {
692 // 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.
693 $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.';
694 }
695 $new_messages[$post_type] = $messages_array;
696 }
697 return $new_messages;
698 }
699
700 /**
701 * Append cache busting parameter to the end of image srcset
702 *
703 * @since 5.7.0
704 */
705 public function append_cache_busting_param_to_image_srcset(
706 $sources,
707 $size_array,
708 $image_src,
709 $image_meta,
710 $attachment_id
711 )
712 {
713 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
714 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
715 if ( is_admin() && in_array( $attachment_id, $recently_replaced_media ) ) {
716 foreach ( $sources as $size => $source ) {
717 $source['url'] .= (( false === strpos( $source['url'], '?' ) ? '?' : '&' )) . 't=' . time();
718 $sources[$size] = $source;
719 }
720 }
721 return $sources;
722 }
723
724 /**
725 * Append cache busting parameter to the end of image src
726 *
727 * @since 5.7.0
728 */
729 public function append_cache_busting_param_to_attachment_image_src( $image, $attachment_id )
730 {
731 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
732 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
733 if ( is_admin() && !empty($image[0]) && in_array( $attachment_id, $recently_replaced_media ) ) {
734 $image[0] .= (( false === strpos( $image[0], '?' ) ? '?' : '&' )) . 't=' . time();
735 }
736 return $image;
737 }
738
739 /**
740 * Append cache busting parameter to image src for js
741 *
742 * @since 5.7.0
743 */
744 public function append_cache_busting_param_to_attachment_for_js( $response, $attachment )
745 {
746 $extra_options = get_option( 'admin_site_enhancements_extra', array() );
747 $recently_replaced_media = ( isset( $extra_options['recently_replaced_media'] ) ? $extra_options['recently_replaced_media'] : array() );
748
749 if ( is_admin() && in_array( $attachment->ID, $recently_replaced_media ) ) {
750 if ( false !== strpos( $response['url'], '?' ) ) {
751 $response['url'] .= (( false === strpos( $response['url'], '?' ) ? '?' : '&' )) . 't=' . time();
752 }
753 if ( isset( $response['sizes'] ) ) {
754 foreach ( $response['sizes'] as $size_name => $size ) {
755 $response['sizes'][$size_name]['url'] .= (( false === strpos( $size['url'], '?' ) ? '?' : '&' )) . 't=' . time();
756 }
757 }
758 }
759
760 return $response;
761 }
762
763 /**
764 * Add SVG mime type for media library uploads
765 *
766 * @link https://developer.wordpress.org/reference/hooks/upload_mimes/
767 * @since 2.6.0
768 */
769 public function add_svg_mime( $mimes )
770 {
771 global $roles_svg_upload_enabled ;
772 $current_user = wp_get_current_user();
773 $current_user_roles = (array) $current_user->roles;
774 // single dimensional array of role slugs
775 if ( count( $roles_svg_upload_enabled ) > 0 ) {
776 // Add mime type for user roles set to enable SVG upload
777 foreach ( $current_user_roles as $role ) {
778 if ( in_array( $role, $roles_svg_upload_enabled ) ) {
779 $mimes['svg'] = 'image/svg+xml';
780 }
781 }
782 }
783 return $mimes;
784 }
785
786 /**
787 * Check and confirm if the real file type is indeed SVG
788 *
789 * @link https://developer.wordpress.org/reference/functions/wp_check_filetype_and_ext/
790 * @since 2.6.0
791 */
792 public function confirm_file_type_is_svg(
793 $filetypes_extensions,
794 $file,
795 $filename,
796 $mimes
797 )
798 {
799 global $roles_svg_upload_enabled ;
800 $current_user = wp_get_current_user();
801 $current_user_roles = (array) $current_user->roles;
802 // single dimensional array of role slugs
803 if ( count( $roles_svg_upload_enabled ) > 0 ) {
804 // Check file extension
805 if ( substr( $filename, -4 ) == '.svg' ) {
806 // Add mime type for user roles set to enable SVG upload
807 foreach ( $current_user_roles as $role ) {
808
809 if ( in_array( $role, $roles_svg_upload_enabled ) ) {
810 $filetypes_extensions['type'] = 'image/svg+xml';
811 $filetypes_extensions['ext'] = 'svg';
812 }
813
814 }
815 }
816 }
817 return $filetypes_extensions;
818 }
819
820 /**
821 * Sanitize the SVG file and maybe allow upload based on the result
822 *
823 * @since 2.6.0
824 */
825 public function sanitize_and_maybe_allow_svg_upload( $file )
826 {
827 if ( !isset( $file['tmp_name'] ) ) {
828 return $file;
829 }
830 $file_tmp_name = $file['tmp_name'];
831 // full path
832 $file_name = ( isset( $file['name'] ) ? $file['name'] : '' );
833 $file_type_ext = wp_check_filetype_and_ext( $file_tmp_name, $file_name );
834 $file_type = ( !empty($file_type_ext['type']) ? $file_type_ext['type'] : '' );
835 // Load sanitizer library - https://github.com/darylldoyle/svg-sanitizer
836 $sanitizer = new Sanitizer();
837
838 if ( 'image/svg+xml' === $file_type ) {
839 $original_svg = file_get_contents( $file_tmp_name );
840 $sanitized_svg = $sanitizer->sanitize( $original_svg );
841 // boolean
842 if ( false === $sanitized_svg ) {
843 $file['error'] = 'This SVG file could not be sanitized, so, was not uploaded for security reasons.';
844 }
845 file_put_contents( $file_tmp_name, $sanitized_svg );
846 }
847
848 return $file;
849 }
850
851 /**
852 * Generate metadata for the svg attachment
853 *
854 * @link https://developer.wordpress.org/reference/functions/wp_generate_attachment_metadata/
855 * @since 2.6.0
856 */
857 public function generate_svg_metadata( $metadata, $attachment_id, $context )
858 {
859
860 if ( get_post_mime_type( $attachment_id ) == 'image/svg+xml' ) {
861 // Get SVG dimensions
862 $svg_path = get_attached_file( $attachment_id );
863 $svg = simplexml_load_file( $svg_path );
864 $width = 0;
865 $height = 0;
866
867 if ( $svg ) {
868 $attributes = $svg->attributes();
869
870 if ( isset( $attributes->width, $attributes->height ) ) {
871 $width = intval( floatval( $attributes->width ) );
872 $height = intval( floatval( $attributes->height ) );
873 } elseif ( isset( $attributes->viewBox ) ) {
874 $sizes = explode( ' ', $attributes->viewBox );
875
876 if ( isset( $sizes[2], $sizes[3] ) ) {
877 $width = intval( floatval( $sizes[2] ) );
878 $height = intval( floatval( $sizes[3] ) );
879 }
880
881 }
882
883 }
884
885 $metadata['width'] = $width;
886 $metadata['height'] = $height;
887 // Get SVG filename
888 $svg_url = wp_get_original_image_url( $attachment_id );
889 $svg_url_path = str_replace( wp_upload_dir()['baseurl'] . '/', '', $svg_url );
890 $metadata['file'] = $svg_url_path;
891 }
892
893 return $metadata;
894 }
895
896 /**
897 * Return svg file URL to show preview in media library
898 *
899 * @link https://developer.wordpress.org/reference/hooks/wp_ajax_action/
900 * @link https://developer.wordpress.org/reference/functions/wp_get_attachment_url/
901 * @since 2.6.0
902 */
903 public function get_svg_attachment_url()
904 {
905 $attachment_url = '';
906 $attachment_id = ( isset( $_REQUEST['attachmentID'] ) ? $_REQUEST['attachmentID'] : '' );
907 // Check response mime type
908
909 if ( $attachment_id ) {
910 $attachment_url = wp_get_attachment_url( $attachment_id );
911 echo $attachment_url ;
912 die;
913 }
914
915 }
916
917 /**
918 * Return svg file URL to show preview in media library
919 *
920 * @link https://developer.wordpress.org/reference/functions/wp_prepare_attachment_for_js/
921 * @since 2.6.0
922 */
923 public function get_svg_url_in_media_library( $response )
924 {
925 // Check response mime type
926 if ( $response['mime'] === 'image/svg+xml' ) {
927 $response['image'] = array(
928 'src' => $response['url'],
929 );
930 }
931 return $response;
932 }
933
934 /**
935 * Add external permalink meta box for enabled post types
936 *
937 * @since 3.9.0
938 */
939 public function add_external_permalink_meta_box( $post_type, $post )
940 {
941 $options = get_option( ASENHA_SLUG_U, array() );
942 $enable_external_permalinks_for = $options['enable_external_permalinks_for'];
943 foreach ( $enable_external_permalinks_for as $post_type_slug => $is_external_permalink_enabled ) {
944 if ( get_post_type() == $post_type_slug && $is_external_permalink_enabled ) {
945 // Skip adding meta box for post types where Gutenberg is enabled
946 // if (
947 // function_exists( 'use_block_editor_for_post_type' )
948 // && use_block_editor_for_post_type( $post_type_slug )
949 // ) {
950 // continue; // go to the beginning of next iteration
951 // }
952 add_meta_box(
953 'asenha-external-permalink',
954 // ID of meta box
955 'External Permalink',
956 // Title of meta box
957 [ $this, 'output_external_permalink_meta_box' ],
958 // Callback function
959 $post_type_slug,
960 // The screen on which the meta box should be output to
961 'normal',
962 // context
963 'high'
964 );
965 }
966 }
967 }
968
969 /**
970 * Render External Permalink meta box
971 *
972 * @since 3.9.0
973 */
974 public function output_external_permalink_meta_box( $post )
975 {
976 ?>
977 <div class="external-permalink-input">
978 <input name="<?php
979 echo esc_attr( 'external_permalink' ) ;
980 ?>" class="large-text" id="<?php
981 echo esc_attr( 'external_permalink' ) ;
982 ?>" type="text" value="<?php
983 echo esc_url( get_post_meta( $post->ID, '_links_to', true ) ) ;
984 ?>" placeholder="https://" />
985 <div class="external-permalink-input-description">Keep empty to use the default WordPress permalink. External permalink will open in a new browser tab.</div>
986 <?php
987 wp_nonce_field(
988 'external_permalink_' . $post->ID,
989 'external_permalink_nonce',
990 false,
991 true
992 );
993 ?>
994 </div>
995 <?php
996 }
997
998 /**
999 * Save external permalink input
1000 *
1001 * @since 3.9.0
1002 */
1003 public function save_external_permalink( $post_id )
1004 {
1005 // Only proceed if nonce is verified
1006
1007 if ( isset( $_POST['external_permalink_nonce'] ) && wp_verify_nonce( $_POST['external_permalink_nonce'], 'external_permalink_' . $post_id ) ) {
1008 // Get the value of external permalink from input field
1009 $external_permalink = ( isset( $_POST['external_permalink'] ) ? esc_url_raw( trim( $_POST['external_permalink'] ) ) : '' );
1010 // Update or delete external permalink post meta
1011
1012 if ( !empty($external_permalink) ) {
1013 update_post_meta( $post_id, '_links_to', $external_permalink );
1014 } else {
1015 delete_post_meta( $post_id, '_links_to' );
1016 }
1017
1018 }
1019
1020 }
1021
1022 /**
1023 * Change WordPress default permalink into external permalink for pages
1024 *
1025 * @since 3.9.0
1026 */
1027 public function use_external_permalink_for_pages( $permalink, $post_id )
1028 {
1029 $external_permalink = get_post_meta( $post_id, '_links_to', true );
1030 if ( !empty($external_permalink) ) {
1031 $permalink = $external_permalink;
1032 }
1033 return $permalink;
1034 }
1035
1036 /**
1037 * Change WordPress default permalink into external permalink for posts and custom post types
1038 *
1039 * @since 3.9.0
1040 */
1041 public function use_external_permalink_for_posts( $permalink, $post )
1042 {
1043 $external_permalink = get_post_meta( $post->ID, '_links_to', true );
1044
1045 if ( !empty($external_permalink) ) {
1046 $permalink = $external_permalink;
1047 if ( !is_admin() ) {
1048 $permalink = $permalink . '#new_tab';
1049 }
1050 }
1051
1052 return $permalink;
1053 }
1054
1055 /**
1056 * Redirect page/post to external permalink if it's loaded directly from the WP default permalink
1057 *
1058 * @since 3.9.0
1059 */
1060 public function redirect_to_external_permalink()
1061 {
1062 global $post ;
1063 // If not on/loading the single page/post URL, do nothing
1064 if ( !is_singular() ) {
1065 return;
1066 }
1067 $external_permalink = get_post_meta( $post->ID, '_links_to', true );
1068
1069 if ( !empty($external_permalink) ) {
1070 wp_redirect( $external_permalink, 302 );
1071 // temporary redirect
1072 exit;
1073 }
1074
1075 }
1076
1077 /**
1078 * Parse links in content to add target="_blank" rel="noopener noreferrer nofollow" attributes
1079 *
1080 * @since 4.9.0
1081 */
1082 public function add_target_and_rel_atts_to_content_links( $content )
1083 {
1084
1085 if ( !empty($content) ) {
1086 // regex pattern for "a href"
1087 $regexp = "<a\\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>";
1088
1089 if ( preg_match_all(
1090 "/{$regexp}/siU",
1091 $content,
1092 $matches,
1093 PREG_SET_ORDER
1094 ) ) {
1095 // $matches might contain parts of $content that has links (a href)
1096 preg_match_all(
1097 "/{$regexp}/siU",
1098 $content,
1099 $matches,
1100 PREG_SET_ORDER
1101 );
1102
1103 if ( is_array( $matches ) ) {
1104 $i = 0;
1105 foreach ( $matches as $match ) {
1106 $original_tag = $match[0];
1107 // e.g. <a title="Link Title" href="http://www.example.com/sit-quaerat">
1108 $tag = $match[0];
1109 // Same value as $original_tag but for further processing
1110 $url = $match[2];
1111 // e.g. http://www.example.com/sit-quaerat
1112
1113 if ( false !== strpos( $url, get_site_url() ) ) {
1114 // Internal link. Do nothing.
1115 } elseif ( false === strpos( $url, 'http' ) ) {
1116 // Relative link to internal URL. Do nothing.
1117 } else {
1118 // External link. Let's do something.
1119 // Regex pattern for target="_blank|parent|self|top"
1120 $pattern = '/target\\s*=\\s*"\\s*_(blank|parent|self|top)\\s*"/';
1121 // If there's no 'target="_blank|parent|self|top"' in $tag, add target="blank"
1122 if ( 0 === preg_match( $pattern, $tag ) ) {
1123 // Replace closing > with ' target="_blank">'
1124 $tag = substr_replace( $tag, ' target="_blank">', -1 );
1125 }
1126 // If there's no 'rel' attribute in $tag, add rel="noopener noreferrer nofollow"
1127 $pattern = '/rel\\s*=\\s*\\"[a-zA-Z0-9_\\s]*\\"/';
1128
1129 if ( 0 === preg_match( $pattern, $tag ) ) {
1130 // Replace closing > with ' rel="noopener noreferrer nofollow">'
1131 $tag = substr_replace( $tag, ' rel="noopener noreferrer nofollow">', -1 );
1132 } else {
1133 // replace rel="noopener" with rel="noopener noreferrer nofollow"
1134 if ( false !== strpos( $tag, 'noopener' ) && false === strpos( $tag, 'noreferrer' ) && false === strpos( $tag, 'nofollow' ) ) {
1135 $tag = str_replace( 'noopener', 'noopener noreferrer nofollow', $tag );
1136 }
1137 }
1138
1139 // Replace original a href tag with one containing target and rel attributes above
1140 $content = str_replace( $original_tag, $tag, $content );
1141 }
1142
1143 $i++;
1144 }
1145 }
1146
1147 }
1148
1149 }
1150
1151 return $content;
1152 }
1153
1154 /**
1155 * Add "open in new tab" checkbox in custom nav menu item settings
1156 *
1157 * @since 5.4.0
1158 */
1159 public function add_custom_nav_menu_open_in_new_tab_field(
1160 $item_id,
1161 $menu_item,
1162 $depth,
1163 $args
1164 )
1165 {
1166 $target_blank = get_post_meta( $item_id, '_menu_item_target_blank', true );
1167
1168 if ( 'custom' == $menu_item->object ) {
1169 ?>
1170 <p class="field-target_blank description-wide">
1171 <label for="edit-menu-item-target-blank-<?php
1172 echo esc_attr( $item_id ) ;
1173 ?>">
1174 <input type="checkbox" id="edit-menu-item-target-blank-<?php
1175 echo esc_attr( $item_id ) ;
1176 ?>" name="menu-item-target-blank[<?php
1177 echo esc_attr( $item_id ) ;
1178 ?>]" value="1" <?php
1179 checked( $target_blank, '1' );
1180 ?> />
1181 Open link in new tab and add rel="noopener noreferrer nofollow" attribute.
1182 </label>
1183 </p>
1184 <?php
1185 }
1186
1187 }
1188
1189 /**
1190 * Save status of "open in new tab" checkbox in custom nav menu item settings
1191 *
1192 * @since 5.4.0
1193 */
1194 public function save_custom_nav_menu_open_in_new_tab_status( $menu_id, $menu_item_db_id, $args )
1195 {
1196
1197 if ( isset( $_POST['menu-item-target-blank'][$menu_item_db_id] ) ) {
1198 update_post_meta( $menu_item_db_id, '_menu_item_target_blank', '1' );
1199 } else {
1200 delete_post_meta( $menu_item_db_id, '_menu_item_target_blank' );
1201 }
1202
1203 }
1204
1205 /**
1206 * Add attributes to custom nav menu item on the frontend
1207 *
1208 * @since 5.4.0
1209 */
1210 public function add_attributes_to_custom_nav_menu_item( $atts, $menu_item, $args )
1211 {
1212 $target_blank = get_post_meta( $menu_item->ID, '_menu_item_target_blank', true );
1213
1214 if ( $target_blank ) {
1215 $atts['target'] = '_blank';
1216 $atts['rel'] = 'noopener noreferrer nofollow';
1217 }
1218
1219 return $atts;
1220 }
1221
1222 /**
1223 * Publish posts of any type with missed schedule.
1224 * We use the Transients API to reduce straining the site with DB queries on busy sites.
1225 * So, this function will only query the DB once every 15 minutes at most.
1226 *
1227 * @since 3.1.0
1228 */
1229 public function publish_missed_schedule_posts()
1230 {
1231
1232 if ( is_front_page() || is_home() || is_page() || is_single() || is_singular() || is_archive() || is_admin() || is_blog_admin() || is_robots() || is_ssl() ) {
1233 // Get missed schedule posts data from cache
1234 $missed_schedule_posts = get_transient( 'asenha_missed_schedule_posts' );
1235 // Nothing found in cache
1236
1237 if ( false === $missed_schedule_posts ) {
1238 global $wpdb ;
1239 $current_gmt_datetime = gmdate( 'Y-m-d H:i:00' );
1240 $args = array(
1241 'public' => true,
1242 '_builtin' => false,
1243 );
1244 $custom_post_types = get_post_types( $args, 'names' );
1245 // array, e.g. array( 'project', 'book', 'staff' )
1246
1247 if ( count( $custom_post_types ) > 0 ) {
1248 $custom_post_types = "'" . implode( "','", $custom_post_types ) . "'";
1249 // string, e.g. 'project','book','staff'
1250 $post_types = "'page','post'," . $custom_post_types;
1251 // 'page','post','project','book','staff'
1252 } else {
1253 $post_types = "'page','post'";
1254 }
1255
1256 $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ({$post_types}) AND post_status='future' AND post_date_gmt<'{$current_gmt_datetime}'";
1257 // The following does not work as backslashes are inserted before single quotes in $post_types
1258 // $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 ) );
1259 $missed_schedule_posts = $wpdb->get_results( $sql, ARRAY_A );
1260 // Save query results as a transient with expiry of 15 minutes
1261 set_transient( 'asenha_missed_schedule_posts', $missed_schedule_posts, 15 * MINUTE_IN_SECONDS );
1262 }
1263
1264 if ( empty($missed_schedule_posts) || !is_array( $missed_schedule_posts ) ) {
1265 return;
1266 }
1267 foreach ( $missed_schedule_posts as $post ) {
1268 wp_publish_post( $post['ID'] );
1269 }
1270 }
1271
1272 }
1273
1274 }