| @@ -1,5 +1,8 @@ | ||
| 1 | 1 | <?php |
| 2 | +// phpcs:set WordPress.Security.ValidatedSanitizedInput customSanitizingFunctions[] ph_clean | |
| 3 | +// ph_clean() recursively sanitizes text; presence, shape and unslashing checks remain separate. | |
| 4 | + | |
| 2 | 5 | /** |
| 3 | 6 | * Add extra profile fields for users in admin |
| 4 | 7 | * |
| 5 | 8 | * @author PropertyHive |
| @@ -16,8 +19,9 @@ | ||
| 16 | 19 | |
| 17 | 20 | /** |
| 18 | 21 | * PH_Admin_Profile Class. |
| 19 | 22 | */ |
| 23 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin_Profile; preserving the existing PH_* class name is required for plugin and extension compatibility. | |
| 20 | 24 | class PH_Admin_Profile { |
| 21 | 25 | |
| 22 | 26 | /** |
| 23 | 27 | * Hook in tabs. |
| @@ -58,24 +62,45 @@ | ||
| 58 | 62 | } |
| 59 | 63 | } |
| 60 | 64 | wp_reset_postdata(); |
| 61 | 65 | |
| 62 | - $show_fields = apply_filters( | |
| 63 | - 'propertyhive_user_meta_fields', | |
| 64 | - array( | |
| 65 | - 'negotiator' => array( | |
| 66 | - 'title' => __( 'Additional Negotiator Information', 'propertyhive' ), | |
| 67 | - 'fields' => array( | |
| 68 | - 'office_id' => array( | |
| 69 | - 'label' => __( 'Office', 'propertyhive' ), | |
| 70 | - 'description' => '', | |
| 71 | - 'type' => 'select', | |
| 72 | - 'options' => array( '' => __( 'Select an office', 'property' ) ) + $offices, | |
| 73 | - ), | |
| 74 | - ), | |
| 75 | - ), | |
| 76 | - ) | |
| 66 | + $fields = array( | |
| 67 | + 'office_id' => array( | |
| 68 | + 'label' => __( 'Office', 'propertyhive' ), | |
| 69 | + 'description' => '', | |
| 70 | + 'type' => 'select', | |
| 71 | + 'options' => array( '' => __( 'Select an office', 'propertyhive' ) ) + $offices, | |
| 72 | + ), | |
| 73 | + 'telephone_number' => array( | |
| 74 | + 'label' => __( 'Telephone Number', 'propertyhive' ), | |
| 75 | + 'description' => '', | |
| 76 | + 'type' => 'text', | |
| 77 | + ), | |
| 78 | + 'photo_attachment_id' => array( | |
| 79 | + 'label' => __( 'Photo', 'propertyhive' ), | |
| 80 | + 'description' => '', | |
| 81 | + 'type' => 'image', | |
| 82 | + ), | |
| 77 | 83 | ); |
| 84 | + | |
| 85 | + $user = wp_get_current_user(); | |
| 86 | + $roles = (array)$user->roles; | |
| 87 | + | |
| 88 | + $fields['crm_only_mode'] = array( | |
| 89 | + 'label' => __( 'Property Hive-Only Mode', 'propertyhive' ), | |
| 90 | + 'description' => __( 'Enabling this option will remove all top level WordPress menu items leaving just Property Hive options making it easier to navigate and use as a CRM', 'propertyhive' ), | |
| 91 | + 'type' => in_array('administrator', $roles) ? 'checkbox' : 'hidden', | |
| 92 | + ); | |
| 93 | + | |
| 94 | + $show_fields = array( | |
| 95 | + 'negotiator' => array( | |
| 96 | + 'title' => __( 'Property Hive Negotiator Details', 'propertyhive' ), | |
| 97 | + 'fields' => $fields, | |
| 98 | + ), | |
| 99 | + ); | |
| 100 | + | |
| 101 | + $show_fields = apply_filters( 'propertyhive_user_meta_fields', $show_fields ); | |
| 102 | + | |
| 78 | 103 | return $show_fields; |
| 79 | 104 | } |
| 80 | 105 | |
| 81 | 106 | /** |
| @@ -99,11 +124,21 @@ | ||
| 99 | 124 | $show_fields = $this->get_user_meta_fields(); |
| 100 | 125 | |
| 101 | 126 | foreach ( $show_fields as $fieldset_key => $fieldset ) : |
| 102 | 127 | ?> |
| 103 | - <h2><?php echo $fieldset['title']; ?></h2> | |
| 128 | + <h2><?php echo esc_html($fieldset['title']); ?></h2> | |
| 104 | 129 | <table class="form-table" id="<?php echo esc_attr( 'fieldset-' . $fieldset_key ); ?>"> |
| 105 | 130 | <?php foreach ( $fieldset['fields'] as $key => $field ) : ?> |
| 131 | + <?php | |
| 132 | + if ( ! empty( $field['type'] ) && 'hidden' === $field['type'] ) | |
| 133 | + { | |
| 134 | + ?> | |
| 135 | + <input type="hidden" name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" value="<?php echo esc_attr( $this->get_user_meta( $user->ID, $key ) != '' ? $this->get_user_meta( $user->ID, $key ) : ( isset( $field['default'] ) ? $field['default'] : '' ) ); ?>" /> | |
| 136 | + <?php | |
| 137 | + } | |
| 138 | + else | |
| 139 | + { | |
| 140 | + ?> | |
| 106 | 141 | <tr> |
| 107 | 142 | <th> |
| 108 | 143 | <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ); ?></label> |
| 109 | 144 | </th> |
| @@ -108,9 +143,9 @@ | ||
| 108 | 143 | <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ); ?></label> |
| 109 | 144 | </th> |
| 110 | 145 | <td> |
| 111 | 146 | <?php if ( ! empty( $field['type'] ) && 'select' === $field['type'] ) : ?> |
| 112 | - <select name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" class="<?php echo esc_attr( $field['class'] ); ?>" style="width: 25em;"> | |
| 147 | + <select name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" class="<?php echo ( isset($field['class']) ? esc_attr($field['class']) : '' ); ?>" style="width: 25em;"> | |
| 113 | 148 | <?php |
| 114 | 149 | $selected = esc_attr( get_user_meta( $user->ID, $key, true ) ); |
| 115 | 150 | foreach ( $field['options'] as $option_key => $option_value ) : |
| 116 | 151 | ?> |
| @@ -117,17 +152,72 @@ | ||
| 117 | 152 | <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $selected, $option_key, true ); ?>><?php echo esc_html( $option_value ); ?></option> |
| 118 | 153 | <?php endforeach; ?> |
| 119 | 154 | </select> |
| 120 | 155 | <?php elseif ( ! empty( $field['type'] ) && 'checkbox' === $field['type'] ) : ?> |
| 121 | - <input type="checkbox" name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" value="1" class="<?php echo esc_attr( $field['class'] ); ?>" <?php checked( (int) get_user_meta( $user->ID, $key, true ), 1, true ); ?> /> | |
| 156 | + <input type="checkbox" name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" value="1" class="<?php echo isset($field['class']) ? esc_attr( $field['class'] ) : ''; ?>" <?php checked( (int) get_user_meta( $user->ID, $key, true ), 1, true ); ?> /> | |
| 157 | + <?php elseif ( ! empty( $field['type'] ) && 'color' === $field['type'] ) : ?> | |
| 158 | + <input type="color" name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" value="<?php echo esc_attr( $this->get_user_meta( $user->ID, $key ) != '' ? $this->get_user_meta( $user->ID, $key ) : ( isset( $field['default'] ) ? $field['default'] : '' ) ); ?>" class="<?php echo esc_attr( $field['class'] ); ?>" /> | |
| 122 | 159 | <?php elseif ( ! empty( $field['type'] ) && 'button' === $field['type'] ) : ?> |
| 123 | 160 | <button type="button" id="<?php echo esc_attr( $key ); ?>" class="button <?php echo esc_attr( $field['class'] ); ?>"><?php echo esc_html( $field['text'] ); ?></button> |
| 124 | - <?php else : ?> | |
| 161 | + <?php elseif ( ! empty( $field['type'] ) && 'image' === $field['type'] ) : ?> | |
| 162 | + <input type="hidden" name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" class="photo-attachment-id" value="<?php echo esc_attr( $this->get_user_meta( $user->ID, $key ) ); ?>" /> | |
| 163 | + <div class="photo-attachment-image"> | |
| 164 | + <?php if ( !empty($this->get_user_meta( $user->ID, $key )) ) { echo wp_get_attachment_image( $this->get_user_meta( $user->ID, $key ), 'thumbnail'); } ?> | |
| 165 | + </div> | |
| 166 | + <div class="wp-media-buttons"> | |
| 167 | + <button class="button propertyhive-add-media" id="propertyhive-add-media"><?php echo esc_html(__('Select', 'propertyhive')); ?></button> | |
| 168 | + </div> | |
| 169 | + <script> | |
| 170 | + | |
| 171 | + var file_frame; | |
| 172 | + | |
| 173 | + jQuery(document).ready(function() | |
| 174 | + { | |
| 175 | + jQuery('body').on('click', '.propertyhive-add-media', function( event ){ | |
| 176 | + | |
| 177 | + event.preventDefault(); | |
| 178 | + | |
| 179 | + // If the media frame already exists, reopen it. | |
| 180 | + if ( file_frame ) { | |
| 181 | + file_frame.open(); | |
| 182 | + return; | |
| 183 | + } | |
| 184 | + | |
| 185 | + // Create the media frame. | |
| 186 | + file_frame = wp.media.frames.file_frame = wp.media({ | |
| 187 | + multiple: false | |
| 188 | + }); | |
| 189 | + | |
| 190 | + // When an image is selected, run a callback. | |
| 191 | + file_frame.on( 'select', function() { | |
| 192 | + var selection = file_frame.state().get('selection'); | |
| 193 | + | |
| 194 | + selection.map( function( attachment ) { | |
| 195 | + | |
| 196 | + attachment = attachment.toJSON(); | |
| 197 | + | |
| 198 | + jQuery('#<?php echo esc_attr( $key ); ?>').val(attachment.id); | |
| 199 | + | |
| 200 | + var photo_html = '<img src="' + attachment.url + '" style="max-width:150px; max-height:150px;" alt=""></li>'; | |
| 201 | + | |
| 202 | + jQuery('.photo-attachment-image').html(photo_html); | |
| 203 | + }); | |
| 204 | + }); | |
| 205 | + | |
| 206 | + // Finally, open the modal | |
| 207 | + file_frame.open(); | |
| 208 | + }); | |
| 209 | + }); | |
| 210 | + </script> | |
| 211 | + <?php else: ?> | |
| 125 | 212 | <input type="text" name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" value="<?php echo esc_attr( $this->get_user_meta( $user->ID, $key ) ); ?>" class="<?php echo ( ! empty( $field['class'] ) ? esc_attr( $field['class'] ) : 'regular-text' ); ?>" /> |
| 126 | 213 | <?php endif; ?> |
| 127 | 214 | <p class="description"><?php echo wp_kses_post( $field['description'] ); ?></p> |
| 128 | 215 | </td> |
| 129 | 216 | </tr> |
| 217 | + <?php | |
| 218 | + } | |
| 219 | + ?> | |
| 130 | 220 | <?php endforeach; ?> |
| 131 | 221 | </table> |
| 132 | 222 | <?php |
| 133 | 223 | endforeach; |
| @@ -139,13 +229,18 @@ | ||
| 139 | 229 | * @param int $user_id User ID of the user being saved |
| 140 | 230 | */ |
| 141 | 231 | public function save_extra_user_meta_fields( $user_id ) { |
| 142 | 232 | |
| 143 | - if ( ! current_user_can( 'manage_propertyhive' ) ) { | |
| 233 | + if ( ! current_user_can( 'manage_propertyhive' ) || ! current_user_can( 'edit_user', $user_id ) ) { | |
| 144 | 234 | return; |
| 145 | 235 | } |
| 146 | 236 | |
| 147 | - $user_meta = get_userdata($user_id); | |
| 237 | + if ( ! isset( $_POST['_wpnonce'] ) || ! is_string( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) ) { | |
| 238 | + return; | |
| 239 | + } | |
| 240 | + | |
| 241 | + $user_meta = get_userdata($user_id); | |
| 242 | + if ( ! $user_meta ) { return; } | |
| 148 | 243 | $user_roles = $user_meta->roles; |
| 149 | 244 | |
| 150 | 245 | if ( ! in_array("administrator", $user_roles) && ! in_array("editor", $user_roles) ) { |
| 151 | 246 | return; |
| @@ -159,9 +254,13 @@ | ||
| 159 | 254 | |
| 160 | 255 | if ( isset( $field['type'] ) && 'checkbox' === $field['type'] ) { |
| 161 | 256 | update_user_meta( $user_id, $key, isset( $_POST[ $key ] ) ); |
| 162 | 257 | } elseif ( isset( $_POST[ $key ] ) ) { |
| 163 | - update_user_meta( $user_id, $key, ph_clean( $_POST[ $key ] ) ); | |
| 258 | + if ( isset( $field['type'] ) && in_array( $field['type'], array( 'text', 'hidden', 'color', 'image', 'select' ), true ) && ! is_scalar( $_POST[ $key ] ) ) { | |
| 259 | + continue; | |
| 260 | + } | |
| 261 | + // Metadata APIs expect slashed values; preserve literal backslashes after sanitizing. | |
| 262 | + update_user_meta( $user_id, $key, wp_slash( ph_clean( wp_unslash( $_POST[ $key ] ) ) ) ); | |
| 164 | 263 | } |
| 165 | 264 | } |
| 166 | 265 | } |
| 167 | 266 | } |