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