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