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