PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 5.5.2
Admin and Site Enhancements (ASE) v5.5.2
9.1.0 9.0.2 9.0.1 9.0.0 8.9.2 8.9.1 8.9.0 8.8.8 8.8.7 8.8.6 8.8.5 8.8.4 8.8.3 8.8.2 8.8.1 8.8.0 8.7.3 8.7.2 8.7.1 8.2.1 8.2.2 8.2.3 8.3.0 8.3.1 8.3.2 8.4.0 8.4.1 8.4.2 8.5.0 8.5.1 8.5.2 8.6.0 8.6.1 8.6.2 8.7.0 5.0.1 5.0.2 5.0.2.1 5.0.2.2 5.0.2.3 5.0.2.4 5.1.0 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.8.0 5.8.1 6.0.0 6.0.3 6.0.4 6.0.5 6.0.5.1 6.0.6 6.0.7 6.0.8.1 6.1.0 6.1.3 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.3.0 6.3.1 6.3.2 6.4.0 6.5.0 6.5.1 6.6.0 6.7.0 6.8.0 6.8.2 6.8.3 6.9.0 6.9.1 6.9.10 6.9.11 6.9.12 6.9.13.1 6.9.13.2 6.9.2 6.9.3 6.9.4 6.9.5 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.2 7.0.2.1 7.0.2.2 7.0.2.3 7.0.3 7.1.0 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2.0 7.2.1 7.3.0 7.3.1 7.3.2 7.3.3 7.4.0 7.4.2 7.4.4 7.4.5 7.4.6 7.4.7 7.4.8 7.5.0 7.5.1 7.5.2 7.5.3 7.5.4 7.6.0 7.6.1 7.6.1.1 7.6.10 7.6.11 7.6.2 7.6.3 7.6.4 7.6.5 7.6.6 7.6.7 7.6.7.1 7.6.8 7.6.9 7.7.0 7.8.0 7.8.1 7.8.10 7.8.11 7.8.12 7.8.13 7.8.14 7.8.15 7.8.16 7.8.17 7.8.18 7.8.2 7.8.3 7.8.4 7.8.5 7.8.5.1 7.8.6 7.8.7 7.8.8 7.8.9 7.9.0 7.9.1 7.9.10 7.9.11 7.9.2 7.9.3 7.9.4 7.9.5 7.9.6 7.9.7 7.9.8 7.9.9 8.0.0 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.0.6 8.0.7 8.0.8 8.1.0 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 trunk 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 1.9.0 2.0.0 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.6.0 2.7.0 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 3.6.1 3.7.0 3.8.0 3.9.0 3.9.1 3.9.2 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.9.0 4.9.1 4.9.2 4.9.3 5.0.0
admin-site-enhancements / classes / class-content-management.php
admin-site-enhancements / classes Last commit date
class-activation.php 3 years ago class-admin-interface.php 3 years ago class-common-methods.php 3 years ago class-content-management.php 3 years ago class-custom-code.php 3 years ago class-deactivation.php 3 years ago class-disable-components.php 3 years ago class-login-logout.php 3 years ago class-optimizations.php 3 years ago class-security.php 3 years ago class-settings-fields-render.php 3 years ago class-settings-sanitization.php 3 years ago class-settings-sections-fields.php 3 years ago class-utilities.php 3 years ago
class-content-management.php
1579 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 ArrayObject ;
9 /**
10 * Class related to Content Management features
11 *
12 * @since 1.0.0
13 */
14 class Content_Management
15 {
16 /**
17 * Current post type. For Content Admin >> Show Custom Taxonomy Filters functionality.
18 */
19 public $post_type ;
20 /**
21 * Show featured images column in list tables for pages and post types that support featured image
22 *
23 * @since 1.0.0
24 */
25 public function show_featured_image_column()
26 {
27 $post_types = get_post_types( array(
28 'public' => true,
29 ), 'names' );
30 foreach ( $post_types as $post_type_key => $post_type_name ) {
31
32 if ( post_type_supports( $post_type_key, 'thumbnail' ) ) {
33 add_filter( "manage_{$post_type_name}_posts_columns", [ $this, 'add_featured_image_column' ], 999 );
34 add_action(
35 "manage_{$post_type_name}_posts_custom_column",
36 [ $this, 'add_featured_image' ],
37 10,
38 2
39 );
40 }
41
42 }
43 }
44
45 /**
46 * Add a column called Featured Image as the first column
47 *
48 * @param mixed $columns
49 * @return void
50 * @since 1.0.0
51 */
52 public function add_featured_image_column( $columns )
53 {
54 $new_columns = array();
55 foreach ( $columns as $key => $value ) {
56 if ( 'title' == $key ) {
57 // We add featured image column before the 'title' column
58 $new_columns['asenha-featured-image'] = 'Featured Image';
59 }
60 if ( 'thumb' == $key ) {
61 // For WooCommerce products, we add featured image column before it's native thumbnail column
62 $new_columns['asenha-featured-image'] = 'Product Image';
63 }
64 $new_columns[$key] = $value;
65 }
66 // Replace WooCommerce thumbnail column with ASE featured image column
67 if ( array_key_exists( 'thumb', $new_columns ) ) {
68 unset( $new_columns['thumb'] );
69 }
70 return $new_columns;
71 }
72
73 /**
74 * Echo featured image's in thumbnail size to a column
75 *
76 * @param mixed $column_name
77 * @param mixed $id
78 * @since 1.0.0
79 */
80 public function add_featured_image( $column_name, $id )
81 {
82 if ( 'asenha-featured-image' === $column_name ) {
83
84 if ( has_post_thumbnail( $id ) ) {
85 $size = 'thumbnail';
86 echo get_the_post_thumbnail( $id, $size, '' ) ;
87 } else {
88 echo '<img src="' . esc_url( plugins_url( 'assets/img/default_featured_image.jpg', __DIR__ ) ) . '" />' ;
89 }
90
91 }
92 }
93
94 /**
95 * Show excerpt column in list tables for pages and post types that support excerpt.
96 *
97 * @since 1.0.0
98 */
99 public function show_excerpt_column()
100 {
101 $post_types = get_post_types( array(
102 'public' => true,
103 ), 'names' );
104 foreach ( $post_types as $post_type_key => $post_type_name ) {
105
106 if ( post_type_supports( $post_type_key, 'excerpt' ) ) {
107 add_filter( "manage_{$post_type_name}_posts_columns", [ $this, 'add_excerpt_column' ] );
108 add_action(
109 "manage_{$post_type_name}_posts_custom_column",
110 [ $this, 'add_excerpt' ],
111 10,
112 2
113 );
114 }
115
116 }
117 }
118
119 /**
120 * Add a column called Featured Image as the first column
121 *
122 * @param mixed $columns
123 * @return void
124 * @since 1.0.0
125 */
126 public function add_excerpt_column( $columns )
127 {
128 $new_columns = array();
129 foreach ( $columns as $key => $value ) {
130 $new_columns[$key] = $value;
131 if ( $key == 'title' ) {
132 $new_columns['asenha-excerpt'] = 'Excerpt';
133 }
134 }
135 return $new_columns;
136 }
137
138 /**
139 * Echo featured image's in thumbnail size to a column
140 *
141 * @param mixed $column_name
142 * @param mixed $id
143 * @since 1.0.0
144 */
145 public function add_excerpt( $column_name, $id )
146 {
147
148 if ( 'asenha-excerpt' === $column_name ) {
149 $excerpt = get_the_excerpt( $id );
150 // about 310 characters
151 $excerpt = substr( $excerpt, 0, 160 );
152 // truncate to 160 characters
153 $short_excerpt = substr( $excerpt, 0, strrpos( $excerpt, ' ' ) );
154 echo wp_kses_post( $short_excerpt ) ;
155 }
156
157 }
158
159 /**
160 * Add ID column list table of pages, posts, custom post types, media, taxonomies, custom taxonomies, users amd comments
161 *
162 * @since 1.0.0
163 */
164 public function show_id_column()
165 {
166 // For pages and hierarchical post types list table
167 add_filter( 'manage_pages_columns', [ $this, 'add_id_column' ] );
168 add_action(
169 'manage_pages_custom_column',
170 [ $this, 'add_id_echo_value' ],
171 10,
172 2
173 );
174 // For posts and non-hierarchical custom posts list table
175 add_filter( 'manage_posts_columns', [ $this, 'add_id_column' ] );
176 add_action(
177 'manage_posts_custom_column',
178 [ $this, 'add_id_echo_value' ],
179 10,
180 2
181 );
182 // For media list table
183 add_filter( 'manage_media_columns', [ $this, 'add_id_column' ] );
184 add_action(
185 'manage_media_custom_column',
186 [ $this, 'add_id_echo_value' ],
187 10,
188 2
189 );
190 // For list table of all taxonomies
191 $taxonomies = get_taxonomies( [
192 'public' => true,
193 ], 'names' );
194 foreach ( $taxonomies as $taxonomy ) {
195 add_filter( 'manage_edit-' . $taxonomy . '_columns', [ $this, 'add_id_column' ] );
196 add_action(
197 'manage_' . $taxonomy . '_custom_column',
198 [ $this, 'add_id_return_value' ],
199 10,
200 3
201 );
202 }
203 // For users list table
204 add_filter( 'manage_users_columns', [ $this, 'add_id_column' ] );
205 add_action(
206 'manage_users_custom_column',
207 [ $this, 'add_id_return_value' ],
208 10,
209 3
210 );
211 // For comments list table
212 add_filter( 'manage_edit-comments_columns', [ $this, 'add_id_column' ] );
213 add_action(
214 'manage_comments_custom_column',
215 [ $this, 'add_id_echo_value' ],
216 10,
217 3
218 );
219 }
220
221 /**
222 * Add a column called ID
223 *
224 * @param mixed $columns
225 * @return void
226 * @since 1.0.0
227 */
228 public function add_id_column( $columns )
229 {
230 $columns['asenha-id'] = 'ID';
231 return $columns;
232 }
233
234 /**
235 * Echo post ID value to a column
236 *
237 * @param mixed $column_name
238 * @param mixed $id
239 * @since 1.0.0
240 */
241 public function add_id_echo_value( $column_name, $id )
242 {
243 if ( 'asenha-id' === $column_name ) {
244 echo esc_html( $id ) ;
245 }
246 }
247
248 /**
249 * Return post ID value to a column
250 *
251 * @param mixed $value
252 * @param mixed $column_name
253 * @param mixed $id
254 * @since 1.0.0
255 */
256 public function add_id_return_value( $value, $column_name, $id )
257 {
258 if ( 'asenha-id' === $column_name ) {
259 $value = $id;
260 }
261 return $value;
262 }
263
264 /**
265 * Add ID in the action row of list tables for pages, posts, custom post types, media, taxonomies, custom taxonomies, users amd comments
266 *
267 * @since 4.7.4
268 */
269 public function show_id_in_action_row()
270 {
271 add_filter(
272 'page_row_actions',
273 array( $this, 'add_id_in_action_row' ),
274 10,
275 2
276 );
277 add_filter(
278 'post_row_actions',
279 array( $this, 'add_id_in_action_row' ),
280 10,
281 2
282 );
283 add_filter(
284 'cat_row_actions',
285 array( $this, 'add_id_in_action_row' ),
286 10,
287 2
288 );
289 add_filter(
290 'tag_row_actions',
291 array( $this, 'add_id_in_action_row' ),
292 10,
293 2
294 );
295 add_filter(
296 'media_row_actions',
297 array( $this, 'add_id_in_action_row' ),
298 10,
299 2
300 );
301 add_filter(
302 'comment_row_actions',
303 array( $this, 'add_id_in_action_row' ),
304 10,
305 2
306 );
307 add_filter(
308 'user_row_actions',
309 array( $this, 'add_id_in_action_row' ),
310 10,
311 2
312 );
313 }
314
315 /**
316 * Output the ID in the action row
317 *
318 * @since 4.7.4
319 */
320 public function add_id_in_action_row( $actions, $object )
321 {
322
323 if ( current_user_can( 'edit_posts' ) ) {
324 // For pages, posts, custom post types, media/attachments, users
325 if ( property_exists( $object, 'ID' ) ) {
326 $id = $object->ID;
327 }
328 // For taxonomies
329 if ( property_exists( $object, 'term_id' ) ) {
330 $id = $object->term_id;
331 }
332 // For comments
333 if ( property_exists( $object, 'comment_ID' ) ) {
334 $id = $object->comment_ID;
335 }
336 $actions['asenha-list-table-item-id'] = '<span class="asenha-list-table-item-id">ID: ' . $id . '</span>';
337 }
338
339 return $actions;
340 }
341
342 /**
343 * Hide comments column in list tables for pages, post types that support comments, and alse media/attachments.
344 *
345 * @since 1.0.0
346 */
347 public function hide_comments_column()
348 {
349 $post_types = get_post_types( array(
350 'public' => true,
351 ), 'names' );
352 foreach ( $post_types as $post_type_key => $post_type_name ) {
353 if ( post_type_supports( $post_type_key, 'comments' ) ) {
354
355 if ( 'attachment' != $post_type_name ) {
356 // For list tables of pages, posts and other post types
357 add_filter( "manage_{$post_type_name}_posts_columns", [ $this, 'remove_comment_column' ] );
358 } else {
359 // For list table of media/attachment
360 add_filter( 'manage_media_columns', [ $this, 'remove_comment_column' ] );
361 }
362
363 }
364 }
365 }
366
367 /**
368 * Add a column called ID
369 *
370 * @param mixed $columns
371 * @return void
372 * @since 1.0.0
373 */
374 public function remove_comment_column( $columns )
375 {
376 unset( $columns['comments'] );
377 return $columns;
378 }
379
380 /**
381 * Hide tags column in list tables for posts.
382 *
383 * @since 1.0.0
384 */
385 public function hide_post_tags_column()
386 {
387 $post_types = get_post_types( array(
388 'public' => true,
389 ), 'names' );
390 foreach ( $post_types as $post_type_key => $post_type_name ) {
391 if ( $post_type_name == 'post' ) {
392 add_filter( "manage_posts_columns", [ $this, 'remove_post_tags_column' ] );
393 }
394 }
395 }
396
397 /**
398 * Add a column called ID
399 *
400 * @param mixed $columns
401 * @return void
402 * @since 1.0.0
403 */
404 public function remove_post_tags_column( $columns )
405 {
406 unset( $columns['tags'] );
407 return $columns;
408 }
409
410 /**
411 * Show custom (hierarchical) taxonomy filter(s) for all post types.
412 *
413 * @since 1.0.0
414 */
415 public function show_custom_taxonomy_filters( $post_type )
416 {
417 $post_taxonomies = get_object_taxonomies( $post_type, 'objects' );
418 // Only show custom taxonomy filters for post types other than 'post'
419 if ( 'post' != $post_type ) {
420 array_walk( $post_taxonomies, [ $this, 'output_taxonomy_filter' ] );
421 }
422 }
423
424 /**
425 * Output filter on the post type's list table for a taxonomy
426 *
427 * @since 1.0.0
428 */
429 public function output_taxonomy_filter( $post_taxonomy )
430 {
431 // Only show taxonomy filter when the taxonomy is hierarchical
432 if ( true === $post_taxonomy->hierarchical ) {
433 wp_dropdown_categories( array(
434 'show_option_all' => sprintf( 'All %s', $post_taxonomy->label ),
435 'orderby' => 'name',
436 'order' => 'ASC',
437 'hide_empty' => false,
438 'hide_if_empty' => true,
439 'selected' => filter_input( INPUT_GET, $post_taxonomy->query_var, FILTER_SANITIZE_STRING ),
440 'hierarchical' => true,
441 'name' => $post_taxonomy->query_var,
442 'taxonomy' => $post_taxonomy->name,
443 'value_field' => 'slug',
444 ) );
445 }
446 }
447
448 /**
449 * Enable duplication of pages, posts and custom posts
450 *
451 * @since 1.0.0
452 */
453 public function asenha_enable_duplication()
454 {
455 $original_post_id = intval( sanitize_text_field( $_REQUEST['post'] ) );
456 $nonce = sanitize_text_field( $_REQUEST['nonce'] );
457
458 if ( wp_verify_nonce( $nonce, 'asenha-duplicate-' . $original_post_id ) && current_user_can( 'edit_posts' ) ) {
459 $original_post = get_post( $original_post_id );
460 // Set some attributes for the duplicate post
461 $new_post_title_suffix = ' (DUPLICATE)';
462 $new_post_status = 'draft';
463 $current_user = wp_get_current_user();
464 $new_post_author_id = $current_user->ID;
465 // Create the duplicate post and store the ID
466 $args = array(
467 'comment_status' => $original_post->comment_status,
468 'ping_status' => $original_post->ping_status,
469 'post_author' => $new_post_author_id,
470 'post_content' => $original_post->post_content,
471 'post_excerpt' => $original_post->post_excerpt,
472 'post_parent' => $original_post->post_parent,
473 'post_password' => $original_post->post_password,
474 'post_status' => $new_post_status,
475 'post_title' => $original_post->post_title . $new_post_title_suffix,
476 'post_type' => $original_post->post_type,
477 'to_ping' => $original_post->to_ping,
478 'menu_order' => $original_post->menu_order,
479 );
480 $new_post_id = wp_insert_post( $args );
481 // Copy over the taxonomies
482 $original_taxonomies = get_object_taxonomies( $original_post->post_type );
483 if ( !empty($original_taxonomies) && is_array( $original_taxonomies ) ) {
484 foreach ( $original_taxonomies as $taxonomy ) {
485 $original_post_terms = wp_get_object_terms( $original_post_id, $taxonomy, array(
486 'fields' => 'slugs',
487 ) );
488 wp_set_object_terms(
489 $new_post_id,
490 $original_post_terms,
491 $taxonomy,
492 false
493 );
494 }
495 }
496 // Copy over the post meta
497 $original_post_metas = get_post_meta( $original_post_id );
498 // all meta keys and the corresponding values
499 if ( !empty($original_post_metas) ) {
500 foreach ( $original_post_metas as $meta_key => $meta_values ) {
501 foreach ( $meta_values as $meta_value ) {
502 update_post_meta( $new_post_id, $meta_key, wp_slash( maybe_unserialize( $meta_value ) ) );
503 }
504 }
505 }
506 // Redirect to list table of the corresponding post type of original post
507 $post_type = get_post_type( $original_post_id );
508
509 if ( 'post' == $post_type ) {
510 wp_redirect( admin_url( 'edit.php' ) );
511 } else {
512 wp_redirect( admin_url( 'edit.php?post_type=' . $post_type ) );
513 }
514
515 } else {
516 wp_die( 'You do not have permission to perform this action.' );
517 }
518
519 }
520
521 /**
522 * Add row action link to perform duplication in page/post list tables
523 *
524 * @since 1.0.0
525 */
526 public function add_duplication_action_link( $actions, $post )
527 {
528 if ( current_user_can( 'edit_posts' ) ) {
529 $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>';
530 }
531 return $actions;
532 }
533
534 /**
535 * Modify the 'Edit' link to be 'Edit or Replace'
536 *
537 */
538 public function modify_media_list_table_edit_link( $actions, $post )
539 {
540 $new_actions = array();
541 foreach ( $actions as $key => $value ) {
542
543 if ( $key == 'edit' ) {
544 $new_actions['edit'] = '<a href="' . get_edit_post_link( $post ) . '" aria-label="Edit or Replace">Edit or Replace</a>';
545 } else {
546 $new_actions[$key] = $value;
547 }
548
549 }
550 return $new_actions;
551 }
552
553 /**
554 * Add "Custom Order" sub-menu for post types
555 *
556 * @since 5.0.0
557 */
558 public function add_content_order_submenu( $context )
559 {
560 $options = get_option( ASENHA_SLUG_U, array() );
561 $content_order_for = $options['content_order_for'];
562 $content_order_enabled_post_types = array();
563 foreach ( $options['content_order_for'] as $post_type_slug => $is_custom_order_enabled ) {
564
565 if ( $is_custom_order_enabled ) {
566 $post_type_object = get_post_type_object( $post_type_slug );
567
568 if ( is_object( $post_type_object ) && property_exists( $post_type_object, 'labels' ) ) {
569 $post_type_name_plural = $post_type_object->labels->name;
570
571 if ( 'post' == $post_type_slug ) {
572 $hook_suffix = add_posts_page(
573 $post_type_name_plural . ' Order',
574 // Page title
575 'Order',
576 // Menu title
577 'edit_pages',
578 // Capability required
579 'custom-order-posts',
580 // Menu and page slug
581 [ $this, 'custom_order_page_output' ]
582 );
583 } else {
584 $hook_suffix = add_submenu_page(
585 'edit.php?post_type=' . $post_type_slug,
586 // Parent (menu) slug. Ref: https://developer.wordpress.org/reference/functions/add_submenu_page/#comment-1404
587 $post_type_name_plural . ' Order',
588 // Page title
589 'Order',
590 // Menu title
591 'edit_pages',
592 // Capability required
593 'custom-order-' . $post_type_slug,
594 // Menu and page slug
595 [ $this, 'custom_order_page_output' ]
596 );
597 }
598
599 add_action( 'admin_print_styles-' . $hook_suffix, [ $this, 'enqueue_content_order_styles' ] );
600 add_action( 'admin_print_scripts-' . $hook_suffix, [ $this, 'enqueue_content_order_scripts' ] );
601 }
602
603 }
604
605 }
606 }
607
608 /**
609 * Output content for the custom order page for each enabled post types
610 * Not using settings API because all done via AJAX
611 *
612 * @since 5.0.0
613 */
614 public function custom_order_page_output()
615 {
616 $parent_slug = get_admin_page_parent();
617
618 if ( 'edit.php' == $parent_slug ) {
619 $post_type_slug = 'post';
620 } else {
621 $post_type_slug = str_replace( 'edit.php?post_type=', '', $parent_slug );
622 }
623
624 // Object with properties for each post status and the count of posts for each status
625 // $post_count_object = wp_count_posts( $post_type_slug );
626 // Number of items with the status 'publish(ed)', 'future' (scheduled), 'draft', 'pending' and 'private'
627 // $post_count = absint( $post_count_object->publish )
628 // + absint( $post_count_object->future )
629 // + absint( $post_count_object->draft )
630 // + absint( $post_count_object->pending )
631 // + absint( $post_count_object->private );
632 ?>
633 <div class="wrap">
634 <h2>
635 <?php
636 echo get_admin_page_title() ;
637 ?>
638 </h2>
639 <?php
640 // Get posts
641 $query = new WP_Query( array(
642 'post_type' => $post_type_slug,
643 'posts_per_page' => -1,
644 'orderby' => 'menu_order title',
645 'order' => 'ASC',
646 'post_status' => array(
647 'publish',
648 'future',
649 'draft',
650 'pending',
651 'private'
652 ),
653 'post_parent' => 0,
654 ) );
655
656 if ( $query->have_posts() ) {
657 ?>
658 <ul id="item-list">
659 <?php
660 while ( $query->have_posts() ) {
661 $query->the_post();
662 $post = get_post( get_the_ID() );
663 $this->custom_order_single_item_output( $post );
664 }
665 ?>
666 </ul>
667 <div id="updating-order-notice" class="updating-order-notice" style="display: none;"><img src="<?php
668 echo ASENHA_URL . 'assets/img/oval.svg' ;
669 ?>" id="spinner-img" class="spinner-img" /><span class="dashicons dashicons-saved" style="display:none;"></span>Updating order...</div>
670 <?php
671 } else {
672 ?>
673 <h3>There is nothing to sort for this post type.</h3>
674 <?php
675 }
676
677 ?>
678 </div> <!-- End of div.wrap -->
679 <?php
680 wp_reset_postdata();
681 }
682
683 /**
684 * Output single item sortable for custom content order
685 *
686 * @since 5.0.0
687 */
688 private function custom_order_single_item_output( $post )
689 {
690
691 if ( is_post_type_hierarchical( $post->post_type ) ) {
692 $post_type_object = get_post_type_object( $post->post_type );
693 $children = get_pages( array(
694 'child_of' => $post->ID,
695 'post_type' => $post->post_type,
696 ) );
697
698 if ( count( $children ) > 0 ) {
699 $has_child_label = '<span class="has-child-label"> <span class="dashicons dashicons-arrow-right"></span> Has child ' . strtolower( $post_type_object->label ) . '</span>';
700 $has_child = 'true';
701 } else {
702 $has_child_label = '';
703 $has_child = 'false';
704 }
705
706 } else {
707 $has_child_label = '';
708 $has_child = 'false';
709 }
710
711 $post_status_label_class = ( $post->post_status == 'publish' ? ' item-status-hidden' : '' );
712 $post_status_object = get_post_status_object( $post->post_status );
713 ?>
714 <li id="list_<?php
715 echo $post->ID ;
716 ?>" data-id="<?php
717 echo $post->ID ;
718 ?>" data-menu-order="<?php
719 echo $post->menu_order ;
720 ?>" data-parent="<?php
721 echo $post->post_parent ;
722 ?>" data-has-child="<?php
723 echo $has_child ;
724 ?>" data-post-type="<?php
725 echo $post->post_type ;
726 ?>">
727 <div class="row">
728 <div class="row-content">
729 <?php
730 echo '<div class="content-main">
731 <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 . '
732 </div>
733 <div class="content-additional">
734 <a href="' . get_the_permalink( $post->ID ) . '" target="_blank" class="item-view-link">View</a>
735 </div>' ;
736 ?>
737 </div>
738 </div>
739 </li>
740 <?php
741 }
742
743 /**
744 * Enqueue styles for content order pages
745 *
746 * @since 5.0.0
747 */
748 public function enqueue_content_order_styles()
749 {
750 wp_enqueue_style(
751 'content-order-style',
752 ASENHA_URL . 'assets/css/content-order.css',
753 array(),
754 ASENHA_VERSION
755 );
756 }
757
758 /**
759 * Enqueue scripts for content order pages
760 *
761 * @since 5.0.0
762 */
763 public function enqueue_content_order_scripts()
764 {
765 global $typenow ;
766 wp_enqueue_script(
767 'content-order-jquery-ui-touch-punch',
768 ASENHA_URL . 'assets/js/jquery.ui.touch-punch.min.js',
769 array( 'jquery-ui-sortable' ),
770 '0.2.3',
771 true
772 );
773 wp_register_script(
774 'content-order-nested-sortable',
775 ASENHA_URL . 'assets/js/jquery.mjs.nestedSortable.js',
776 array( 'content-order-jquery-ui-touch-punch' ),
777 '2.0.0',
778 true
779 );
780 wp_enqueue_script(
781 'content-order-sort',
782 ASENHA_URL . 'assets/js/content-order-sort.js',
783 array( 'content-order-nested-sortable' ),
784 ASENHA_VERSION,
785 true
786 );
787 wp_localize_script( 'content-order-sort', 'contentOrderSort', array(
788 'action' => 'save_custom_order',
789 'nonce' => wp_create_nonce( 'order_sorting_nonce' ),
790 'hirarchical' => ( is_post_type_hierarchical( $typenow ) ? 'true' : 'false' ),
791 ) );
792 }
793
794 /**
795 * Save custom content order coming from ajax call
796 *
797 * @since 5.0.0
798 */
799 public function save_custom_content_order()
800 {
801 global $wpdb ;
802 // Check user capabilities
803 if ( !current_user_can( 'edit_pages' ) ) {
804 wp_send_json( 'Something went wrong.' );
805 }
806 // Verify nonce
807 if ( !wp_verify_nonce( $_POST['nonce'], 'order_sorting_nonce' ) ) {
808 wp_send_json( 'Something went wrong.' );
809 }
810 // Get ajax variables
811 $action = ( isset( $_POST['action'] ) ? $_POST['action'] : '' );
812 $item_parent = ( isset( $_POST['item_parent'] ) ? absint( $_POST['item_parent'] ) : 0 );
813 $menu_order_start = ( isset( $_POST['start'] ) ? absint( $_POST['start'] ) : 0 );
814 $post_id = ( isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0 );
815 $item_menu_order = ( isset( $_POST['menu_order'] ) ? absint( $_POST['menu_order'] ) : 0 );
816 $items_to_exclude = ( isset( $_POST['excluded_items'] ) ? absint( $_POST['excluded_items'] ) : array() );
817 $post_type = ( isset( $_POST['post_type'] ) ? $_POST['post_type'] : false );
818 // Make processing faster by removing certain actions
819 remove_action( 'pre_post_update', 'wp_save_post_revision' );
820 // $response array for ajax response
821 $response = array();
822 // Update the item whose order/position was moved
823
824 if ( $post_id > 0 && !isset( $_POST['more_posts'] ) ) {
825 // https://developer.wordpress.org/reference/classes/wpdb/update/
826 $wpdb->update(
827 $wpdb->posts,
828 // The table
829 array(
830 'menu_order' => $item_menu_order,
831 ),
832 array(
833 'ID' => $post_id,
834 )
835 );
836 clean_post_cache( $post_id );
837 $items_to_exclude[] = $post_id;
838 }
839
840 // Get all posts from the post type related to ajax request
841 $query_args = array(
842 'post_type' => $post_type,
843 'orderby' => 'menu_order title',
844 'order' => 'ASC',
845 'posts_per_page' => -1,
846 'suppress_filters' => true,
847 'ignore_sticky_posts' => true,
848 'post_status' => array(
849 'publish',
850 'future',
851 'draft',
852 'pending',
853 'private'
854 ),
855 'post_parent' => $item_parent,
856 'post__not_in' => $items_to_exclude,
857 'update_post_term_cache' => false,
858 'update_post_meta_cache' => false,
859 );
860 $posts = new WP_Query( $query_args );
861
862 if ( $posts->have_posts() ) {
863 // Iterate through posts and update menu order and post parent
864 foreach ( $posts->posts as $post ) {
865 // If the $post is the one being displaced (shited downward) by the moved item, increment it's menu_order by one
866 if ( $menu_order_start == $item_menu_order && $post_id > 0 ) {
867 $menu_order_start++;
868 }
869 // Only process posts other than the moved item, which has been processed earlier outside this loop
870
871 if ( $post_id != $post->ID ) {
872 // Update menu_order
873 $wpdb->update( $wpdb->posts, array(
874 'menu_order' => $menu_order_start,
875 ), array(
876 'ID' => $post->ID,
877 ) );
878 clean_post_cache( $post->ID );
879 }
880
881 $items_to_exclude[] = $post->ID;
882 $menu_order_start++;
883 }
884 die( json_encode( $response ) );
885 } else {
886 die( json_encode( $response ) );
887 }
888
889 }
890
891 /**
892 * Set default ordering of list tables of sortable post types in wp-admin by 'menu_order title'
893 *
894 * @since 5.0.0
895 */
896 public function orderby_menu_order_in_admin_lists( $wp_query )
897 {
898 global $pagenow, $typenow ;
899 $options = get_option( ASENHA_SLUG_U, array() );
900 $content_order_for = $options['content_order_for'];
901 $content_order_enabled_post_types = array();
902 foreach ( $options['content_order_for'] as $post_type_slug => $is_custom_order_enabled ) {
903 if ( $is_custom_order_enabled ) {
904 $content_order_enabled_post_types[] = $post_type_slug;
905 }
906 }
907 if ( is_admin() && 'edit.php' == $pagenow && !isset( $_GET['orderby'] ) ) {
908
909 if ( in_array( $typenow, $content_order_enabled_post_types ) && (post_type_supports( $typenow, 'page-attributes' ) || is_post_type_hierarchical( $typenow )) ) {
910 $wp_query->set( 'orderby', 'menu_order title' );
911 $wp_query->set( 'order', 'ASC' );
912 }
913
914 }
915 }
916
917 /**
918 * Add media replacement button in the edit screen of media/attachment
919 *
920 * @since 1.1.0
921 */
922 public function add_media_replacement_button( $fields, $post )
923 {
924 // Enqueues all scripts, styles, settings, and templates necessary to use all media JS APIs.
925 // Reference: https://codex.wordpress.org/Javascript_Reference/wp.media
926 wp_enqueue_media();
927 // Add new field to attachment fields for the media replace functionality
928 $fields['asenha-media-replace'] = array();
929 $fields['asenha-media-replace']['label'] = '';
930 $fields['asenha-media-replace']['input'] = 'html';
931 $fields['asenha-media-replace']['html'] = '
932 <div id="media-replace-div" class="postbox">
933 <div class="postbox-header">
934 <h2 class="hndle ui-sortable-handle">Replace Media</h2>
935 </div>
936 <div class="inside">
937 <button type="button" id="asenha-media-replace" class="button-secondary button-large asenha-media-replace-button">Select New Media File</button>
938 <input type="hidden" id="new-attachment-id" name="new-attachment-id" />
939 <div class="asenha-media-replace-notes">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.</div>
940 </div>
941 </div>
942 ';
943 return $fields;
944 }
945
946 /**
947 * Replace existing media with the newly updated file
948 *
949 * @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L55
950 * @since 1.1.0
951 */
952 public function replace_media( $old_attachment_id )
953 {
954 $old_post_meta = get_post( $old_attachment_id, ARRAY_A );
955 $old_post_mime = $old_post_meta['post_mime_type'];
956 // e.g. 'image/jpeg'
957 // Get the new attachment/media ID, meta and mime type
958 $new_attachment_id = intval( sanitize_text_field( $_POST['new-attachment-id'] ) );
959 $new_post_meta = get_post( $new_attachment_id, ARRAY_A );
960 $new_post_mime = $new_post_meta['post_mime_type'];
961 // e.g. 'image/jpeg'
962 // Check if the media file ID selected via the media frame and passed on to the #new-attachment-id hidden field
963 // Ensure the mime type matches too
964
965 if ( !empty($new_attachment_id) && is_numeric( $new_attachment_id ) && $old_post_mime == $new_post_mime ) {
966 $new_attachment_meta = wp_get_attachment_metadata( $new_attachment_id );
967 // If original file is larger than 2560 pixel
968 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
969
970 if ( array_key_exists( 'original_image', $new_attachment_meta ) ) {
971 // Get the original media file path
972 $new_media_file_path = wp_get_original_image_path( $new_attachment_id );
973 } else {
974 // Get the path to newly uploaded media file. An image file name may end with '-scaled'.
975 $new_attachment_file = get_post_meta( $new_attachment_id, '_wp_attached_file', true );
976 $upload_dir = wp_upload_dir();
977 $new_media_file_path = $upload_dir['basedir'] . '/' . $new_attachment_file;
978 }
979
980 // Check if the new media file exist / was successfully uploaded
981 if ( !is_file( $new_media_file_path ) ) {
982 return false;
983 }
984 // Delete existing/old media files. Post and post meta entries for it are still there in the database.
985 $this->delete_media_files( $old_attachment_id );
986 // If original file is larger than 2560 pixel
987 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
988
989 if ( array_key_exists( 'original_image', $new_attachment_meta ) ) {
990 // Get the original media file path
991 $old_media_file_path = wp_get_original_image_path( $old_attachment_id );
992 } else {
993 // Get the path to the old/existing media file that will be replaced and deleted. An image file name may end with '-scaled'.
994 $old_attachment_file = get_post_meta( $old_attachment_id, '_wp_attached_file', true );
995 $old_media_file_path = $upload_dir['basedir'] . '/' . $old_attachment_file;
996 }
997
998 // Check if the directory path to the old media file is still intact
999 if ( !file_exists( dirname( $old_media_file_path ) ) ) {
1000 // Recreate the directory path
1001 mkdir( dirname( $old_media_file_path ), 0755, true );
1002 }
1003 // Copy the new media file into the old media file's path
1004 copy( $new_media_file_path, $old_media_file_path );
1005 // Regenerate attachment meta data and image sub-sizes from the new media file that was just copied to the old path
1006 $old_media_post_meta_updated = wp_generate_attachment_metadata( $old_attachment_id, $old_media_file_path );
1007 // 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.
1008 wp_update_attachment_metadata( $old_attachment_id, $old_media_post_meta_updated );
1009 // Delete the newly uploaded media file and it's sub-sizes, and also delete post and post meta entries for it in the database.
1010 wp_delete_attachment( $new_attachment_id, true );
1011 }
1012
1013 }
1014
1015 /**
1016 * Delete the existing/old media files when performing media replacement
1017 *
1018 * @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L80
1019 * @since 1.1.0
1020 */
1021 public function delete_media_files( $post_id )
1022 {
1023 $attachment_meta = wp_get_attachment_metadata( $post_id );
1024 // Will get '-scaled' version if it exists, e.g. /path/to/uploads/year/month/file-name.jpg
1025 $attachment_file_path = get_attached_file( $post_id );
1026 // e.g. file-name.jpg
1027 $attachment_file_basename = basename( $attachment_file_path );
1028 // Delete intermediate images if there are any
1029 if ( isset( $attachment_meta['sizes'] ) && is_array( $attachment_meta['sizes'] ) ) {
1030 foreach ( $attachment_meta['sizes'] as $size => $size_info ) {
1031 // /path/to/uploads/year/month/file-name.jpg --> /path/to/uploads/year/month/file-name-1024x768.jpg
1032 $intermediate_file_path = str_replace( $attachment_file_basename, $size_info['file'], $attachment_file_path );
1033 wp_delete_file( $intermediate_file_path );
1034 }
1035 }
1036 // Delete the attachment file, which maybe the '-scaled' version
1037 wp_delete_file( $attachment_file_path );
1038 // If original file is larger than 2560 pixel
1039 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
1040
1041 if ( array_key_exists( 'original_image', $attachment_meta ) ) {
1042 $attachment_original_file_path = wp_get_original_image_path( $post_id );
1043 // Delete the original file
1044 wp_delete_file( $attachment_original_file_path );
1045 }
1046
1047 }
1048
1049 /**
1050 * Customize the attachment updated message
1051 *
1052 * @link https://github.com/WordPress/wordpress-develop/blob/6.0.2/src/wp-admin/edit-form-advanced.php#L180
1053 * @since 1.1.0
1054 */
1055 public function attachment_updated_custom_message( $messages )
1056 {
1057 $new_messages = array();
1058 foreach ( $messages as $post_type => $messages_array ) {
1059 if ( $post_type == 'attachment' ) {
1060 // 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.
1061 $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.';
1062 }
1063 $new_messages[$post_type] = $messages_array;
1064 }
1065 return $new_messages;
1066 }
1067
1068 /**
1069 * Add SVG mime type for media library uploads
1070 *
1071 * @link https://developer.wordpress.org/reference/hooks/upload_mimes/
1072 * @since 2.6.0
1073 */
1074 public function add_svg_mime( $mimes )
1075 {
1076 global $roles_svg_upload_enabled ;
1077 $current_user = wp_get_current_user();
1078 $current_user_roles = (array) $current_user->roles;
1079 // single dimensional array of role slugs
1080 if ( count( $roles_svg_upload_enabled ) > 0 ) {
1081 // Add mime type for user roles set to enable SVG upload
1082 foreach ( $current_user_roles as $role ) {
1083 if ( in_array( $role, $roles_svg_upload_enabled ) ) {
1084 $mimes['svg'] = 'image/svg+xml';
1085 }
1086 }
1087 }
1088 return $mimes;
1089 }
1090
1091 /**
1092 * Check and confirm if the real file type is indeed SVG
1093 *
1094 * @link https://developer.wordpress.org/reference/functions/wp_check_filetype_and_ext/
1095 * @since 2.6.0
1096 */
1097 public function confirm_file_type_is_svg(
1098 $filetypes_extensions,
1099 $file,
1100 $filename,
1101 $mimes
1102 )
1103 {
1104 global $roles_svg_upload_enabled ;
1105 $current_user = wp_get_current_user();
1106 $current_user_roles = (array) $current_user->roles;
1107 // single dimensional array of role slugs
1108 if ( count( $roles_svg_upload_enabled ) > 0 ) {
1109 // Check file extension
1110 if ( substr( $filename, -4 ) == '.svg' ) {
1111 // Add mime type for user roles set to enable SVG upload
1112 foreach ( $current_user_roles as $role ) {
1113
1114 if ( in_array( $role, $roles_svg_upload_enabled ) ) {
1115 $filetypes_extensions['type'] = 'image/svg+xml';
1116 $filetypes_extensions['ext'] = 'svg';
1117 }
1118
1119 }
1120 }
1121 }
1122 return $filetypes_extensions;
1123 }
1124
1125 /**
1126 * Sanitize the SVG file and maybe allow upload based on the result
1127 *
1128 * @since 2.6.0
1129 */
1130 public function sanitize_and_maybe_allow_svg_upload( $file )
1131 {
1132 if ( !isset( $file['tmp_name'] ) ) {
1133 return $file;
1134 }
1135 $file_tmp_name = $file['tmp_name'];
1136 // full path
1137 $file_name = ( isset( $file['name'] ) ? $file['name'] : '' );
1138 $file_type_ext = wp_check_filetype_and_ext( $file_tmp_name, $file_name );
1139 $file_type = ( !empty($file_type_ext['type']) ? $file_type_ext['type'] : '' );
1140 // Load sanitizer library - https://github.com/darylldoyle/svg-sanitizer
1141 $sanitizer = new Sanitizer();
1142
1143 if ( 'image/svg+xml' === $file_type ) {
1144 $original_svg = file_get_contents( $file_tmp_name );
1145 $sanitized_svg = $sanitizer->sanitize( $original_svg );
1146 // boolean
1147 if ( false === $sanitized_svg ) {
1148 $file['error'] = 'This SVG file could not be sanitized, so, was not uploaded for security reasons.';
1149 }
1150 file_put_contents( $file_tmp_name, $sanitized_svg );
1151 }
1152
1153 return $file;
1154 }
1155
1156 /**
1157 * Generate metadata for the svg attachment
1158 *
1159 * @link https://developer.wordpress.org/reference/functions/wp_generate_attachment_metadata/
1160 * @since 2.6.0
1161 */
1162 public function generate_svg_metadata( $metadata, $attachment_id, $context )
1163 {
1164
1165 if ( get_post_mime_type( $attachment_id ) == 'image/svg+xml' ) {
1166 // Get SVG dimensions
1167 $svg_path = get_attached_file( $attachment_id );
1168 $svg = simplexml_load_file( $svg_path );
1169 $width = 0;
1170 $height = 0;
1171
1172 if ( $svg ) {
1173 $attributes = $svg->attributes();
1174
1175 if ( isset( $attributes->width, $attributes->height ) ) {
1176 $width = intval( floatval( $attributes->width ) );
1177 $height = intval( floatval( $attributes->height ) );
1178 } elseif ( isset( $attributes->viewBox ) ) {
1179 $sizes = explode( ' ', $attributes->viewBox );
1180
1181 if ( isset( $sizes[2], $sizes[3] ) ) {
1182 $width = intval( floatval( $sizes[2] ) );
1183 $height = intval( floatval( $sizes[3] ) );
1184 }
1185
1186 }
1187
1188 }
1189
1190 $metadata['width'] = $width;
1191 $metadata['height'] = $height;
1192 // Get SVG filename
1193 $svg_url = wp_get_original_image_url( $attachment_id );
1194 $svg_url_path = str_replace( wp_upload_dir()['baseurl'] . '/', '', $svg_url );
1195 $metadata['file'] = $svg_url_path;
1196 }
1197
1198 return $metadata;
1199 }
1200
1201 /**
1202 * Return svg file URL to show preview in media library
1203 *
1204 * @link https://developer.wordpress.org/reference/hooks/wp_ajax_action/
1205 * @link https://developer.wordpress.org/reference/functions/wp_get_attachment_url/
1206 * @since 2.6.0
1207 */
1208 public function get_svg_attachment_url()
1209 {
1210 $attachment_url = '';
1211 $attachment_id = ( isset( $_REQUEST['attachmentID'] ) ? $_REQUEST['attachmentID'] : '' );
1212 // Check response mime type
1213
1214 if ( $attachment_id ) {
1215 $attachment_url = wp_get_attachment_url( $attachment_id );
1216 echo $attachment_url ;
1217 die;
1218 }
1219
1220 }
1221
1222 /**
1223 * Return svg file URL to show preview in media library
1224 *
1225 * @link https://developer.wordpress.org/reference/functions/wp_prepare_attachment_for_js/
1226 * @since 2.6.0
1227 */
1228 public function get_svg_url_in_media_library( $response )
1229 {
1230 // Check response mime type
1231 if ( $response['mime'] === 'image/svg+xml' ) {
1232 $response['image'] = array(
1233 'src' => $response['url'],
1234 );
1235 }
1236 return $response;
1237 }
1238
1239 /**
1240 * Add external permalink meta box for enabled post types
1241 *
1242 * @since 3.9.0
1243 */
1244 public function add_external_permalink_meta_box( $post_type, $post )
1245 {
1246 $options = get_option( ASENHA_SLUG_U, array() );
1247 $enable_external_permalinks_for = $options['enable_external_permalinks_for'];
1248 foreach ( $enable_external_permalinks_for as $post_type_slug => $is_external_permalink_enabled ) {
1249 if ( get_post_type() == $post_type_slug && $is_external_permalink_enabled ) {
1250 // Skip adding meta box for post types where Gutenberg is enabled
1251 // if (
1252 // function_exists( 'use_block_editor_for_post_type' )
1253 // && use_block_editor_for_post_type( $post_type_slug )
1254 // ) {
1255 // continue; // go to the beginning of next iteration
1256 // }
1257 add_meta_box(
1258 'asenha-external-permalink',
1259 // ID of meta box
1260 'External Permalink',
1261 // Title of meta box
1262 [ $this, 'output_external_permalink_meta_box' ],
1263 // Callback function
1264 $post_type_slug,
1265 // The screen on which the meta box should be output to
1266 'normal',
1267 // context
1268 'high'
1269 );
1270 }
1271 }
1272 }
1273
1274 /**
1275 * Render External Permalink meta box
1276 *
1277 * @since 3.9.0
1278 */
1279 public function output_external_permalink_meta_box( $post )
1280 {
1281 ?>
1282 <div class="external-permalink-input">
1283 <input name="<?php
1284 echo esc_attr( 'external_permalink' ) ;
1285 ?>" class="large-text" id="<?php
1286 echo esc_attr( 'external_permalink' ) ;
1287 ?>" type="text" value="<?php
1288 echo esc_url( get_post_meta( $post->ID, '_links_to', true ) ) ;
1289 ?>" placeholder="https://" />
1290 <div class="external-permalink-input-description">Keep empty to use the default WordPress permalink. External permalink will open in a new browser tab.</div>
1291 <?php
1292 wp_nonce_field(
1293 'external_permalink_' . $post->ID,
1294 'external_permalink_nonce',
1295 false,
1296 true
1297 );
1298 ?>
1299 </div>
1300 <?php
1301 }
1302
1303 /**
1304 * Save external permalink input
1305 *
1306 * @since 3.9.0
1307 */
1308 public function save_external_permalink( $post_id )
1309 {
1310 // Only proceed if nonce is verified
1311
1312 if ( isset( $_POST['external_permalink_nonce'] ) && wp_verify_nonce( $_POST['external_permalink_nonce'], 'external_permalink_' . $post_id ) ) {
1313 // Get the value of external permalink from input field
1314 $external_permalink = ( isset( $_POST['external_permalink'] ) ? esc_url_raw( trim( $_POST['external_permalink'] ) ) : '' );
1315 // Update or delete external permalink post meta
1316
1317 if ( !empty($external_permalink) ) {
1318 update_post_meta( $post_id, '_links_to', $external_permalink );
1319 } else {
1320 delete_post_meta( $post_id, '_links_to' );
1321 }
1322
1323 }
1324
1325 }
1326
1327 /**
1328 * Change WordPress default permalink into external permalink for pages
1329 *
1330 * @since 3.9.0
1331 */
1332 public function use_external_permalink_for_pages( $permalink, $post_id )
1333 {
1334 $external_permalink = get_post_meta( $post_id, '_links_to', true );
1335 if ( !empty($external_permalink) ) {
1336 $permalink = $external_permalink;
1337 }
1338 return $permalink;
1339 }
1340
1341 /**
1342 * Change WordPress default permalink into external permalink for posts and custom post types
1343 *
1344 * @since 3.9.0
1345 */
1346 public function use_external_permalink_for_posts( $permalink, $post )
1347 {
1348 $external_permalink = get_post_meta( $post->ID, '_links_to', true );
1349
1350 if ( !empty($external_permalink) ) {
1351 $permalink = $external_permalink;
1352 if ( !is_admin() ) {
1353 $permalink = $permalink . '#new_tab';
1354 }
1355 }
1356
1357 return $permalink;
1358 }
1359
1360 /**
1361 * Redirect page/post to external permalink if it's loaded directly from the WP default permalink
1362 *
1363 * @since 3.9.0
1364 */
1365 public function redirect_to_external_permalink()
1366 {
1367 global $post ;
1368 // If not on/loading the single page/post URL, do nothing
1369 if ( !is_singular() ) {
1370 return;
1371 }
1372 $external_permalink = get_post_meta( $post->ID, '_links_to', true );
1373
1374 if ( !empty($external_permalink) ) {
1375 wp_redirect( $external_permalink, 302 );
1376 // temporary redirect
1377 exit;
1378 }
1379
1380 }
1381
1382 /**
1383 * Parse links in content to add target="_blank" rel="noopener noreferrer nofollow" attributes
1384 *
1385 * @since 4.9.0
1386 */
1387 public function add_target_and_rel_atts_to_content_links( $content )
1388 {
1389
1390 if ( !empty($content) ) {
1391 // regex pattern for "a href"
1392 $regexp = "<a\\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>";
1393
1394 if ( preg_match_all(
1395 "/{$regexp}/siU",
1396 $content,
1397 $matches,
1398 PREG_SET_ORDER
1399 ) ) {
1400 // $matches might contain parts of $content that has links (a href)
1401 preg_match_all(
1402 "/{$regexp}/siU",
1403 $content,
1404 $matches,
1405 PREG_SET_ORDER
1406 );
1407
1408 if ( is_array( $matches ) ) {
1409 $i = 0;
1410 foreach ( $matches as $match ) {
1411 $original_tag = $match[0];
1412 // e.g. <a title="Link Title" href="http://www.example.com/sit-quaerat">
1413 $tag = $match[0];
1414 // Same value as $original_tag but for further processing
1415 $url = $match[2];
1416 // e.g. http://www.example.com/sit-quaerat
1417
1418 if ( false !== strpos( $url, get_site_url() ) ) {
1419 // Internal link. Do nothing.
1420 } elseif ( false === strpos( $url, 'http' ) ) {
1421 // Relative link to internal URL. Do nothing.
1422 } else {
1423 // External link. Let's do something.
1424 // Regex pattern for target="_blank|parent|self|top"
1425 $pattern = '/target\\s*=\\s*"\\s*_(blank|parent|self|top)\\s*"/';
1426 // If there's no 'target="_blank|parent|self|top"' in $tag, add target="blank"
1427 if ( 0 === preg_match( $pattern, $tag ) ) {
1428 // Replace closing > with ' target="_blank">'
1429 $tag = substr_replace( $tag, ' target="_blank">', -1 );
1430 }
1431 // If there's no 'rel' attribute in $tag, add rel="noopener noreferrer nofollow"
1432 $pattern = '/rel\\s*=\\s*\\"[a-zA-Z0-9_\\s]*\\"/';
1433
1434 if ( 0 === preg_match( $pattern, $tag ) ) {
1435 // Replace closing > with ' rel="noopener noreferrer nofollow">'
1436 $tag = substr_replace( $tag, ' rel="noopener noreferrer nofollow">', -1 );
1437 } else {
1438 // replace rel="noopener" with rel="noopener noreferrer nofollow"
1439 if ( false !== strpos( $tag, 'noopener' ) && false === strpos( $tag, 'noreferrer' ) && false === strpos( $tag, 'nofollow' ) ) {
1440 $tag = str_replace( 'noopener', 'noopener noreferrer nofollow', $tag );
1441 }
1442 }
1443
1444 // Replace original a href tag with one containing target and rel attributes above
1445 $content = str_replace( $original_tag, $tag, $content );
1446 }
1447
1448 $i++;
1449 }
1450 }
1451
1452 }
1453
1454 }
1455
1456 return $content;
1457 }
1458
1459 /**
1460 * Add "open in new tab" checkbox in custom nav menu item settings
1461 *
1462 * @since 5.4.0
1463 */
1464 public function add_custom_nav_menu_open_in_new_tab_field(
1465 $item_id,
1466 $menu_item,
1467 $depth,
1468 $args
1469 )
1470 {
1471 $target_blank = get_post_meta( $item_id, '_menu_item_target_blank', true );
1472
1473 if ( 'custom' == $menu_item->object ) {
1474 ?>
1475 <p class="field-target_blank description-wide">
1476 <label for="edit-menu-item-target-blank-<?php
1477 echo esc_attr( $item_id ) ;
1478 ?>">
1479 <input type="checkbox" id="edit-menu-item-target-blank-<?php
1480 echo esc_attr( $item_id ) ;
1481 ?>" name="menu-item-target-blank[<?php
1482 echo esc_attr( $item_id ) ;
1483 ?>]" value="1" <?php
1484 checked( $target_blank, '1' );
1485 ?> />
1486 Open link in new tab and add rel="noopener noreferrer nofollow" attribute.
1487 </label>
1488 </p>
1489 <?php
1490 }
1491
1492 }
1493
1494 /**
1495 * Save status of "open in new tab" checkbox in custom nav menu item settings
1496 *
1497 * @since 5.4.0
1498 */
1499 public function save_custom_nav_menu_open_in_new_tab_status( $menu_id, $menu_item_db_id, $args )
1500 {
1501
1502 if ( isset( $_POST['menu-item-target-blank'][$menu_item_db_id] ) ) {
1503 update_post_meta( $menu_item_db_id, '_menu_item_target_blank', '1' );
1504 } else {
1505 delete_post_meta( $menu_item_db_id, '_menu_item_target_blank' );
1506 }
1507
1508 }
1509
1510 /**
1511 * Add attributes to custom nav menu item on the frontend
1512 *
1513 * @since 5.4.0
1514 */
1515 public function add_attributes_to_custom_nav_menu_item( $atts, $menu_item, $args )
1516 {
1517 $target_blank = get_post_meta( $menu_item->ID, '_menu_item_target_blank', true );
1518
1519 if ( $target_blank ) {
1520 $atts['target'] = '_blank';
1521 $atts['rel'] = 'noopener noreferrer nofollow';
1522 }
1523
1524 return $atts;
1525 }
1526
1527 /**
1528 * Publish posts of any type with missed schedule.
1529 * We use the Transients API to reduce straining the site with DB queries on busy sites.
1530 * So, this function will only query the DB once every 15 minutes at most.
1531 *
1532 * @since 3.1.0
1533 */
1534 public function publish_missed_schedule_posts()
1535 {
1536
1537 if ( is_front_page() || is_home() || is_page() || is_single() || is_singular() || is_archive() || is_admin() || is_blog_admin() || is_robots() || is_ssl() ) {
1538 // Get missed schedule posts data from cache
1539 $missed_schedule_posts = get_transient( 'asenha_missed_schedule_posts' );
1540 // Nothing found in cache
1541
1542 if ( false === $missed_schedule_posts ) {
1543 global $wpdb ;
1544 $current_gmt_datetime = gmdate( 'Y-m-d H:i:00' );
1545 $args = array(
1546 'public' => true,
1547 '_builtin' => false,
1548 );
1549 $custom_post_types = get_post_types( $args, 'names' );
1550 // array, e.g. array( 'project', 'book', 'staff' )
1551
1552 if ( count( $custom_post_types ) > 0 ) {
1553 $custom_post_types = "'" . implode( "','", $custom_post_types ) . "'";
1554 // string, e.g. 'project','book','staff'
1555 $post_types = "'page','post'," . $custom_post_types;
1556 // 'page','post','project','book','staff'
1557 } else {
1558 $post_types = "'page','post'";
1559 }
1560
1561 $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ({$post_types}) AND post_status='future' AND post_date_gmt<'{$current_gmt_datetime}'";
1562 // The following does not work as backslashes are inserted before single quotes in $post_types
1563 // $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 ) );
1564 $missed_schedule_posts = $wpdb->get_results( $sql, ARRAY_A );
1565 // Save query results as a transient with expiry of 15 minutes
1566 set_transient( 'asenha_missed_schedule_posts', $missed_schedule_posts, 15 * MINUTE_IN_SECONDS );
1567 }
1568
1569 if ( empty($missed_schedule_posts) || !is_array( $missed_schedule_posts ) ) {
1570 return;
1571 }
1572 foreach ( $missed_schedule_posts as $post ) {
1573 wp_publish_post( $post['ID'] );
1574 }
1575 }
1576
1577 }
1578
1579 }