| @@ -30,8 +30,78 @@ | ||
| 30 | 30 | return __CLASS__; |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | /** |
| 34 | + * Check UI scope without the REST session capability requirement. | |
| 35 | + * | |
| 36 | + * @param int $client_id Client ID. | |
| 37 | + * @param bool $full Require access to every member site. | |
| 38 | + * @return bool Whether the client is in scope. | |
| 39 | + */ | |
| 40 | + public static function has_client_access( $client_id, $full = false ) { | |
| 41 | + $allowed = MainWP_Abilities_Util::get_allowed_site_ids(); | |
| 42 | + if ( 'all' === $allowed ) { | |
| 43 | + return true; | |
| 44 | + } | |
| 45 | + if ( (int) $client_id <= 0 ) { | |
| 46 | + return false; | |
| 47 | + } | |
| 48 | + $sites = MainWP_DB_Client::instance()->get_websites_by_client_ids( (int) $client_id, array( 'no_perm_check' => true ) ); | |
| 49 | + if ( ! is_array( $sites ) ) { | |
| 50 | + return false; | |
| 51 | + } | |
| 52 | + $ids = array_map( 'intval', wp_list_pluck( $sites, 'id' ) ); | |
| 53 | + if ( empty( $ids ) || ! array_intersect( $ids, $allowed ) ) { | |
| 54 | + return false; | |
| 55 | + } | |
| 56 | + return ! $full || ! array_diff( $ids, $allowed ); | |
| 57 | + } | |
| 58 | + | |
| 59 | + /** | |
| 60 | + * Keep only the clients a restricted user may view: at least one of the client's sites is allowed. | |
| 61 | + * | |
| 62 | + * Judged on the site rows from one batched roster query, never on the GROUP_CONCAT selected_sites | |
| 63 | + * column: MySQL truncates that string at group_concat_max_len, and a cut-off id fragment can match | |
| 64 | + * an allowed site the client does not have. | |
| 65 | + * | |
| 66 | + * @param mixed $clients Client rows, arrays or objects carrying client_id. | |
| 67 | + * @return mixed The input unchanged for unrestricted users or non-arrays, otherwise the visible rows reindexed. | |
| 68 | + */ | |
| 69 | + public static function filter_viewable_clients( $clients ) { | |
| 70 | + $allowed = MainWP_Abilities_Util::get_allowed_site_ids(); | |
| 71 | + if ( 'all' === $allowed || ! is_array( $clients ) ) { | |
| 72 | + return $clients; | |
| 73 | + } | |
| 74 | + | |
| 75 | + $client_id = static function ( $client ) { | |
| 76 | + if ( is_array( $client ) ) { | |
| 77 | + return isset( $client['client_id'] ) ? (int) $client['client_id'] : 0; | |
| 78 | + } | |
| 79 | + return is_object( $client ) && isset( $client->client_id ) ? (int) $client->client_id : 0; | |
| 80 | + }; | |
| 81 | + | |
| 82 | + $visible = array(); | |
| 83 | + $ids = array_filter( array_unique( array_map( $client_id, $clients ) ) ); | |
| 84 | + if ( ! empty( $ids ) && ! empty( $allowed ) ) { | |
| 85 | + $sites = MainWP_DB_Client::instance()->get_websites_by_client_ids( array_values( $ids ), array( 'no_perm_check' => true ) ); | |
| 86 | + foreach ( (array) $sites as $site ) { | |
| 87 | + if ( in_array( (int) $site->id, $allowed, true ) ) { | |
| 88 | + $visible[ (int) $site->client_id ] = true; | |
| 89 | + } | |
| 90 | + } | |
| 91 | + } | |
| 92 | + | |
| 93 | + return array_values( | |
| 94 | + array_filter( | |
| 95 | + $clients, | |
| 96 | + static function ( $client ) use ( $client_id, $visible ) { | |
| 97 | + return isset( $visible[ $client_id( $client ) ] ); | |
| 98 | + } | |
| 99 | + ) | |
| 100 | + ); | |
| 101 | + } | |
| 102 | + | |
| 103 | + /** | |
| 34 | 104 | * Current page. |
| 35 | 105 | * |
| 36 | 106 | * @static |
| 37 | 107 | * @var string $page Current page. |
| @@ -128,9 +198,9 @@ | ||
| 128 | 198 | add_submenu_page( |
| 129 | 199 | 'mainwp_tab', |
| 130 | 200 | esc_html__( 'Clients', 'mainwp' ), |
| 131 | 201 | '<div class="mainwp-hidden">' . esc_html__( 'Client Fields', 'mainwp' ) . '</div>', |
| 132 | - 'read', | |
| 202 | + 'manage_options', | |
| 133 | 203 | 'ClientAddField', |
| 134 | 204 | array( |
| 135 | 205 | static::get_class_name(), |
| 136 | 206 | 'render_client_fields', |
| @@ -211,9 +281,9 @@ | ||
| 211 | 281 | <?php } ?> |
| 212 | 282 | <?php if ( ! MainWP_Menu::is_disable_menu_item( 3, 'ClientImport' ) ) { ?> |
| 213 | 283 | <a href="<?php echo esc_url( admin_url( 'admin.php?page=ClientImport' ) ); ?>" class="mainwp-submenu"><?php esc_html_e( 'Import Clients', 'mainwp' ); ?></a> |
| 214 | 284 | <?php } ?> |
| 215 | - <?php if ( ! MainWP_Menu::is_disable_menu_item( 3, 'ClientAddField' ) ) { ?> | |
| 285 | + <?php if ( \mainwp_current_user_can( 'dashboard', 'manage_clients' ) && ! MainWP_Menu::is_disable_menu_item( 3, 'ClientAddField' ) ) { ?> | |
| 216 | 286 | <a href="<?php echo esc_url( admin_url( 'admin.php?page=ClientAddField' ) ); ?>" class="mainwp-submenu"><?php esc_html_e( 'Client Properties', 'mainwp' ); ?></a> |
| 217 | 287 | <?php } ?> |
| 218 | 288 | <?php |
| 219 | 289 | if ( isset( static::$subPages ) && is_array( static::$subPages ) ) { |
| @@ -284,9 +354,9 @@ | ||
| 284 | 354 | 'title' => esc_html__( 'Client Fields', 'mainwp' ), |
| 285 | 355 | 'parent_key' => 'ManageClients', |
| 286 | 356 | 'href' => 'admin.php?page=ClientAddField', |
| 287 | 357 | 'slug' => 'ClientAddField', |
| 288 | - 'right' => '', | |
| 358 | + 'right' => 'manage_clients', | |
| 289 | 359 | 'leftsub_order_level2' => 4, |
| 290 | 360 | ), |
| 291 | 361 | ); |
| 292 | 362 | |
| @@ -311,8 +381,17 @@ | ||
| 311 | 381 | $delete = isset( $_POST['delete'] ) ? intval( $_POST['delete'] ) : 0; |
| 312 | 382 | $client_id = isset( $_POST['iconItemId'] ) ? intval( $_POST['iconItemId'] ) : 0; |
| 313 | 383 | $delnonce = isset( $_POST['delnonce'] ) ? sanitize_key( $_POST['delnonce'] ) : ''; |
| 314 | 384 | |
| 385 | + if ( ! \mainwp_current_user_can( 'dashboard', 'manage_clients' ) || ( $client_id && ( ! static::has_client_access( $client_id ) || ! MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client_id ) ) ) ) { | |
| 386 | + static::exit_json( | |
| 387 | + array( | |
| 388 | + 'result' => 'failed', | |
| 389 | + 'error' => esc_html__( 'Error - insufficient permissions!', 'mainwp' ), | |
| 390 | + ) | |
| 391 | + ); | |
| 392 | + } | |
| 393 | + | |
| 315 | 394 | if ( $delete ) { |
| 316 | 395 | if ( ! MainWP_System_Utility::is_valid_custom_nonce( 'client', $iconfile_slug, $delnonce ) ) { |
| 317 | 396 | die( 'Invalid nonce!' ); |
| 318 | 397 | } |
| @@ -323,12 +402,12 @@ | ||
| 323 | 402 | 'image' => '', |
| 324 | 403 | 'client_id' => $client_id, |
| 325 | 404 | ); |
| 326 | 405 | MainWP_DB_Client::instance()->update_client( $update ); |
| 327 | - MainWP_Utility::instance()->delete_uploaded_icon_file( 'client-images', $client->image ); | |
| 406 | + MainWP_Utility::delete_client_image( $client->image ); | |
| 328 | 407 | } |
| 329 | 408 | } elseif ( ! empty( $iconfile_slug ) ) { |
| 330 | - MainWP_Utility::instance()->delete_uploaded_icon_file( 'client-images', $iconfile_slug ); | |
| 409 | + MainWP_Utility::delete_client_image( $iconfile_slug ); | |
| 331 | 410 | } |
| 332 | 411 | wp_die( wp_json_encode( array( 'result' => 'success' ) ) ); |
| 333 | 412 | } |
| 334 | 413 | |
| @@ -335,9 +414,9 @@ | ||
| 335 | 414 | $output = isset( $_FILES['mainwp_upload_icon_uploader'] ) ? MainWP_System_Utility::handle_upload_image( 'client-images', $_FILES['mainwp_upload_icon_uploader'] ) : null; |
| 336 | 415 | // phpcs:enable WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 337 | 416 | |
| 338 | 417 | $uploaded_icon = 'NOTCHANGE'; |
| 339 | - if ( is_array( $output ) && isset( $output['filename'] ) && ! empty( $output['filename'] ) ) { | |
| 418 | + if ( is_array( $output ) && isset( $output['filename'] ) && MainWP_Utility::is_valid_client_image_name( $output['filename'] ) ) { | |
| 340 | 419 | $uploaded_icon = $output['filename']; |
| 341 | 420 | } |
| 342 | 421 | |
| 343 | 422 | if ( 'NOTCHANGE' !== $uploaded_icon ) { |
| @@ -377,8 +456,18 @@ | ||
| 377 | 456 | $delete = isset( $_POST['delete'] ) ? intval( $_POST['delete'] ) : 0; |
| 378 | 457 | $contact_id = isset( $_POST['iconItemId'] ) ? intval( $_POST['iconItemId'] ) : 0; |
| 379 | 458 | $delnonce = isset( $_POST['delnonce'] ) ? sanitize_key( $_POST['delnonce'] ) : ''; |
| 380 | 459 | |
| 460 | + $contact = $contact_id ? MainWP_DB_Client::instance()->get_wp_client_contact_by( 'contact_id', $contact_id ) : null; | |
| 461 | + if ( ! \mainwp_current_user_can( 'dashboard', 'manage_clients' ) || ( $contact_id && ( ! $contact || ! static::has_client_access( $contact->contact_client_id ) ) ) ) { | |
| 462 | + static::exit_json( | |
| 463 | + array( | |
| 464 | + 'result' => 'failed', | |
| 465 | + 'error' => esc_html__( 'Error - insufficient permissions!', 'mainwp' ), | |
| 466 | + ) | |
| 467 | + ); | |
| 468 | + } | |
| 469 | + | |
| 381 | 470 | if ( $delete ) { |
| 382 | 471 | if ( ! MainWP_System_Utility::is_valid_custom_nonce( 'contact', $iconfile_slug, $delnonce ) ) { |
| 383 | 472 | die( 'Invalid nonce!' ); |
| 384 | 473 | } |
| @@ -389,12 +478,12 @@ | ||
| 389 | 478 | 'contact_image' => '', |
| 390 | 479 | 'contact_id' => $contact_id, |
| 391 | 480 | ); |
| 392 | 481 | MainWP_DB_Client::instance()->update_client_contact( $update ); |
| 393 | - MainWP_Utility::instance()->delete_uploaded_icon_file( 'client-images', $contact_data->contact_image ); | |
| 482 | + MainWP_Utility::delete_client_image( $contact_data->contact_image ); | |
| 394 | 483 | } |
| 395 | 484 | } elseif ( ! empty( $iconfile_slug ) ) { |
| 396 | - MainWP_Utility::instance()->delete_uploaded_icon_file( 'client-images', $iconfile_slug ); | |
| 485 | + MainWP_Utility::delete_client_image( $iconfile_slug ); | |
| 397 | 486 | } |
| 398 | 487 | wp_die( wp_json_encode( array( 'result' => 'success' ) ) ); |
| 399 | 488 | } |
| 400 | 489 | |
| @@ -401,9 +490,9 @@ | ||
| 401 | 490 | $output = isset( $_FILES['mainwp_upload_icon_uploader'] ) ? MainWP_System_Utility::handle_upload_image( 'client-images', $_FILES['mainwp_upload_icon_uploader'] ) : null; |
| 402 | 491 | // phpcs:enable WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 403 | 492 | |
| 404 | 493 | $uploaded_icon = 'NOTCHANGE'; |
| 405 | - if ( is_array( $output ) && isset( $output['filename'] ) && ! empty( $output['filename'] ) ) { | |
| 494 | + if ( is_array( $output ) && isset( $output['filename'] ) && MainWP_Utility::is_valid_client_image_name( $output['filename'] ) ) { | |
| 406 | 495 | $uploaded_icon = $output['filename']; |
| 407 | 496 | } |
| 408 | 497 | |
| 409 | 498 | if ( 'NOTCHANGE' !== $uploaded_icon ) { |
| @@ -447,8 +536,13 @@ | ||
| 447 | 536 | public static function render_header( $shownPage = '' ) { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated. |
| 448 | 537 | // phpcs:disable WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 449 | 538 | $client_id = isset( $_GET['client_id'] ) ? intval( $_GET['client_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 450 | 539 | |
| 540 | + if ( $client_id && ( ( ! \mainwp_current_user_can( 'dashboard', 'manage_clients' ) && ! \mainwp_current_user_can( 'dashboard', 'access_client_dashboard' ) ) || ! static::has_client_access( $client_id ) ) ) { | |
| 541 | + \mainwp_do_not_have_permissions( esc_html__( 'client dashboard', 'mainwp' ) ); | |
| 542 | + return; | |
| 543 | + } | |
| 544 | + | |
| 451 | 545 | $params = array( |
| 452 | 546 | 'title' => esc_html__( 'Clients', 'mainwp' ), |
| 453 | 547 | 'which' => 'overview' === $shownPage ? 'page_clients_overview' : '', |
| 454 | 548 | ); |
| @@ -458,9 +552,9 @@ | ||
| 458 | 552 | $client = MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client_id ); |
| 459 | 553 | if ( $client ) { |
| 460 | 554 | $arr_client = MainWP_Utility::map_fields( $client, array( 'image', 'selected_icon_info' ), false ); // array map. |
| 461 | 555 | $client_pic = MainWP_Client_Handler::get_client_contact_image( $arr_client ); |
| 462 | - $params['title'] = $client_pic . '<div class="content">' . $client->name . '<div class="sub header"><a href="mailto:' . esc_attr( $client->client_email ) . '" target="_blank" style="font-weight:normal!important;">' . $client->client_email . '</a> </div></div>'; | |
| 556 | + $params['title'] = $client_pic . '<div class="content">' . esc_html( $client->name ) . '<div class="sub header"><a href="' . esc_url( 'mailto:' . $client->client_email ) . '" target="_blank" style="font-weight:normal!important;">' . esc_html( $client->client_email ) . '</a> </div></div>'; | |
| 463 | 557 | } |
| 464 | 558 | } |
| 465 | 559 | |
| 466 | 560 | MainWP_UI::render_top_header( $params ); |
| @@ -652,9 +746,9 @@ | ||
| 652 | 746 | <div id="mainwp-edit-clients-modal" class="ui modal"> |
| 653 | 747 | <i class="close icon"></i> |
| 654 | 748 | <div class="header"><?php esc_html_e( 'Edit client', 'mainwp' ); ?></div> |
| 655 | 749 | <div class="scrolling content"> |
| 656 | - <div class="ui info message"><?php esc_html_e( 'Empty fields will not be passed to child sites.', 'mainwp' ); ?></div> | |
| 750 | + <div class="ui info message"><?php esc_html_e( 'Blank fields will not be changed.', 'mainwp' ); ?></div> | |
| 657 | 751 | <form id="update_client_profile" class="ui form"> |
| 658 | 752 | <?php MainWP_UI::generate_wp_nonce( 'mainwp-admin-nonce' ); ?> |
| 659 | 753 | <h3 class="ui header"><?php esc_html_e( 'Name', 'mainwp' ); ?></h3> |
| 660 | 754 | <div class="ui grid field"> |
| @@ -900,8 +994,12 @@ | ||
| 900 | 994 | /** |
| 901 | 995 | * Renders the Add New Client form. |
| 902 | 996 | */ |
| 903 | 997 | public static function render_add_client() { |
| 998 | + if ( ! \mainwp_current_user_can( 'dashboard', 'manage_clients' ) ) { | |
| 999 | + \mainwp_do_not_have_permissions( esc_html__( 'manage clients', 'mainwp' ) ); | |
| 1000 | + return; | |
| 1001 | + } | |
| 904 | 1002 | $show = 'Add'; |
| 905 | 1003 | $client_id = 0; |
| 906 | 1004 | $selected_sites = array(); |
| 907 | 1005 | $edit_client = false; |
| @@ -906,11 +1004,19 @@ | ||
| 906 | 1004 | $selected_sites = array(); |
| 907 | 1005 | $edit_client = false; |
| 908 | 1006 | |
| 909 | 1007 | if ( isset( $_GET['client_id'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended |
| 910 | - $show = 'Edit'; | |
| 911 | - $client_id = intval( $_GET['client_id'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended | |
| 912 | - $edit_client = $client_id ? MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client_id ) : false; | |
| 1008 | + $show = 'Edit'; | |
| 1009 | + $client_id = intval( $_GET['client_id'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended | |
| 1010 | + if ( ! static::has_client_access( $client_id ) ) { | |
| 1011 | + \mainwp_do_not_have_permissions( esc_html__( 'manage clients', 'mainwp' ) ); | |
| 1012 | + return; | |
| 1013 | + } | |
| 1014 | + $edit_client = $client_id ? MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client_id ) : false; | |
| 1015 | + if ( ! $edit_client ) { | |
| 1016 | + \mainwp_do_not_have_permissions( esc_html__( 'manage clients', 'mainwp' ) ); | |
| 1017 | + return; | |
| 1018 | + } | |
| 913 | 1019 | $client_sites = MainWP_DB_Client::instance()->get_websites_by_client_ids( $client_id ); |
| 914 | 1020 | |
| 915 | 1021 | if ( $client_sites ) { |
| 916 | 1022 | foreach ( $client_sites as $site ) { |
| @@ -989,8 +1095,12 @@ | ||
| 989 | 1095 | /** |
| 990 | 1096 | * Renders the Import Client form. |
| 991 | 1097 | */ |
| 992 | 1098 | public static function render_import_clients() { // phpcs:ignore -- NOSONAR - Current complexity is the only way to achieve desired results, pull request solutions appreciated. |
| 1099 | + if ( ! \mainwp_current_user_can( 'dashboard', 'manage_clients' ) ) { | |
| 1100 | + \mainwp_do_not_have_permissions( esc_html__( 'manage clients', 'mainwp' ) ); | |
| 1101 | + return; | |
| 1102 | + } | |
| 993 | 1103 | static::render_header( 'ImportClients' ); |
| 994 | 1104 | $title_page = esc_html__( 'Import Clients', 'mainwp' ); |
| 995 | 1105 | // phpcs:disable WordPress.Security.NonceVerification.Missing |
| 996 | 1106 | |
| @@ -996,15 +1106,22 @@ | ||
| 996 | 1106 | |
| 997 | 1107 | $has_import_data = ! empty( $_POST['mainwp_client_import_add'] ); |
| 998 | 1108 | // phpcs:enable WordPress.Security.NonceVerification.Missing |
| 999 | 1109 | if ( $has_import_data && check_admin_referer( 'mainwp-admin-nonce' ) ) { |
| 1000 | - static::render_import_client_modal(); | |
| 1110 | + $result = static::render_import_client_modal(); | |
| 1001 | 1111 | } |
| 1002 | 1112 | ?> |
| 1003 | 1113 | <div class="ui padded segment" id="mainwp-import-clients"> |
| 1004 | 1114 | <form method="POST" action="" enctype="multipart/form-data" id="mainwp_client_import_form" class="ui form"> |
| 1005 | 1115 | <div> |
| 1006 | - <?php $el_id_mes_zn_1 = 'mainwp-message-zone'; ?> | |
| 1116 | + <?php | |
| 1117 | + if ( ! empty( $result ) && is_wp_error( $result ) ) { | |
| 1118 | + ?> | |
| 1119 | + <div class="ui message yellow"><i class="close icon"></i> <?php echo esc_html( $result->get_error_message() ); ?></div> | |
| 1120 | + <?php | |
| 1121 | + } | |
| 1122 | + $el_id_mes_zn_1 = 'mainwp-message-zone'; | |
| 1123 | + ?> | |
| 1007 | 1124 | <div id="<?php echo esc_attr( $el_id_mes_zn_1 ); ?>" class="ui message" style="display:none"></div> |
| 1008 | 1125 | <h3 class="ui dividing header"> |
| 1009 | 1126 | <?php echo esc_html( $title_page ); ?> |
| 1010 | 1127 | <div class="sub header"><?php esc_html_e( 'Import multiple clients to your MainWP Dashboard.', 'mainwp' ); ?></div> |
| @@ -1038,16 +1155,29 @@ | ||
| 1038 | 1155 | /** |
| 1039 | 1156 | * Method render_import_client_modal() |
| 1040 | 1157 | * |
| 1041 | 1158 | * Render HTML import client modal. |
| 1159 | + * | |
| 1160 | + * @return mixed Result. | |
| 1042 | 1161 | */ |
| 1043 | 1162 | public static function render_import_client_modal() { |
| 1163 | + $import_client_data = false; | |
| 1164 | + $has_file_upload = isset( $_FILES['mainwp_client_import_file_bulkupload'] ) && isset( $_FILES['mainwp_client_import_file_bulkupload']['error'] ) && UPLOAD_ERR_OK === $_FILES['mainwp_client_import_file_bulkupload']['error']; // phpcs:ignore -- NOSONAR | |
| 1165 | + if ( $has_file_upload ) { | |
| 1166 | + $import_client_data = static::handle_client_import_files(); | |
| 1167 | + } | |
| 1168 | + if ( ! is_array( $import_client_data ) || empty( $import_client_data['data'] ) || ! is_array( $import_client_data['data'] ) ) { | |
| 1169 | + return new \WP_Error( | |
| 1170 | + 'client_import_failed', | |
| 1171 | + __( 'Failed to import client data. Please try again.', 'mainwp' ) | |
| 1172 | + ); | |
| 1173 | + } | |
| 1044 | 1174 | ?> |
| 1045 | 1175 | <div class="ui large modal mainwp-qsw-import-client-modal" id="mainwp-import-client-modal" > |
| 1046 | 1176 | <i class="close icon"></i> |
| 1047 | 1177 | <div class="header"><?php esc_html_e( 'Import Clients', 'mainwp' ); ?></div> |
| 1048 | 1178 | <div class="scrolling content"> |
| 1049 | - <?php static::render_import_client_row_modal(); ?> | |
| 1179 | + <?php static::render_import_client_row_modal( $import_client_data ); ?> | |
| 1050 | 1180 | </div> |
| 1051 | 1181 | <div class="actions"> |
| 1052 | 1182 | <div class="ui two column grid"> |
| 1053 | 1183 | <div class="left aligned middle aligned column"> |
| @@ -1067,8 +1197,10 @@ | ||
| 1067 | 1197 | } ).modal( 'show' ); |
| 1068 | 1198 | } ); |
| 1069 | 1199 | </script> |
| 1070 | 1200 | <?php |
| 1201 | + | |
| 1202 | + return true; | |
| 1071 | 1203 | } |
| 1072 | 1204 | |
| 1073 | 1205 | /** |
| 1074 | 1206 | * Method render_import_client_row_modal() |
| @@ -1073,50 +1205,50 @@ | ||
| 1073 | 1205 | /** |
| 1074 | 1206 | * Method render_import_client_row_modal() |
| 1075 | 1207 | * |
| 1076 | 1208 | * Render row HTML import client. |
| 1209 | + * | |
| 1210 | + * @param array $client_data Import row data. | |
| 1211 | + * | |
| 1212 | + * @return mixed Result or WP_Error. | |
| 1077 | 1213 | */ |
| 1078 | - public static function render_import_client_row_modal() { | |
| 1079 | - ?> | |
| 1080 | - <div id="mainwp-importing-clients" class="ui active dimmer"> | |
| 1081 | - <div class="ui double text loader"><?php esc_html_e( 'Importing...', 'mainwp' ); ?></div> | |
| 1082 | - </div> | |
| 1083 | - <div class="ui message" id="mainwp-import-clients-status-message"> | |
| 1084 | - <i class="notched circle loading icon"></i> <?php echo esc_html__( 'Importing...', 'mainwp' ); ?> | |
| 1085 | - </div> | |
| 1086 | - <?php | |
| 1087 | - $has_file_upload = isset( $_FILES['mainwp_client_import_file_bulkupload'] ) && isset( $_FILES['mainwp_client_import_file_bulkupload']['error'] ) && UPLOAD_ERR_OK === $_FILES['mainwp_client_import_file_bulkupload']['error']; // phpcs:ignore -- NOSONAR | |
| 1214 | + public static function render_import_client_row_modal( $client_data = array() ) { // phpcs:ignore -- NOSONAR -complex. | |
| 1088 | 1215 | |
| 1089 | - if ( $has_file_upload ) { | |
| 1090 | - $import_client_data = static::handle_client_import_files(); | |
| 1091 | - if ( ! empty( $import_client_data ) && is_array( $import_client_data ) ) { | |
| 1092 | - $row = 0; | |
| 1093 | - $header_line = trim( $import_client_data['header_line'] ); | |
| 1094 | - foreach ( $import_client_data['data'] as $val_client_data ) { | |
| 1095 | - $encoded = wp_json_encode( $val_client_data ); | |
| 1096 | - $original = implode( | |
| 1097 | - ', ', | |
| 1098 | - array_map( | |
| 1099 | - function ( $item ) { | |
| 1100 | - return is_array( $item ) ? implode( ';', $item ) : $item; | |
| 1101 | - }, | |
| 1102 | - $val_client_data | |
| 1103 | - ) | |
| 1104 | - ); | |
| 1105 | - ?> | |
| 1106 | - <input type="hidden" id="mainwp_manageclients_import_csv_line_<?php echo esc_attr( $row + 1 ); ?>" value="" encoded-data="<?php echo esc_attr( $encoded ); ?>" original="<?php echo esc_attr( $original ); ?>"/> | |
| 1107 | - <?php | |
| 1108 | - ++$row; | |
| 1109 | - } | |
| 1216 | + if ( is_array( $client_data ) && ! empty( $client_data['data'] ) && is_array( $client_data['data'] ) ) { | |
| 1217 | + ?> | |
| 1218 | + <div id="mainwp-importing-clients" class="ui active dimmer"> | |
| 1219 | + <div class="ui double text loader"><?php esc_html_e( 'Importing...', 'mainwp' ); ?></div> | |
| 1220 | + </div> | |
| 1221 | + <div class="ui message" id="mainwp-import-clients-status-message"> | |
| 1222 | + <i class="notched circle loading icon"></i> <?php echo esc_html__( 'Importing...', 'mainwp' ); ?> | |
| 1223 | + </div> | |
| 1224 | + <?php | |
| 1225 | + $row = 0; | |
| 1226 | + $header_line = trim( $client_data['header_line'] ); | |
| 1227 | + foreach ( $client_data['data'] as $val_client_data ) { | |
| 1228 | + $encoded = wp_json_encode( $val_client_data ); | |
| 1229 | + $original = implode( | |
| 1230 | + ', ', | |
| 1231 | + array_map( | |
| 1232 | + function ( $item ) { | |
| 1233 | + return is_array( $item ) ? implode( ';', $item ) : $item; | |
| 1234 | + }, | |
| 1235 | + $val_client_data | |
| 1236 | + ) | |
| 1237 | + ); | |
| 1110 | 1238 | ?> |
| 1111 | - <input type="hidden" id="mainwp_manageclients_do_import" value="1"/> | |
| 1112 | - <input type="hidden" id="mainwp_manageclients_total_import" value="<?php echo esc_attr( $row ); ?>"/> | |
| 1113 | - <div class="mainwp_manageclients_import_listing" id="mainwp_manageclients_import_logging"> | |
| 1114 | - <span class="log ui medium text"><?php echo esc_html( $header_line ) . '<br/>'; ?></span> | |
| 1115 | - </div> | |
| 1116 | - <div class="mainwp_manageclients_import_listing" id="mainwp_manageclients_import_fail_logging" style="display: none;"><?php echo esc_html( $header_line ); ?> </div> | |
| 1239 | + <input type="hidden" id="mainwp_manageclients_import_csv_line_<?php echo esc_attr( $row + 1 ); ?>" value="" encoded-data="<?php echo esc_attr( $encoded ); ?>" original="<?php echo esc_attr( $original ); ?>"/> | |
| 1117 | 1240 | <?php |
| 1241 | + ++$row; | |
| 1118 | 1242 | } |
| 1243 | + ?> | |
| 1244 | + <input type="hidden" id="mainwp_manageclients_do_import" value="1"/> | |
| 1245 | + <input type="hidden" id="mainwp_manageclients_total_import" value="<?php echo esc_attr( $row ); ?>"/> | |
| 1246 | + <div class="mainwp_manageclients_import_listing" id="mainwp_manageclients_import_logging"> | |
| 1247 | + <span class="log ui medium text"><?php echo esc_html( $header_line ) . '<br/>'; ?></span> | |
| 1248 | + </div> | |
| 1249 | + <div class="mainwp_manageclients_import_listing" id="mainwp_manageclients_import_fail_logging" style="display: none;"><?php echo esc_html( $header_line ); ?> </div> | |
| 1250 | + <?php | |
| 1119 | 1251 | } |
| 1120 | 1252 | } |
| 1121 | 1253 | |
| 1122 | 1254 | /** |
| @@ -1129,10 +1261,20 @@ | ||
| 1129 | 1261 | * |
| 1130 | 1262 | * @return array Import data. |
| 1131 | 1263 | */ |
| 1132 | 1264 | public static function handle_client_import_files() { // phpcs:ignore -- NOSONAR |
| 1133 | - $tmp_path = isset( $_FILES['mainwp_client_import_file_bulkupload']['tmp_name'] ) ? sanitize_text_field( wp_unslash( $_FILES['mainwp_client_import_file_bulkupload']['tmp_name'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- NOSONAR | |
| 1134 | - MainWP_System_Utility::get_wp_file_system(); | |
| 1265 | + | |
| 1266 | + if ( isset( $_FILES['mainwp_client_import_file_bulkupload']['error'] ) && UPLOAD_ERR_OK !== $_FILES['mainwp_client_import_file_bulkupload']['error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- NOSONAR | |
| 1267 | + return false; | |
| 1268 | + } | |
| 1269 | + | |
| 1270 | + $tmp_path = isset( $_FILES['mainwp_client_import_file_bulkupload']['tmp_name'] ) ? $_FILES['mainwp_client_import_file_bulkupload']['tmp_name'] : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- NOSONAR | |
| 1271 | + | |
| 1272 | + if ( ! is_uploaded_file( $tmp_path ) ) { | |
| 1273 | + return false; | |
| 1274 | + } | |
| 1275 | + | |
| 1276 | + $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system(); | |
| 1135 | 1277 | //phpcs:enable |
| 1136 | 1278 | /** |
| 1137 | 1279 | * WordPress files system object. |
| 1138 | 1280 | * |
| @@ -1139,14 +1281,23 @@ | ||
| 1139 | 1281 | * @global object |
| 1140 | 1282 | */ |
| 1141 | 1283 | global $wp_filesystem; |
| 1142 | 1284 | |
| 1143 | - $content = $wp_filesystem->get_contents( $tmp_path ); | |
| 1285 | + $lines = array(); | |
| 1286 | + $header_line = ''; | |
| 1144 | 1287 | |
| 1145 | - // to compatible with EOL on OSs. | |
| 1146 | - $content = str_replace( "\r\n", "\r", $content ); | |
| 1147 | - $content = str_replace( "\n", "\r", $content ); | |
| 1148 | - $lines = explode( "\r", $content ); | |
| 1288 | + if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) { | |
| 1289 | + $content = $wp_filesystem->get_contents( $tmp_path ); | |
| 1290 | + if ( ! empty( $content ) ) { | |
| 1291 | + // to compatible with EOL on OSs. | |
| 1292 | + $content = str_replace( "\r\n", "\r", $content ); | |
| 1293 | + $content = str_replace( "\n", "\r", $content ); | |
| 1294 | + $lines = explode( "\r", $content ); | |
| 1295 | + } | |
| 1296 | + } else { | |
| 1297 | + return false; | |
| 1298 | + } | |
| 1299 | + | |
| 1149 | 1300 | $import_data = array(); |
| 1150 | 1301 | $default_values = array( |
| 1151 | 1302 | 'client.name' => '', |
| 1152 | 1303 | 'client.email' => '', |
| @@ -1206,8 +1357,13 @@ | ||
| 1206 | 1357 | * Renders the Add New Client Fields form. |
| 1207 | 1358 | */ |
| 1208 | 1359 | public static function render_client_fields() { // phpcs:ignore -- NOSONAR - Current complexity is the only way to achieve desired results, pull request solutions appreciated. |
| 1209 | 1360 | |
| 1361 | + if ( ! \mainwp_current_user_can( 'dashboard', 'manage_clients' ) ) { | |
| 1362 | + \mainwp_do_not_have_permissions( esc_html__( 'manage clients', 'mainwp' ) ); | |
| 1363 | + return; | |
| 1364 | + } | |
| 1365 | + | |
| 1210 | 1366 | $fields = MainWP_DB_Client::instance()->get_client_fields(); |
| 1211 | 1367 | $has_no_fields = empty( $fields ); |
| 1212 | 1368 | |
| 1213 | 1369 | static::render_header( 'AddField' ); |
| @@ -1371,8 +1527,13 @@ | ||
| 1371 | 1527 | * Bulk client addition $_POST Handler. |
| 1372 | 1528 | */ |
| 1373 | 1529 | public static function add_client() { // phpcs:ignore -- NOSONAR -Current complexity is required to achieve desired results. Pull request solutions appreciated. |
| 1374 | 1530 | |
| 1531 | + if ( ! \mainwp_current_user_can( 'dashboard', 'manage_clients' ) ) { | |
| 1532 | + echo wp_json_encode( array( 'error' => esc_html__( 'Error - insufficient permissions!', 'mainwp' ) ) ); | |
| 1533 | + return; | |
| 1534 | + } | |
| 1535 | + | |
| 1375 | 1536 | $selected_sites = ( isset( $_POST['selected_sites'] ) && is_array( $_POST['selected_sites'] ) ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['selected_sites'] ) ) : array(); //phpcs:ignore WordPress.Security.NonceVerification.Missing |
| 1376 | 1537 | $client_fields = isset( $_POST['client_fields'] ) ? wp_unslash( $_POST['client_fields'] ) : array(); //phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 1377 | 1538 | |
| 1378 | 1539 | if ( ! is_array( $client_fields ) ) { |
| @@ -1384,13 +1545,60 @@ | ||
| 1384 | 1545 | if ( ! empty( $editing_client_id ) && ! wp_verify_nonce( $_POST['nonce_client_id'], 'editing-client-' . $editing_client_id ) ) { //phpcs:ignore -- NOSONAR - ok. |
| 1385 | 1546 | die( 'Invalid nonce!' ); |
| 1386 | 1547 | } |
| 1387 | 1548 | |
| 1549 | + $current_client = $editing_client_id ? MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $editing_client_id ) : null; | |
| 1550 | + $allowed_sites = MainWP_Abilities_Util::get_allowed_site_ids(); | |
| 1551 | + if ( ( $editing_client_id && ( ! $current_client || ! static::has_client_access( $editing_client_id ) ) ) || ( 'all' !== $allowed_sites && array_diff( array_map( 'intval', $selected_sites ), $allowed_sites ) ) ) { | |
| 1552 | + echo wp_json_encode( array( 'error' => esc_html__( 'Error - insufficient permissions!', 'mainwp' ) ) ); | |
| 1553 | + return; | |
| 1554 | + } | |
| 1555 | + | |
| 1556 | + // Foreign contacts and fields must not authorize a partial client save. | |
| 1557 | + $primary_id = isset( $client_fields['default_field']['primary_contact_id'] ) ? intval( $client_fields['default_field']['primary_contact_id'] ) : 0; | |
| 1558 | + $primary = $primary_id ? MainWP_DB_Client::instance()->get_wp_client_contact_by( 'contact_id', $primary_id ) : null; | |
| 1559 | + if ( $primary_id && ( ! $primary || ! $editing_client_id || (int) $primary->contact_client_id !== $editing_client_id ) ) { | |
| 1560 | + echo wp_json_encode( array( 'error' => esc_html__( 'Error - insufficient permissions!', 'mainwp' ) ) ); | |
| 1561 | + return; | |
| 1562 | + } | |
| 1563 | + foreach ( isset( $client_fields['custom_fields'] ) && is_array( $client_fields['custom_fields'] ) ? $client_fields['custom_fields'] : array() as $field_val ) { | |
| 1564 | + $field_id = is_array( $field_val ) ? array_key_first( $field_val ) : 0; | |
| 1565 | + $field = $field_id ? MainWP_DB_Client::instance()->get_client_fields_by( 'field_id', $field_id ) : null; | |
| 1566 | + if ( ! $field || ( (int) $field->client_id && ( ! $editing_client_id || (int) $field->client_id !== $editing_client_id ) ) ) { | |
| 1567 | + echo wp_json_encode( array( 'error' => esc_html__( 'Error - insufficient permissions!', 'mainwp' ) ) ); | |
| 1568 | + return; | |
| 1569 | + } | |
| 1570 | + } | |
| 1571 | + $contact_ids = isset( $client_fields['contacts_field']['contact_id'] ) && is_array( $client_fields['contacts_field']['contact_id'] ) ? $client_fields['contacts_field']['contact_id'] : array(); | |
| 1572 | + if ( isset( $client_fields['delele_contacts'] ) && is_array( $client_fields['delele_contacts'] ) ) { | |
| 1573 | + $contact_ids = array_merge( $contact_ids, $client_fields['delele_contacts'] ); | |
| 1574 | + } | |
| 1575 | + foreach ( $contact_ids as $contact_id ) { | |
| 1576 | + if ( ! $contact_id ) { | |
| 1577 | + continue; | |
| 1578 | + } | |
| 1579 | + $contact = MainWP_DB_Client::instance()->get_wp_client_contact_by( 'contact_id', intval( $contact_id ) ); | |
| 1580 | + if ( ! $contact || ! $editing_client_id || (int) $contact->contact_client_id !== $editing_client_id ) { | |
| 1581 | + echo wp_json_encode( array( 'error' => esc_html__( 'Error - insufficient permissions!', 'mainwp' ) ) ); | |
| 1582 | + return; | |
| 1583 | + } | |
| 1584 | + } | |
| 1585 | + if ( $current_client && isset( $client_fields['default_field']['client.suspended'] ) && (int) $client_fields['default_field']['client.suspended'] !== (int) $current_client->suspended && ! static::has_client_access( $editing_client_id, true ) ) { | |
| 1586 | + echo wp_json_encode( array( 'error' => esc_html__( 'Error - insufficient permissions!', 'mainwp' ) ) ); | |
| 1587 | + return; | |
| 1588 | + } | |
| 1589 | + | |
| 1388 | 1590 | if ( ! isset( $client_fields['default_field']['client.name'] ) || empty( $client_fields['default_field']['client.name'] ) ) { |
| 1389 | 1591 | echo wp_json_encode( array( 'error' => esc_html__( 'Client name are empty. Please try again.', 'mainwp' ) ) ); |
| 1390 | 1592 | return; |
| 1391 | 1593 | } |
| 1392 | 1594 | |
| 1595 | + $posted_note = isset( $client_fields['default_field']['client.note'] ) ? $client_fields['default_field']['client.note'] : null; | |
| 1596 | + if ( MainWP_Utility::client_note_exceeds_limit( $posted_note ) ) { | |
| 1597 | + echo wp_json_encode( array( 'error' => MainWP_Utility::client_note_length_error() ) ); | |
| 1598 | + return; | |
| 1599 | + } | |
| 1600 | + | |
| 1393 | 1601 | $add_new = true; |
| 1394 | 1602 | |
| 1395 | 1603 | $default_client_fields = MainWP_Client_Handler::get_default_client_fields(); |
| 1396 | 1604 | $client_to_add = array(); |
| @@ -1395,9 +1603,16 @@ | ||
| 1395 | 1603 | $default_client_fields = MainWP_Client_Handler::get_default_client_fields(); |
| 1396 | 1604 | $client_to_add = array(); |
| 1397 | 1605 | foreach ( $default_client_fields as $field_name => $item ) { |
| 1398 | 1606 | if ( ! empty( $item['db_field'] ) && isset( $client_fields['default_field'][ $field_name ] ) ) { |
| 1399 | - $client_to_add[ $item['db_field'] ] = sanitize_text_field( wp_unslash( $client_fields['default_field'][ $field_name ] ) ); | |
| 1607 | + if ( 'note' === $item['db_field'] ) { | |
| 1608 | + // client_fields is already unslashed. A non-string note is stored as empty. | |
| 1609 | + $field_value = $client_fields['default_field'][ $field_name ]; | |
| 1610 | + $client_to_add[ $item['db_field'] ] = is_string( $field_value ) ? MainWP_Utility::sanitize_client_note( $field_value ) : ''; | |
| 1611 | + } else { | |
| 1612 | + $field_value = wp_unslash( $client_fields['default_field'][ $field_name ] ); | |
| 1613 | + $client_to_add[ $item['db_field'] ] = sanitize_text_field( $field_value ); | |
| 1614 | + } | |
| 1400 | 1615 | } |
| 1401 | 1616 | } |
| 1402 | 1617 | |
| 1403 | 1618 | $client_to_add['primary_contact_id'] = isset( $client_fields['default_field']['primary_contact_id'] ) ? intval( $client_fields['default_field']['primary_contact_id'] ) : 0; |
| @@ -1407,10 +1622,9 @@ | ||
| 1407 | 1622 | $new_suspended = isset( $client_to_add['suspended'] ) ? (int) $client_to_add['suspended'] : 0; |
| 1408 | 1623 | $old_suspended = $new_suspended; |
| 1409 | 1624 | |
| 1410 | 1625 | if ( $client_id ) { |
| 1411 | - $current_client = MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client_id ); | |
| 1412 | - $old_suspended = $current_client->suspended; | |
| 1626 | + $old_suspended = $current_client->suspended; | |
| 1413 | 1627 | |
| 1414 | 1628 | $client_to_add['client_id'] = $client_id; // update client. |
| 1415 | 1629 | if ( isset( $client_to_add['created'] ) && ! empty( $client_to_add['created'] ) ) { |
| 1416 | 1630 | $client_to_add['created'] = strtotime( $client_to_add['created'] ); |
| @@ -1495,20 +1709,24 @@ | ||
| 1495 | 1709 | $client_image = sanitize_text_field( wp_unslash( $_POST['mainwp_add_edit_client_uploaded_icon_hidden'] ) ); // phpcs:ignore WordPress.Security.NonceVerification |
| 1496 | 1710 | } |
| 1497 | 1711 | |
| 1498 | 1712 | // compatible with quick setup. |
| 1499 | - if ( isset( $_FILES['mainwp_client_image_uploader'] ) && isset( $_FILES['mainwp_client_image_uploader']['error']['client_field'] ) && UPLOAD_ERR_OK === $_FILES['mainwp_client_image_uploader']['error']['client_field'] ) { // phpcs:ignore WordPress.Security.NonceVerification | |
| 1500 | - $output = MainWP_System_Utility::handle_upload_image( 'client-images', $_FILES['mainwp_client_image_uploader'], 'client_field' ); // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 1501 | - if ( is_array( $output ) && isset( $output['filename'] ) && ! empty( $output['filename'] ) ) { | |
| 1713 | + if ( isset( $_FILES['mainwp_client_image_uploader']['error']['client_field'] ) && UPLOAD_ERR_OK === $_FILES['mainwp_client_image_uploader']['error']['client_field'] ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 1714 | + $output = MainWP_System_Utility::handle_upload_image( 'client-images', $_FILES['mainwp_client_image_uploader'], 'client_field' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 1715 | + if ( is_array( $output ) && isset( $output['filename'] ) && MainWP_Utility::is_valid_client_image_name( $output['filename'] ) ) { | |
| 1502 | 1716 | $client_image = $output['filename']; |
| 1503 | 1717 | } |
| 1504 | 1718 | } |
| 1505 | 1719 | |
| 1506 | 1720 | $client_data = MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client_id ); |
| 1721 | + if ( $client_data && ! MainWP_Utility::is_valid_client_image_name( $client_image ) ) { | |
| 1722 | + $client_image = $client_data->image; | |
| 1723 | + } | |
| 1507 | 1724 | if ( $client_data && $client_data->image !== $client_image && $client_id ) { |
| 1725 | + // The hidden field is attacker-controlled; only the stored name identifies the old image. | |
| 1508 | 1726 | $old_file = $client_data->image; |
| 1509 | 1727 | if ( $old_file !== $client_image && ! empty( $old_file ) ) { |
| 1510 | - MainWP_Utility::instance()->delete_uploaded_icon_file( 'client-images', $old_file ); | |
| 1728 | + MainWP_Utility::delete_client_image( $old_file ); | |
| 1511 | 1729 | } |
| 1512 | 1730 | $update = array( |
| 1513 | 1731 | 'client_id' => $client_id, |
| 1514 | 1732 | 'image' => $client_image, |
| @@ -1582,11 +1800,12 @@ | ||
| 1582 | 1800 | if ( isset( $_POST['mainwp_add_edit_contact_uploaded_icon_hidden']['contacts_field'][ $indx ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification |
| 1583 | 1801 | $contact_image = sanitize_text_field( wp_unslash( $_POST['mainwp_add_edit_contact_uploaded_icon_hidden']['contacts_field'][ $indx ] ) ); // phpcs:ignore WordPress.Security.NonceVerification |
| 1584 | 1802 | } |
| 1585 | 1803 | if ( $contact_data && $contact_data->contact_image !== $contact_image && $contact_id ) { |
| 1804 | + // The hidden field is attacker-controlled; only the stored name identifies the old image. | |
| 1586 | 1805 | $old_file = $contact_data->contact_image; |
| 1587 | 1806 | if ( $old_file !== $contact_image && ! empty( $old_file ) ) { |
| 1588 | - MainWP_Utility::instance()->delete_uploaded_icon_file( 'client-images', $old_file ); | |
| 1807 | + MainWP_Utility::delete_client_image( $old_file ); | |
| 1589 | 1808 | } |
| 1590 | 1809 | $update = array( |
| 1591 | 1810 | 'contact_id' => $contact_id, |
| 1592 | 1811 | 'contact_image' => $contact_image, |
| @@ -2036,9 +2255,9 @@ | ||
| 2036 | 2255 | if ( 'client.note' === $field_name ) { |
| 2037 | 2256 | $el_id_fln_1 = $field_name; |
| 2038 | 2257 | ?> |
| 2039 | 2258 | <div class="editor"> |
| 2040 | - <textarea class="code settings-field-value-change-handler" cols="80" rows="10" id="client_fields[default_field][<?php echo esc_attr( $el_id_fln_1 ); ?>]" name="client_fields[default_field][<?php echo esc_attr( $field_name ); ?>]"><?php echo esc_html( $val ); ?></textarea> | |
| 2259 | + <textarea class="code settings-field-value-change-handler" cols="80" rows="10" id="client_fields[default_field][<?php echo esc_attr( $el_id_fln_1 ); ?>]" name="client_fields[default_field][<?php echo esc_attr( $field_name ); ?>]"><?php echo esc_textarea( MainWP_Utility::client_note_for_editor( (string) $val ) ); ?></textarea> | |
| 2041 | 2260 | </div> |
| 2042 | 2261 | <?php |
| 2043 | 2262 | } elseif ( $client_id && 'client.created' === $field_name ) { |
| 2044 | 2263 | $created = empty( $val ) ? time() : $val; |
| @@ -2163,8 +2382,11 @@ | ||
| 2163 | 2382 | </div> |
| 2164 | 2383 | <div class="ui section hidden divider after-add-contact-field"></div> |
| 2165 | 2384 | </div> |
| 2166 | 2385 | </div> |
| 2386 | + <?php if ( $edit_client ) : ?> | |
| 2387 | + <?php MainWP_Knowledge_UI::render_panel( 'client', $client_id, true ); ?> | |
| 2388 | + <?php endif; ?> | |
| 2167 | 2389 | </div> |
| 2168 | 2390 | <input type="hidden" name="client_fields[client_id]" value="<?php echo intval( $client_id ); ?>"> |
| 2169 | 2391 | <script type="text/javascript"> |
| 2170 | 2392 | jQuery( document ).ready( function () { |
| @@ -2533,8 +2755,21 @@ | ||
| 2533 | 2755 | $field_name = isset( $_POST['field_name'] ) ? sanitize_text_field( wp_unslash( $_POST['field_name'] ) ) : ''; |
| 2534 | 2756 | $field_name = trim( $field_name, '[]' ); |
| 2535 | 2757 | // phpcs:enable |
| 2536 | 2758 | |
| 2759 | + if ( $field_id ) { | |
| 2760 | + $owner = MainWP_DB_Client::instance()->get_client_fields_by( 'field_id', $field_id ); | |
| 2761 | + if ( ! $owner ) { | |
| 2762 | + $return['error'] = esc_html__( 'Error - insufficient permissions!', 'mainwp' ); | |
| 2763 | + static::exit_json( $return ); | |
| 2764 | + } | |
| 2765 | + $client_id = (int) $owner->client_id; | |
| 2766 | + } | |
| 2767 | + if ( ! \mainwp_current_user_can( 'dashboard', 'manage_clients' ) || ( $client_id && ( ! static::has_client_access( $client_id ) || ! MainWP_DB_Client::instance()->get_wp_client_by( 'client_id', $client_id ) ) ) ) { | |
| 2768 | + $return['error'] = esc_html__( 'Error - insufficient permissions!', 'mainwp' ); | |
| 2769 | + static::exit_json( $return ); | |
| 2770 | + } | |
| 2771 | + | |
| 2537 | 2772 | // update general or individual client field. |
| 2538 | 2773 | if ( $field_id ) { |
| 2539 | 2774 | $current = MainWP_DB_Client::instance()->get_client_fields_by( 'field_id', $field_id ); |
| 2540 | 2775 | if ( $current && $current->field_name === $field_name && $current->field_desc === $field_desc ) { |
| @@ -2590,23 +2825,52 @@ | ||
| 2590 | 2825 | * Save Client Note. |
| 2591 | 2826 | */ |
| 2592 | 2827 | public static function save_note() { |
| 2593 | 2828 | if ( isset( $_POST['clientid'] ) && ! empty( $_POST['clientid'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 2594 | - $note = isset( $_POST['note'] ) ? wp_unslash( $_POST['note'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 2595 | - $esc_note = MainWP_Utility::esc_content( $note ); | |
| 2596 | - $update = array( | |
| 2829 | + $note = isset( $_POST['note'] ) ? wp_unslash( $_POST['note'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 2830 | + if ( ! is_string( $note ) ) { | |
| 2831 | + $note = ''; | |
| 2832 | + } | |
| 2833 | + if ( MainWP_Utility::client_note_exceeds_limit( $note ) ) { | |
| 2834 | + self::exit_json( | |
| 2835 | + array( | |
| 2836 | + 'error' => MainWP_Utility::client_note_length_error(), | |
| 2837 | + ) | |
| 2838 | + ); | |
| 2839 | + } | |
| 2840 | + $stored = MainWP_Utility::sanitize_client_note( $note ); | |
| 2841 | + $update = array( | |
| 2597 | 2842 | 'client_id' => intval( $_POST['clientid'] ), // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 2598 | - 'note' => $esc_note, | |
| 2843 | + 'note' => $stored, | |
| 2599 | 2844 | ); |
| 2600 | 2845 | MainWP_DB_Client::instance()->update_client( $update ); |
| 2601 | - die( | |
| 2602 | - wp_json_encode( | |
| 2603 | - array( | |
| 2604 | - 'result' => 'SUCCESS', | |
| 2605 | - 'esc_note_content' => $esc_note, | |
| 2606 | - ) | |
| 2846 | + self::exit_json( | |
| 2847 | + array( | |
| 2848 | + 'result' => 'SUCCESS', | |
| 2849 | + // The editor shows < and >, not the stored < / > text. | |
| 2850 | + 'note' => MainWP_Utility::client_note_for_editor( $stored ), | |
| 2851 | + 'esc_note_content' => MainWP_Utility::client_note_html( $stored ), | |
| 2607 | 2852 | ) |
| 2608 | 2853 | ); |
| 2609 | 2854 | } |
| 2610 | - die( wp_json_encode( array( 'undefined_error' => true ) ) ); | |
| 2855 | + self::exit_json( array( 'undefined_error' => true ) ); | |
| 2856 | + } | |
| 2857 | + | |
| 2858 | + /** | |
| 2859 | + * Send a JSON payload and stop the request. | |
| 2860 | + * | |
| 2861 | + * During an AJAX request this uses wp_die() so the status stays HTTP 200 | |
| 2862 | + * and the body stays the JSON string. Otherwise it dies with that string. | |
| 2863 | + * | |
| 2864 | + * Shared by the notes modal and the clients notes AJAX handler. | |
| 2865 | + * | |
| 2866 | + * @param array $payload Response data. | |
| 2867 | + * @return void | |
| 2868 | + */ | |
| 2869 | + public static function exit_json( $payload ) { | |
| 2870 | + $json = wp_json_encode( $payload ); | |
| 2871 | + if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) { | |
| 2872 | + wp_die( $json ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON response body. | |
| 2873 | + } | |
| 2874 | + die( $json ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON response body. | |
| 2611 | 2875 | } |
| 2612 | 2876 | } |