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