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