diff --git a/adminpages/member-edit.php b/adminpages/member-edit.php index 00f27ca5c..74e996cce 100644 --- a/adminpages/member-edit.php +++ b/adminpages/member-edit.php @@ -25,11 +25,19 @@ function pmpro_member_edit_get_panels() { // Add user fields panels. $user_id = PMPro_Member_Edit_Panel::get_user()->ID; if ( $user_id ) { - $profile_user_fields = pmpro_get_user_fields_for_profile( $user_id, true ); - if ( ! empty( $profile_user_fields ) ) { - foreach ( $profile_user_fields as $group_name => $user_fields ) { - $panels[] = new PMPro_Member_Edit_Panel_User_Fields( $group_name ); + foreach( PMPro_Field_Group::get_all() as $group ) { + $fields_to_display = $group->get_fields_to_display( + array( + 'scope' => 'profile', + 'user_id' => $user_id, + ) + ); + + if ( empty( $fields_to_display ) ) { + continue; } + + $panels[] = new PMPro_Member_Edit_Panel_User_Fields( $group->name ); } } diff --git a/adminpages/member-edit/pmpro-class-member-edit-panel-user-fields.php b/adminpages/member-edit/pmpro-class-member-edit-panel-user-fields.php index 30d0aff86..4155d4e8d 100644 --- a/adminpages/member-edit/pmpro-class-member-edit-panel-user-fields.php +++ b/adminpages/member-edit/pmpro-class-member-edit-panel-user-fields.php @@ -1,12 +1,19 @@ field_group = PMPro_Field_Group::get( $field_group_name ); $this->slug = 'user-fields-' . sanitize_title( $field_group_name ); - $this->title = $field_group_name; + $this->title = $this->field_group->label; $this->submit_text = current_user_can( 'edit_users' ) ? __( 'Update Member', 'paid-memberships-pro' ) : ''; } @@ -15,9 +22,8 @@ public function __construct( $field_group_name ) { */ protected function display_panel_contents() { // Print the group description. - $field_group = pmpro_get_field_group_by_name( $this->title ); - if ( ! empty( $field_group->description ) ) { - echo wp_kses_post( $field_group->description ); + if ( ! empty( $this->field_group->description ) ) { + echo wp_kses_post( $this->field_group->description ); } // Check if this is a checkout field location and show a message about custom code. @@ -31,21 +37,22 @@ protected function display_panel_contents() { 'before_submit_button', 'just_profile' ); - if ( in_array( $this->title, $checkout_field_locations ) ) { + if ( in_array( $this->field_group->name, $checkout_field_locations ) ) { esc_html_e( 'These user fields were added via custom code to hook into the following location:', 'paid-memberships-pro' ); - echo ' ' . esc_html( $this->title ) . ''; + echo ' ' . esc_html( $this->field_group->name ) . ''; } // Print the fields. - $profile_user_fields = pmpro_get_user_fields_for_profile( self::get_user()->ID, true ); ?> title] as $field ) { - if ( pmpro_is_field( $field ) ) { - $field->displayInProfile( self::get_user()->ID ); // Field will be readonly if cannot edit users. - } - } + $this->field_group->display( + array( + 'markup' => 'table', + 'show_group_label' => false, + 'user_id' => self::get_user()->ID, + ) + ); ?>
ID ) !== false ); // Function returns false on failed, null on saved. Will check edit_users cap in function. + $saved = $this->field_group->save_fields( + array( + 'user_id' => self::get_user()->ID, + ) + ); // Show success message. if ( $saved ) { diff --git a/adminpages/user-fields/field-settings.php b/adminpages/user-fields/field-settings.php new file mode 100644 index 000000000..85e37c1d6 --- /dev/null +++ b/adminpages/user-fields/field-settings.php @@ -0,0 +1,179 @@ +label; + $field_name = $field->name; + $field_type = $field->type; + $field_required = $field->required; + $field_readonly = $field->readonly; + $field_profile = $field->profile; + $field_wrapper_class = $field->wrapper_class; + $field_element_class = $field->element_class; + $field_hint = $field->hint; + $field_options = $field->options; + $field_allowed_file_types = $field->allowed_file_types; + $field_max_file_size = $field->max_file_size; + $field_default = $field->default; +} else { + // Default field values + $field_label = ''; + $field_name = ''; + $field_type = ''; + $field_required = false; + $field_readonly = false; + $field_profile = ''; + $field_wrapper_class = ''; + $field_element_class = ''; + $field_hint = ''; + $field_options = ''; + $field_allowed_file_types = ''; + $field_max_file_size = ''; + $field_default = ''; +} + +// Other vars +$levels = pmpro_sort_levels_by_order( pmpro_getAllLevels( true, true ) ); +?> +
+ + + +
diff --git a/adminpages/user-fields/group-settings.php b/adminpages/user-fields/group-settings.php new file mode 100644 index 000000000..e21e0651c --- /dev/null +++ b/adminpages/user-fields/group-settings.php @@ -0,0 +1,126 @@ +name; + $group_show_checkout = $group->checkout; + $group_show_profile = $group->profile; + $group_description = $group->description; + $group_levels = $group->levels; + $group_fields = $group->fields; +} else { + // Default group settings. + $group_name = ''; + $group_show_checkout = 'yes'; + $group_show_profile = 'yes'; + $group_description = ''; + $group_levels = array(); + $group_fields = array(); +} + +// Other vars +$levels = pmpro_sort_levels_by_order( pmpro_getAllLevels( true, true ) ); + +// Render field group HTML. +?> +
+
+
+ + + + + +
+

+ +

+ + +
+ +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+
3 ) { ?>style="height: 90px; overflow: auto;"> + +
+ +
+ +
+
+ +
+ +

+ + + +
+ + + + +
+ +
+ + +
+ +
+
diff --git a/adminpages/userfields.php b/adminpages/userfields.php index 7bb2f5505..ad6db4ef4 100644 --- a/adminpages/userfields.php +++ b/adminpages/userfields.php @@ -60,7 +60,9 @@ require_once( dirname(__FILE__) . '/admin_header.php' ); // Show warning if there are additional fields that are coded. - if ( pmpro_has_coded_user_fields() ) { + $num_fields_from_settings = array_sum( array_map( function ($group) { return count( $group->fields ); }, pmpro_get_user_fields_settings() ) ); // Fields from UI settings page. + $total_registered_fields = array_sum( array_map( function ($group) { return count( $group->get_fields() ); }, PMPro_Field_Group::get_all() ) ); // All registered fields. + if ( $num_fields_from_settings < $total_registered_fields ) { ?>

diff --git a/classes/class-pmpro-field-group.php b/classes/class-pmpro-field-group.php new file mode 100644 index 000000000..f59307dc9 --- /dev/null +++ b/classes/class-pmpro-field-group.php @@ -0,0 +1,555 @@ +name = $name; + $this->label = $label; + $this->description = $description; + } + + /** + * Magic getter for read-only properties. + * + * @param string $name The property name. + * @return mixed The property value. + */ + public function __get( $name ) { + if ( isset( $this->$name ) ) { + return $this->$name; + } + + return null; + } + + /** + * Add a field group. + * + * @since TBD + * + * @param string $name The name of the field group. + * @param string|null $label The label for the field group. If NULL, a cleaned version of the name will be used. + * @param string $description The description for the field group. + * + * @return PMPro_Field_Group The field group object. + */ + public static function add( $name, $label = NULL, $description = '' ) { + global $pmpro_field_groups; + + // If the field group already exists, update the label and description. + if ( ! empty( $pmpro_field_groups[ $name ] ) ) { // Looking at global to avoid infinite loop when a group doesn't exist. + $existing_field_group = self::get( $name ); + $existing_field_group->label = $label; + $existing_field_group->description = $description; + + return $existing_field_group; + } + + // If no label is provided, use the name. + if ( empty( $label ) ) { + if ( $name === 'checkout_boxes' ) { + apply_filters( 'pmpro_default_field_group_label', __( 'More Information','paid-memberships-pro' ) ); + } else { + $label = ucwords( str_replace( '_', ' ', $name ) ); + } + } + + // Create a new field group object. + $field_group = new PMPro_Field_Group( $name, $label, $description ); + + // Add the field group to the global array. + $pmpro_field_groups[ $name ] = $field_group; + + return $field_group; + } + + /** + * Get all added field groups. + * + * @since TBD + * + * @return array An array of PMPro_Field_Group objects. + */ + public static function get_all() { + global $pmpro_field_groups; + + if ( empty( $pmpro_field_groups ) ) { + $pmpro_field_groups = array(); + } + + return $pmpro_field_groups; + } + + /** + * Get an added field group by name. + * + * @since TBD + * + * @param string $name The name of the field group. + * @return PMPro_Field_Group The field group object. + */ + public static function get( $name ) { + // Get all field groups. + $field_groups = self::get_all(); + + // If we don't yet have the field group, create it. + if ( empty( $field_groups[ $name ] ) ) { + return self::add( $name ); + } + + // Return the field group. + return $field_groups[ $name ]; + } + + /** + * Get the field group for a field. + * + * @since TBD + * + * @param PMPro_Field $field The field object. + * @return PMPro_Field_Group|null The field group object, or NULL if the field is not in a group. + */ + public static function get_group_for_field( $field ) { + global $pmpro_field_groups; + + if ( empty( $pmpro_field_groups ) ) { + $pmpro_field_groups = array(); + } + + foreach ( $pmpro_field_groups as $field_group ) { + $group_fields = $field_group->get_fields(); + foreach( $group_fields as $group_field ) { + if ( $group_field->name === $field->name ) { + return $field_group; + } + } + } + + return null; + } + + /** + * Get a field by name. + * + * @since TBD + * + * @param string $name The name of the field. + * @return PMPro_Field|null The field object, or NULL if the field is not in a group. + */ + public static function get_field( $name ) { + global $pmpro_user_fields; + + if ( empty( $pmpro_user_fields ) ) { + $pmpro_user_fields = array(); + } + + foreach ( $pmpro_user_fields as $group_name => $fields ) { + foreach ( $fields as $field ) { + if ( $field->name === $name ) { + return $field; + } + } + } + + return null; + } + + /** + * Add a field to this field group. + * + * @since TBD + * + * @param PMPro_Field $field The field object to add. + * @return bool True if the field was added, otherwise false. + */ + public function add_field( $field ) { + global $pmpro_user_fields; + if ( empty( $pmpro_user_fields ) ) { + $pmpro_user_fields = array(); + } + + /** + * Filter the field to add. + * + * @since 2.9.3 + * + * @param PMProField $field The field being added. + * @param string $group_name The name of the group to add the field to. + */ + $field = apply_filters( 'pmpro_add_user_field', $field, $this->name ); + + // Make sure that we have a valid field. + if ( empty( $field ) || ! pmpro_is_field( $field ) ) { + return false; + } + + // Make sure the group is in the global array of fields. + if ( empty( $pmpro_user_fields[ $this->name ] ) ) { + $pmpro_user_fields[ $this->name ] = array(); + } + + // Add the field to the group. + $pmpro_user_fields[ $this->name ][] = $field; + + return true; + } + + /** + * Get all fields in this field group. + * + * @since TBD + * + * @return array An array of PMPro_Field objects. + */ + public function get_fields() { + global $pmpro_user_fields; + if ( empty( $pmpro_user_fields ) ) { + $pmpro_user_fields = array(); + } + + if ( empty( $pmpro_user_fields[ $this->name ] ) ) { + $pmpro_user_fields[ $this->name ] = array(); + } + + return $pmpro_user_fields[ $this->name ]; + } + + /** + * Get all fields to display in a specific context. + * + * @since TBD + * + * @param array $args The arguments for getting the fields. + */ + public function get_fields_to_display( $args = array() ) { + $default_args = array( + 'scope' => 'profile', // The scope of the fields to show. Can be 'profile' or 'checkout'. + 'user_id' => NULL, // The user ID to show the users for. If null, we are showing fields for the current user. + ); + $args = wp_parse_args( $args, $default_args ); + + // Get all fields in this group. + $fields = $this->get_fields(); + + // Get the user ID. + $user_id = empty( $args['user_id'] ) ? get_current_user_id() : $args['user_id']; + + // Get a list of the fields that should be displayed. + $fields_to_display = array(); + foreach ( $fields as $field ) { + // Validate the field for scope. + if ( 'checkout' === $args['scope'] ) { + // At checkout. + // Check if this field should only be shown in the profile. + if ( in_array( $field->profile, array( 'only', 'only_admin' ), true ) ) { + continue; + } + + // Check if this field is for the level being purchased. + // Get the checkout level. + $checkout_level = pmpro_getLevelAtCheckout(); + $chekcout_level_id = ! empty( $checkout_level->id ) ? (int)$checkout_level->id : NULL; + if ( empty( $chekcout_level_id ) ) { + continue; + } + if ( ! empty( $field->levels ) && ! in_array( (int) $chekcout_level_id, $field->levels, true ) ) { + continue; + } + } else { + // In profile. + // Check if this field should ever be shown in the profile. + if ( empty( $field->profile ) ) { + continue; + } + + // Check if this field should only be shown to admins. + if ( ( ! current_user_can( 'manage_options' ) && ! current_user_can( 'pmpro_membership_manager' ) ) && in_array( $field->profile, array( 'admins', 'admin', 'only_admin' ), true ) ) { + continue; + } + + // Check if the user has a level required for this field. + if ( ! empty( $field->levels ) && ! pmpro_hasMembershipLevel( $field->levels, $user_id ) ) { + continue; + } + } + + // Add the field to the list of fields to display. + $fields_to_display[] = $field; + } + + return $fields_to_display; + } + + /** + * Display the field group. + * + * @since TBD + * + * @param array $args The arguments for displaying the fields. + */ + public function display( $args = array() ) { + $default_args = array( + 'markup' => 'card', // The markup to use for the field group. Can be 'card', 'div' or 'table'. + 'scope' => 'profile', // The scope of the fields to show. Can be 'profile' or 'checkout'. + 'show_group_label' => true, // Whether or not to show the field group. + 'prefill_from_request' => false, // Whether or not to prefill the field values from the $_REQUEST array. + 'show_required' => false, // Whether or not to show required fields. + 'user_id' => NULL, // The user ID to show the users for. If null, we are showing fields for the current user. + ); + $args = wp_parse_args( $args, $default_args ); + + // Get the user ID. + $user_id = empty( $args['user_id'] ) ? get_current_user_id() : $args['user_id']; + + // Get the fields to display. + $fields_to_display = $this->get_fields_to_display( $args ); + + // If we don't have any fields to display, don't display the group. + if ( empty( $fields_to_display ) ) { + return; + } + + // Display the field group. + if ( empty( $args['show_group_label'] ) ) { + $group_header = ''; + $group_footer = ''; + } elseif ( $args['markup'] === 'card' ) { + // Get the "header" for the field group. + ob_start(); + ?> +
+
+
+ label ) ) { ?> + +

label ); ?>

+
+ +
+ description ) ) { ?> +
description ); ?>
+ + +
+
+
+
+ +
+
+ label ) ) { ?> + +

label ); ?>

+
+ +
+ description ) ) { ?> +
description ); ?>
+ + +
+
+ +

label ); ?>

+ description ) ) { + ?> +

description ); ?>

+ + + +
+ get_value_from_request() ) { + $value = $field->get_value_from_request(); + } elseif ( ! empty( $user_id ) && metadata_exists( 'user', $user_id, $field->meta_key ) ) { + $value = get_user_meta( $user_id, $field->meta_key, true ); + } elseif ( ! empty( $user_id ) ) { + $userdata = get_userdata( $user_id ); + if ( ! empty( $userdata->{$field->name} ) ) { + $value = $userdata->{$field->name}; + } elseif(isset($field->value)) { + $value = $field->value; + } + } elseif(isset($field->value)) { + $value = $field->value; + } + + if ( $args['markup'] === 'div' || $args['markup'] === 'card' ) { + // Fix divclass. + if ( ! empty( $field->divclass ) ) { + $field->divclass .= " "; + } + + // Add a class to the field based on the type. + $field->divclass .= "pmpro_form_field pmpro_form_field-" . $field->type; + $field->class .= " pmpro_form_input-" . $field->type; + + // Add a class to the field based on the id. + $field->divclass .= " pmpro_form_field-" . $field->id; + $field->class .= " pmpro_form_input-" . $field->id; + + // Add the required class to field. + if ( ! empty( $args['show_required'] ) && ! empty( $field->required ) ) { + $field->divclass .= " pmpro_form_field-required"; + $field->class .= " pmpro_form_input-required"; + } + + // Add the class to not show a field is required if set. + if ( ! empty( $args['show_required'] ) && ( empty( $field->showrequired ) || is_string( $field->showrequired ) ) ) { + $field->divclass .= " pmpro_form_field-hide-required"; + } + + // Run the class through the filter. + $field->divclass = pmpro_get_element_class( $field->divclass, $field->id ); + $field->class = pmpro_get_element_class( $field->class, $field->id ); + + ?> +
divclass ) ) { echo 'class="' . esc_attr( $field->divclass ) . '"'; } ?>> + showmainlabel)) { ?> + + display( $value ); ?> + + display( $value ); ?> + + + hint)) { ?> +

hint );?>

+ +
+ + + + showmainlabel ) ) { ?> + + + + + display($value); + else + echo "
" . wp_kses_post( $field->displayValue($value) ) . "
"; + ?> + hint)) { ?> +

hint );?>

+ + + + 'profile', // The scope of the fields to save. Can be 'profile' or 'checkout'. + 'user_id' => NULL, // The user ID to save the users for. If null, we are saving fields for the current user. + ); + $args = wp_parse_args( $args, $default_args ); + + // Get the user ID if needed. + $user_id = empty( $args['user_id'] ) ? get_current_user_id() : $args['user_id']; + + // Make sure the current user can edit this user. + if ( 'scope' == 'profile' && ! current_user_can( 'edit_user', $user_id ) ) { + return false; + } + + // Get the fields to display. + $fields_to_display = $this->get_fields_to_display( $args ); + + // Save the fields. + foreach ( $fields_to_display as $field ) { + $field->save_field_for_user( $user_id ); + } + + return true; + } +} \ No newline at end of file diff --git a/classes/class-pmpro-field.php b/classes/class-pmpro-field.php index b23fa1009..b6f4575e4 100755 --- a/classes/class-pmpro-field.php +++ b/classes/class-pmpro-field.php @@ -10,7 +10,7 @@ class PMPro_Field { * * @var string */ - public $name = ''; + private $name = ''; /** * The type of field that this is. @@ -19,7 +19,7 @@ class PMPro_Field { * * @var string */ - public $type = ''; + private $type = ''; /** * The meta key for this field. @@ -30,7 +30,7 @@ class PMPro_Field { * * @var string */ - public $meta_key = ''; + private $meta_key = ''; /** * The label of the field. @@ -41,7 +41,7 @@ class PMPro_Field { * * @var string */ - public $label = ''; + private $label = ''; /** * Whether the label should be shown. @@ -50,7 +50,7 @@ class PMPro_Field { * * @var bool */ - public $showmainlabel = true; + private $showmainlabel = true; /** * A hint to be displayed with the field. @@ -59,7 +59,7 @@ class PMPro_Field { * * @var string */ - public $hint = ''; + private $hint = ''; /** * The membership levels that this field should be displayed for. @@ -68,7 +68,7 @@ class PMPro_Field { * * @var array */ - public $levels = array(); + private $levels = array(); /** * Whether the field is required. @@ -77,7 +77,7 @@ class PMPro_Field { * * @var bool */ - public $required = false; + private $required = false; /** * Whether the field should be shown as required if $required is set to true. @@ -86,7 +86,7 @@ class PMPro_Field { * * @var bool */ - public $showrequired = true; + private $showrequired = true; /** * Where this field should be shown. @@ -97,7 +97,7 @@ class PMPro_Field { * * @var mixed */ - public $profile = true; + private $profile = true; /** * Whether the field is readonly. @@ -106,7 +106,7 @@ class PMPro_Field { * * @var bool */ - public $readonly = false; + private $readonly = false; /** * Array to define conditions when a field should be shown or hidden. @@ -115,7 +115,7 @@ class PMPro_Field { * * @var array */ - public $depends = array(); + private $depends = array(); /** * Flag to determine if depends conditions should be ANDed or ORed together. @@ -124,7 +124,7 @@ class PMPro_Field { * * @var bool */ - public $depends_or = false; + private $depends_or = false; /** * Whether the field value should be sanitized before saving. @@ -133,7 +133,7 @@ class PMPro_Field { * * @var bool */ - public $sanitize = true; + private $sanitize = true; /** * The ID to show for the field. @@ -142,7 +142,7 @@ class PMPro_Field { * * @var string */ - public $id = ''; + private $id = ''; /** * Class for the input field. @@ -151,7 +151,7 @@ class PMPro_Field { * * @var string */ - public $class = ''; + private $class = ''; /** * Class for the div wrapper for the input field. @@ -160,7 +160,7 @@ class PMPro_Field { * * @var string */ - public $divclass = ''; + private $divclass = ''; /** * Whether this field should be included in a members list CSV export. @@ -169,7 +169,7 @@ class PMPro_Field { * * @var bool */ - public $memberslistcsv = false; + private $memberslistcsv = false; /** * The save function that should be used for this field. @@ -180,7 +180,7 @@ class PMPro_Field { * * @var callable */ - public $save_function = null; + private $save_function = null; /** * Whether this field should be shown when adding a member using @@ -190,7 +190,7 @@ class PMPro_Field { * * @var bool */ - public $addmember = false; + private $addmember = false; /** * The size attribute when using a text input field type. @@ -199,7 +199,7 @@ class PMPro_Field { * * @var int */ - public $size = 30; + private $size = 30; /** * The number of rows to show when using a textarea field type. @@ -208,7 +208,7 @@ class PMPro_Field { * * @var int */ - public $rows = 5; + private $rows = 5; /** * The number of columns to show when using a textarea field type. @@ -217,7 +217,7 @@ class PMPro_Field { * * @var int */ - public $cols = 80; + private $cols = 80; /** * The options for a select, select2, multiselect, checkbox_grouped, or radio field type. @@ -226,7 +226,7 @@ class PMPro_Field { * * @var array */ - public $options = array(); + private $options = array(); /** * Whether multiple options should be selectable when using a select, select2, or multiselect field type. @@ -235,7 +235,7 @@ class PMPro_Field { * * @var bool */ - public $multiple = false; + private $multiple = false; /** * The text to show next to a checkbox when using a checkbox field type. @@ -244,7 +244,7 @@ class PMPro_Field { * * @var string */ - public $text = ''; + private $text = ''; /** * The HTML to show for an HTML field type. @@ -253,7 +253,7 @@ class PMPro_Field { * * @var string */ - public $html = ''; + private $html = ''; /** * The default value for a field. @@ -262,7 +262,7 @@ class PMPro_Field { * * @var string */ - public $default = ''; + private $default = ''; /** * File upload types. @@ -272,7 +272,7 @@ class PMPro_Field { * @var string * */ - public $allowed_file_types = ''; + private $allowed_file_types = ''; /** * File upload limit @@ -281,7 +281,7 @@ class PMPro_Field { * * @var int */ - public $max_file_size = ''; + private $max_file_size = ''; function __construct($name = NULL, $type = NULL, $attr = NULL) { if ( ! empty( $name ) ) @@ -290,6 +290,137 @@ function __construct($name = NULL, $type = NULL, $attr = NULL) { return true; } + /** + * Magic getter to allow reading private class properties. + * + * @param string $name The property name. + * @return mixed The property value. + */ + function __get( $name ) { + if ( isset( $this->$name ) ) { + if ( ! $this->is_valid_property( $name ) ) { + _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'The property %s is not valid for the field type %s.', 'paid-memberships-pro' ), $name, $this->type ), 'TBD' ); + } + return $this->$name; + } else { + _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'The property %s does not exist.', 'paid-memberships-pro' ), $name ), 'TBD' ); + } + + return null; + } + + /** + * Magic setter to allow setting private class properties and + * throwing warnings when we want to phase out a property. + * + * @param string $name The property name. + * @param mixed $value The property value. + */ + function __set( $name, $value ) { + if ( 'type' === $name ) { + _doing_it_wrong( __FUNCTION__, esc_html__( 'PMPro_Field properties should not be modified directly and may break in a future version. Instead, create a new PMPro_Field object.', 'paid-memberships-pro' ), 'TBD' ); + } + + $this->$name = $value; + } + + /** + * Magic isset to check if a private class property is set. + * + * @param string $name The property name. + * @return bool Whether the property is set. + */ + function __isset( $name ) { + return isset( $this->$name ); + } + + /** + * Magic __call to allow calling private class methods and throwing warnings + * when we want to phase out a method. + */ + function __call( $name, $arguments ) { + switch( $name ) { + case 'set': + _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'The method %s of PMPro_Field has become private and will not be available in a future version. Instead, use the $args property of the constructor when creating a new PMPro_Field object.', 'paid-memberships-pro' ), $name ), 'TBD' ); + break; + case 'saveUsersTable': + case 'saveTermRelationshipsTable': + case 'saveFile': + _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'The method %s of PMPro_Field has become private and will not be available in a future version. Instead, use the save_field_for_user method of the PMPro_Field object.', 'paid-memberships-pro' ), $name ), 'TBD' ); + break; + case 'getHTML': + case 'getDependenciesJS': + _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'The method %s of PMPro_Field has become private and will not be available in a future version. Instead, use the display() method of the PMPro_Field object.', 'paid-memberships-pro' ), $name ), 'TBD' ); + break; + default: + _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'The method %s of PMPro_Field has become private and will not be available in a future version.', 'paid-memberships-pro' ), $name ), 'TBD' ); + break; + } + return call_user_func( array( $this, $name ), $arguments ); + } + + /** + * Check if a property should be present for the current field type. + * + * @since TBD + * + * @param string $property The property to check. + * return bool Whether the property is valid for the field type. + */ + private function is_valid_property( $property ) { + switch ( $property ) { + case 'name': + case 'type': + case 'meta_key': + case 'label': + case 'showmainlabel': + case 'hint': + case 'levels': + case 'required': + case 'showrequired': + case 'profile': + case 'readonly': + case 'depends': + case 'depends_or': + case 'sanitize': + case 'id': + case 'class': + case 'divclass': + case 'memberslistcsv': + case 'save_function': + case 'addmember': + case 'default': + return true; + break; + case 'size': + return in_array( $this->type, array( 'text', 'number' ) ); + break; + case 'rows': + case 'cols': + return 'textarea' === $this->type; + break; + case 'options': + return in_array( $this->type, array( 'select', 'multiselect', 'select2', 'radio', 'checkbox_grouped' ) ); + break; + case 'multiple': + return in_array( $this->type, array( 'select', 'select2', 'multiselect' ) ); + break; + case 'text': + return 'checkbox' === $this->type; + break; + case 'html': + return 'html' === $this->type; + break; + case 'allowed_file_types': + case 'max_file_size': + return 'file' === $this->type; + break; + default: + return false; + break; + } + } + /* setup field based on passed values attr is array of one or more of the following: @@ -300,7 +431,7 @@ function __construct($name = NULL, $type = NULL, $attr = NULL) { - just_profile = bool (not required. true means only show field in profile) - class = string (class to add to html element) */ - function set($name, $type, $attr = array()) + private function set($name, $type, $attr = array()) { $this->name = $name; $this->type = $type; @@ -468,8 +599,86 @@ function set($name, $type, $attr = array()) return true; } + /** + * Get the field value from $_REQEUST or $_SESSION. + * The value will be sanitized if the field has the sanitize property set to true. + * + * @since TBD + * + * @return mixed The value of the field or null if not found. + */ + function get_value_from_request() { + if ( isset( $_REQUEST[ $this->name ] ) ) { + $value = $_REQUEST[$this->name]; + } elseif ( isset( $_REQUEST[ $this->name . '_checkbox' ] ) && $this->type == 'checkbox' ) { + // Empty checkbox. + $value = 0; + } elseif ( ! empty( $_REQUEST[ $this->name . '_checkbox' ] ) && in_array( $this->type, array( 'checkbox_grouped', 'select2' ) ) ) { + // Empty group checkboxes or select2. + $value = array(); + } elseif ( isset( $_FILES[$this->name] ) && $this->type == 'file' ) { + // File field. + $value = $_FILES[$this->name]['name']; + } elseif ( isset( $_SESSION[$this->name] ) ) { + // Value stored in session. + if ( is_array( $_SESSION[$this->name] ) && isset( $_SESSION[$this->name]['name'] ) ) { + // File field in session. + $_FILES[$this->name] = $_SESSION[$this->name]; + $value = $_SESSION[$this->name]['name']; + } else { + // Other field in session. + $value = $_SESSION[$this->name]; + } + + // Clean up session. + unset($_SESSION[$this->name]); + } else { + // No value found. + return null; + } + + // Sanitize the value if needed. + if ( ! empty( $field->sanitize ) ) { + if ( $this->type == 'textarea' ) { + $value = sanitize_textarea_field( $value ); + } elseif ( is_array( $value ) ) { + $value = array_map( 'sanitize_text_field', $value ); + } else { + $value = sanitize_text_field( $value ); + } + } + + return $value; + } + + /** + * Save the field for a user. + * + * @since TBD + * + * @param int $user_id The user ID to save the field for. + */ + function save_field_for_user( int $user_id ) { + // Get the value of the field. + $value = $this->get_value_from_request(); + + // If field was not submitted, bail. + if ( null === $value ) { + return; + } + + // Check if we have a save function. + if ( ! empty( $this->save_function ) ) { + // Call the save function. + call_user_func( $this->save_function, $user_id, $this->name, $value, $this ); + } else { + // Save the value to usermeta. + update_user_meta($user_id, $this->meta_key, $value); + } + } + // Save function for users table field. - function saveUsersTable( $user_id, $name, $value ) { + private function saveUsersTable( $user_id, $name, $value ) { // Special sanitization needed for certain user fields. if ( $name === 'user_url' ) { $value = esc_url_raw( $value ); @@ -483,7 +692,7 @@ function saveUsersTable( $user_id, $name, $value ) { } // Save function for user taxonomy field. - function saveTermRelationshipsTable( $user_id, $name, $value ) { + private function saveTermRelationshipsTable( $user_id, $name, $value ) { // Get the taxonomy to save for. if ( isset( $this->taxonomy ) ) { $taxonomy = $this->taxonomy; @@ -512,7 +721,7 @@ function saveTermRelationshipsTable( $user_id, $name, $value ) { } //save function for files - function saveFile($user_id, $name, $value) + private function saveFile($user_id, $name, $value) { //setup some vars $user = get_userdata($user_id); @@ -619,7 +828,7 @@ function saveFile($user_id, $name, $value) if ( $filetype && 0 === strpos( $filetype['type'], 'image/' ) ) { $preview_file = wp_get_image_editor( $file_path ); if ( ! is_wp_error( $preview_file ) ) { - $preview_file->resize( 200, NULL, false ); + $preview_file->resize( 400, 400, false ); $preview_file->generate_filename( 'pmpro_file_preview' ); $preview_file = $preview_file->save(); } @@ -649,15 +858,17 @@ function saveFile($user_id, $name, $value) update_user_meta($user_id, $meta_key, $file_meta_value_array ); } - //echo the HTML for the field - function display($value = NULL) - { - echo $this->getHTML($value); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + /** + * Display the field. + */ + function display( $value = NULL ) { + echo $this->getHTML( $value ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $this->getDependenciesJS(); return; } //get HTML for the field - function getHTML($value = "") + private function getHTML($value = "") { // Vars to store HTML to be added to the beginning or end. $r_beginning = ''; @@ -683,7 +894,7 @@ function getHTML($value = "") if($this->type == "text") { - $r = 'id ) . '" name="' . esc_attr( $this->name ) . '" value="' . ( is_string( $value ) ? esc_attr(wp_unslash($value) ) : '' ) . '" '; if(!empty($this->size)) $r .= 'size="' . esc_attr( $this->size ) . '" '; if(!empty($this->class)) @@ -909,7 +1120,7 @@ function getHTML($value = "") $r .= 'readonly="readonly" '; if(!empty($this->html_attributes)) $r .= $this->getHTMLAttributes(); - $r .= '>' . esc_textarea(wp_unslash($value)) . ''; + $r .= '>' . ( ( is_string( $value ) ) ? esc_textarea(wp_unslash($value) ) : '' ) . ''; } elseif($this->type == "hidden") { @@ -934,35 +1145,32 @@ function getHTML($value = "") { $r = ''; - //old value - if(is_user_logged_in()) - { - global $current_user; - $old_value = get_user_meta($current_user->ID, $this->meta_key, true); - if(!empty($old_value)) - $r .= ''; - } - // Show the existing file with a preview and allow user to delete or replace. - if ( ! empty( $value ) ) { + if ( ! empty( $value ) && ( is_array( $value ) || ! empty( $this->file ) ) ) { + if ( is_array( $value ) ) { + $file = $value; + } elseif ( ! empty( $this->file ) ) { + // Legacy support for $this->file. + $file = $this->file; + } // Show a preview of existing file if image type. - if ( ( ! empty( $this->preview ) ) && ! empty( $this->file['previewurl'] ) ) { - $filetype = wp_check_filetype( basename( $this->file['previewurl'] ), null ); + if ( ( ! empty( $this->preview ) ) && ! empty( $file['previewurl'] ) ) { + $filetype = wp_check_filetype( basename( $file['previewurl'] ), null ); if ( $filetype && 0 === strpos( $filetype['type'], 'image/' ) ) { - $r_beginning .= '
' . esc_attr( basename($value) ) . '
'; + $r_beginning .= '
' . esc_attr( basename($file['filename']) ) . '
'; } } - if( ! empty( $this->file['fullurl'] ) ) { - $r_beginning .= '
' . sprintf(__('Current File: %s', 'paid-memberships-pro' ), '' . esc_html( basename($value) ) . '' ) . '
'; - } else { + if( ! empty( $file['fullurl'] ) ) { + $r_beginning .= '
' . sprintf(__('Current File: %s', 'paid-memberships-pro' ), '' . esc_html( basename($file['filename']) ) . '' ) . '
'; + } elseif( is_string( $value ) ) { $r_beginning .= sprintf(__('Current File: %s', 'paid-memberships-pro' ), basename($value) ); } $r_beginning .= '
'; // Allow user to delete the uploaded file if we know the full location. - if ( ( ! empty( $this->allow_delete ) ) && ! empty( $this->file['fullurl'] ) ) { + if ( ( ! empty( $this->allow_delete ) ) && ! empty( $file['fullurl'] ) ) { // Check whether the current user can delete the uploaded file based on the field attribute 'allow_delete'. if ( $this->allow_delete === true || ( $this->allow_delete === 'admins' || $this->allow_delete === 'only_admin' && current_user_can( 'manage_options' ) ) @@ -977,48 +1185,52 @@ function getHTML($value = "") $r_beginning .= ''; } $r_beginning .= '
'; - } + //include script to change enctype of the form and allow deletion + $r .= ' + + '; + } - }); - - '; - - $r .= '
'; + $r .= ' + + '; + $r .= '
'; $r .= 'allowed_file_types ) ) { @@ -1164,7 +1376,7 @@ function getHTMLAttributes() { return $html; } - function getDependenciesJS() + private function getDependenciesJS() { global $pmpro_user_fields; //dependencies @@ -1172,27 +1384,44 @@ function getDependenciesJS() { //build the checks $checks_escaped = array(); + $binds = array(); foreach($this->depends as $check) { if(!empty($check['id'])) { // If checking checkbox_grouped, need to update the $check['id'] with index of option. $field_id = $check['id']; - $depends_checkout_box = PMPro_Field::get_checkout_box_name_for_field( $field_id ); - if ( empty( $depends_checkout_box ) ) { + $depends_field = PMPro_Field_Group::get_field( $field_id ); + if ( empty( $depends_field ) ) { continue; } - foreach ( $pmpro_user_fields[ $depends_checkout_box ] as $field ) { - if ( $field->type === 'checkbox_grouped' && $field->name === $field_id && ! empty( $field->options ) ) { - $field_id = $field_id . '_' . intval( array_search( $check['value'], array_keys( $field->options ) )+1 ); - } + + $depends_field_group = PMPro_Field_Group::get_group_for_field( $depends_field ); + if ( empty( $depends_field_group ) ) { + continue; } - $checks_escaped[] = "((jQuery('#" . esc_html( $field_id ) ."')".".is(':checkbox')) " - ."? jQuery('#" . esc_html( $field_id ) . ":checked').length > 0" - .":(jQuery('#" . esc_html( $field_id ) . "').val() == " . json_encode($check['value']) . " || jQuery.inArray( jQuery('#" . esc_html( $field_id ) . "').val(), " . json_encode($check['value']) . ") > -1)) ||"."(jQuery(\"input:radio[name='". esc_html( $check['id'] ) ."']:checked\").val() == ".json_encode($check['value'])." || jQuery.inArray(".json_encode($check['value']).", jQuery(\"input:radio[name='". esc_html( $field_id ) ."']:checked\").val()) > -1)"; - - $binds[] = "#" . esc_html( $field_id ) .",input:radio[name=". esc_html( $field_id ) ."]"; + // Let's simplify. + switch ( $depends_field->type ) { + case 'checkbox_grouped': + // Find an input with the name of the field and the value of the option, then check if it is selected. + // Don't use the ID. + $checks_escaped[] = "jQuery('input[name=\"" . esc_js( $field_id ) . "[]\"][value=" . esc_js( $check['value'] ) . "]:checked').length > 0"; + $binds[] = "input[name=\"" . esc_js( $field_id ) . "[]\"]"; + break; + case 'checkbox': + $checks_escaped[] = "jQuery('#" . esc_html( $field_id ) . "').is(':checked') == " . ( empty( $check['value'] ) ? 'false' : 'true' ); + $binds[] = "#" . esc_html( $field_id ); + break; + case 'radio': + $checks_escaped[] = "jQuery('#" . esc_html( $field_id ) . " input[type=radio][value=" . esc_js( $check['value'] ) . "]:checked').length > 0"; + $binds[] = "#" . esc_html( $field_id ) . " input[type=radio][name=" . esc_js( $field_id ) . "]"; + break; + default: + $checks_escaped[] = "jQuery('#" . esc_html( $field_id ) . "').val() == " . json_encode( $check['value'] ) . " || jQuery.inArray( jQuery('#" . esc_html( $field_id ) . "').val(), " . json_encode( $check['value'] ) . ") > -1"; + $binds[] = "#" . esc_html( $field_id ); + break; + } } } @@ -1245,33 +1474,20 @@ function pmpro_id );?>_hideshow() { } } + /** + * Display the field at checkout. + * + * @deprecated TBD Use PMPro_Field_Group::display() instead. + */ function displayAtCheckout() { + _deprecated_function( __METHOD__, 'TBD', 'PMPro_Field_Group::display()' ); global $current_user; - if(isset($_REQUEST[$this->name])) { - $value = pmpro_sanitize( $_REQUEST[$this->name], $this ); - } elseif(isset($_SESSION[$this->name])) { - //file or value? - if(is_array($_SESSION[$this->name]) && !empty($_SESSION[$this->name]['name'])) - { - $_FILES[$this->name] = $_SESSION[$this->name]; - $this->file = pmpro_sanitize( $_SESSION[$this->name]['name'], $this ); - $value = pmpro_sanitize( $_SESSION[$this->name]['name'], $this ); - } else { - $value = pmpro_sanitize( $_SESSION[$this->name], $this ); - } - } - elseif(!empty($current_user->ID) && metadata_exists("user", $current_user->ID, $this->meta_key)) - { - $meta = get_user_meta($current_user->ID, $this->meta_key, true); - if(is_array($meta) && !empty($meta['filename'])) - { - $this->file = get_user_meta($current_user->ID, $this->meta_key, true); - $value = $this->file['filename']; - } else { - $value = $meta; - } + if( null !== $this->get_value_from_request() ) { + $value = $this->get_value_from_request(); + } elseif(!empty($current_user->ID) && metadata_exists("user", $current_user->ID, $this->meta_key)) { + $value = get_user_meta($current_user->ID, $this->meta_key, true); } elseif ( ! empty( $current_user->ID ) ) { $userdata = get_userdata( $current_user->ID ); if ( ! empty( $userdata->{$this->name} ) ) { @@ -1333,23 +1549,18 @@ function displayAtCheckout()
getDependenciesJS(); } + /** + * @deprecated TBD Use PMPro_Field_Group::display() instead. + */ function displayInProfile($user_id, $edit = NULL) { + _deprecated_function( __METHOD__, 'TBD', 'PMPro_Field_Group::display()' ); global $current_user; if(metadata_exists("user", $user_id, $this->meta_key)) { - $meta = get_user_meta($user_id, $this->meta_key, true); - if(is_array($meta) && !empty($meta['filename'])) - { - $this->file = get_user_meta($user_id, $this->meta_key, true); - $value = $this->file['filename']; - } - else - $value = $meta; + $value = get_user_meta($user_id, $this->meta_key, true); } elseif(!empty($this->value)) $value = $this->value; @@ -1375,8 +1586,6 @@ function displayInProfile($user_id, $edit = NULL) getDependenciesJS(); } /** @@ -1387,54 +1596,203 @@ function displayInProfile($user_id, $edit = NULL) * @since 3.0 Shows files as links and added echo parameter. */ function displayValue( $value, $echo = true ) { + // Build the output. $output = ''; $allowed_html = array(); - if(is_array( $value ) && ! empty( $this->options ) ) { - $labels = array(); - foreach( $value as $item ) { - $labels[] = $this->options[$item]; - } - $output .= implode( ', ', $labels); - } elseif( is_array( $value ) ) { - $output .= implode( ', ', $value ); - } elseif( ! empty( $this->options ) && isset( $this->options[$value] ) ) { - $output .= $this->options[$value]; - } elseif ( $this->type == 'checkbox' ) { - $output .= $value ? __( 'Yes', 'paid-memberships-pro' ) : __( 'No', 'paid-memberships-pro' ); - } elseif ( $this->type == 'file' ) { - // Show a preview of existing file if image type. - if ( ( ! empty( $this->preview ) ) && ! empty( $value ) && ! empty( $this->file['previewurl'] ) ) { - $filetype = wp_check_filetype( basename( $this->file['previewurl'] ), null ); - if ( $filetype && 0 === strpos( $filetype['type'], 'image/' ) ) { - $output .= '
' . basename($value) . '
'; + // Switch on the type of field. + switch( $this->type ) { + case 'text': + case 'textarea': + // Make sure that the value is a string. + $output = is_string( $value ) ? $value : ''; + + // If the field is a URL, check if we should try to embed it or show it as a link. + if ( wp_http_validate_url( $value ) ) { + /** + * Filter whether links should be clickable, embedded, or shown as plain text. + * + * @since TBD + * + * @param string $link_display_type The type of link display. Accepts 'embedded', 'clickable_link', 'clickable_label', or 'text'. + * @param string $value The value to be shown. + * @param PMPro_Field $field Field object that the value is for. + */ + $link_display_type = apply_filters( 'pmpro_field_value_link_display_type', 'embedded', $value, $this ); + switch ( $link_display_type ) { + case 'embedded': + $url_embed = wp_oembed_get( $value ); + if ( ! empty( $url_embed ) ) { + // Oembed returned a value. + $output = $url_embed; + $allowed_html = array( + 'iframe' => array( + 'src' => true, + 'height' => true, + 'width' => true, + 'frameborder' => true, + 'allowfullscreen' => true, + 'allow' => true, + ), + 'script' => array( + 'type' => true, + 'src' => true, + ), + ); + break; + } + // If we got here, we can't embed. Fall through to clickable link. + case 'clickable_link': + $output = '' . esc_html( $value ) . ''; + $allowed_html = array( + 'a' => array( + 'href' => true, + 'target' => true, + ), + ); + break; + case 'clickable_label': + $output = '' . esc_html( $this->label ) . ''; + $allowed_html = array( + 'a' => array( + 'href' => true, + 'target' => true, + ), + ); + break; + default: + // Do nothing. The value is already set. + $output = $value; + break; + } + } else { + $output = $value; } - $allowed_html['div'] = array( - 'class' => array(), - ); - $allowed_html['img'] = array( - 'src' => array(), - 'alt' => array(), - ); - } - - // Show link to file if available, or name if not. - if( ! empty( $this->file ) && ! empty( $this->file['fullurl'] ) ) { - $output .= '' . basename($value) . ''; - $allowed_html['span'] = array( - 'class' => array(), - ); - $allowed_html['a'] = array( - 'href' => array(), - 'target' => array(), - ); - } elseif( empty( $value ) ) { - $output .= esc_html__( 'N/A', 'paid-memberships-pro' ); - } else { - $output .= sprintf(__('Current File: %s', 'paid-memberships-pro' ), basename($value) ); - } - } else { - $output .= $value; + break; + case 'checkbox': + $output = $value ? __( 'Yes', 'paid-memberships-pro' ) : __( 'No', 'paid-memberships-pro' ); + break; + case 'number': + // Make sure that the value is a number. + $output = is_numeric( $value ) ? number_format_i18n( $value ) : ''; + break; + case 'date': + $output = date_i18n( get_option( 'date_format' ), $value ); + break; + case 'select': + case 'multiselect': + case 'select2': + case 'radio': + case 'checkbox_grouped': + // For simplicity, make sure that $value and $this->options are arrays. + if ( ! is_array( $value ) ) { + $value = array( $value ); + } + if ( ! is_array( $this->options ) ) { + $this->options = array(); + } + $labels = array(); + foreach( $value as $item ) { + $labels[] = array_key_exists( $item, $this->options ) ? $this->options[ $item ] : $item; + } + $output = implode( ', ', $labels ); + break; + case 'file': + // Validate the value. + if ( empty( $value ) ) { + $output = __( 'No file uploaded.', 'paid-memberships-pro' ); + } elseif ( ! is_array( $value ) || empty( $value['fullurl'] ) ) { + $output = __( 'Invalid file data.', 'paid-memberships-pro' ); + } else { + // We have a file. Determine how to display it. + $file_type = wp_check_filetype($value['fullurl']); + switch ( $file_type['type'] ) { + case 'image/jpeg': + case 'image/png': + case 'image/gif': + $output = ''; + $allowed_html = array( + 'a' => array( + 'href' => array(), + 'title' => array(), + 'target' => array(), + ), + 'div' => array( + 'class' => array(), + ), + 'img' => array( + 'alt' => array(), + 'class' => array(), + 'src' => array(), + ), + 'span' => array( + 'class' => array(), + ), + ); + break; + case 'video/mpeg': + case 'video/mp4': + $output = do_shortcode('[video src="' . $value['fullurl'] . '"]'); + $allowed_html = array( + 'video' => array( + 'src' => true, + 'poster' => true, + 'width' => true, + 'height' => true, + 'preload' => true, + 'controls' => true, + 'autoplay' => true, + 'loop' => true, + 'muted' => true, + ), + 'source' => array( + 'src' => true, + 'type' => true, + ), + ); + break; + case 'audio/mpeg': + case 'audio/wav': + $output = do_shortcode('[audio src="' . $value['fullurl'] . '"]'); + $allowed_html = array( + 'audio' => array( + 'src' => true, + 'controls' => true, + 'autoplay' => true, + 'loop' => true, + 'muted' => true, + 'preload' => true, + ), + 'source' => array( + 'src' => true, + 'type' => true, + ), + ); + break; + default: + $output = '
' . $value['filename'] . '
'; + $allowed_html = array( + 'a' => array( + 'href' => array(), + 'target' => array(), + ), + 'img' => array( + 'class' => array(), + 'src' => array(), + ), + 'div' => array( + 'class' => array(), + ), + ); + break; + } + // Wrap the output in a div. + $output = '
' . $output . '
'; + } + break; + default: + $output = (string) $value; + break; } // Enforce string as output. @@ -1445,7 +1803,6 @@ function displayValue( $value, $echo = true ) { } else { return wp_kses( $output, $allowed_html ); } - } /** @@ -1456,28 +1813,34 @@ function displayValue( $value, $echo = true ) { * @param array $array The array to check if it is associative. * @return bool True if the array is associative, false otherwise. */ - function is_assoc( $array ) { + private function is_assoc( $array ) { if ( empty( $array ) ) { return false; } return array_keys( $array ) !== range( 0, count( $array ) - 1) ; } + /** + * @deprecated TBD Use PMPro_Field_Group::get_group_for_field() instead. + */ static function get_checkout_box_name_for_field( $field_name ) { - global $pmpro_user_fields; - foreach( $pmpro_user_fields as $checkout_box_name => $fields ) { - foreach($fields as $field) { - if( $field->name == $field_name ) { - return $checkout_box_name; - } - } + _deprecated_function( __METHOD__, 'TBD', 'PMPro_Field_Group::get_group_for_field()' ); + $field = PMPro_Field_Group::get_field( $field_name ); + if ( empty( $field ) ) { + return ''; } - return ''; + + $field_group = PMPro_Field_Group::get_group_for_field( $field ); + return $field_group ? $field_group->name : ''; } + /** + * @deprecated TBD + */ function was_present_on_checkout_page() { + _deprecated_function( __METHOD__, 'TBD' ); // Check if checkout box that field is in is on page. - $checkout_box = PMPro_Field::get_checkout_box_name_for_field( $this->name ); + $checkout_box = PMPro_Field_Group::get_group_for_field( $this ); if ( empty( $checkout_box ) ) { // Checkout box does not exist. return false; @@ -1489,7 +1852,7 @@ function was_present_on_checkout_page() { 'after_email', 'after_captcha', ); - if ( is_user_logged_in() && in_array( $checkout_box, $user_fields_locations ) ) { + if ( is_user_logged_in() && in_array( $checkout_box->name, $user_fields_locations ) ) { // User is logged in and field is only for new users. return false; } @@ -1532,23 +1895,70 @@ function was_present_on_checkout_page() { return true; } + /** + * Check if the field was filled if needed. + */ function was_filled_if_needed() { - // If field is never required or is not present on checkout page, return true. - if ( ! $this->required || ! $this->was_present_on_checkout_page() ) { + // If the field is not required, skip it. + if ( empty( $this->required ) ) { return true; } - // Return whether the field is filled. + // If this field has a 'depends` attribute, check if the field was actually shown. + if ( ! empty( $this->depends ) ) { + foreach ( $this->depends as $check ) { + // If the $check object isn't valid, skip it. + if ( ! isset( $check->id ) || ! isset( $check->value ) ) { + return true; + } + + // Get the field to check. + $check_field = PMPro_Field_Group::get_field( $check->id ); + if ( empty( $check_field ) ) { + // The check field doesn't exist, so this field wasn't shown. + return true; + } + + // Get the value of the field. + $check_field_value = $check_field->get_value_from_request(); + if ( null === $check_field_value ) { + // The check field wasn't submitted, so this field wasn't shown. + return true; + } + + // If $check['value'] doesn't match the value of the field, skip this field. + if ( is_array( $check_field_value ) ) { + if ( ! in_array( $check->value, $check_field_value ) ) { + return true; + } + } else { + if ( $check->value !== $check_field_value ) { + return true; + } + } + } + } + + // At this point, we know that the field needs to be filled. + $value = $this->get_value_from_request(); switch ( $this->type ) { case 'text': case 'textarea': case 'number': - $filled = ( isset( $_REQUEST[$this->name] ) && '' !== trim( $_REQUEST[$this->name] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $filled = ( null !== $value && '' !== trim( $value ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + break; + case 'file': + if ( ! empty( $_FILES[ $this->name ]['name'] ) ) { + $filled = true; + } elseif ( ! empty( get_user_meta( get_current_user_id(), $this->name, true ) ) && empty( $_REQUEST['pmpro_delete_file_' . $this->name . '_field'] ) ) { + $filled = true; + } else { + $filled = false; + } break; default: - $filled = ! ( empty( $_REQUEST[$this->name] ) && empty( $_FILES[$this->name]['name'] ) && empty( $_REQUEST[$this->name.'_old'] ) ); + $filled = ! empty( $value ); } - return $filled; } } \ No newline at end of file diff --git a/css/admin.css b/css/admin.css index a79bf3f43..76ecbfa76 100644 --- a/css/admin.css +++ b/css/admin.css @@ -1228,7 +1228,7 @@ td > .pmpro_tag { } .pmpro_admin-pmpro-member button.wp-generate-pw { - margin-bottom: var(--pmpro--spacing--small ); + margin-bottom: var(--pmpro--spacing--small); } .pmpro_admin-pmpro-member #pmpro-member-edit-memberships-panel .pmpro_section .pmpro_section_inside { @@ -1909,18 +1909,21 @@ table.pmprommpu_levels tr.remove_level td {background: #F2DEDE; } } .pmpro_admin-pmpro-userfields .pmpro_userfield-inside input[type=text], +.pmpro_admin-pmpro-userfields .pmpro_userfield-inside input[type=number], .pmpro_admin-pmpro-userfields .pmpro_userfield-inside select, -.pmpro_admin-pmpro-userfields .pmpro_userfield-inside textarea { - width: 90%; +.pmpro_admin-pmpro-userfields .pmpro_userfield-inside textarea, +.pmpro_admin-pmpro-userfields .pmpro_userfield-inside .pmpro_checkbox_box { + max-width: 100%; + width: 100%; } .pmpro_admin-pmpro-userfields .pmpro_userfield-inside textarea { - height: 90px; + height: 65px; } .pmpro_admin-pmpro-userfields .pmpro_userfield-inside h3 { margin: 0; - padding: 8px 12px; + padding: var(--pmpro--spacing--small); } .pmpro_admin-pmpro-userfields .pmpro_userfield-group { @@ -1954,11 +1957,12 @@ table.pmprommpu_levels tr.remove_level td {background: #F2DEDE; } .pmpro_admin-pmpro-userfields .pmpro_userfield-group-thead li { font-weight: bold; - padding: 8px 12px; + padding: var(--pmpro--spacing--small); } .pmpro_admin-pmpro-userfields .pmpro_userfield-group-tbody li { - padding: 8px 12px; + margin: 0; + padding: var(--pmpro--spacing--small); } .pmpro_admin-pmpro-userfields li.pmpro_userfield-group-column-order { @@ -1986,43 +1990,18 @@ table.pmprommpu_levels tr.remove_level td {background: #F2DEDE; } font-weight: bold; } -.pmpro_admin-pmpro-userfields .pmpro_userfield-group-field-collapse li.pmpro_userfield-group-column-label .pmpro_userfield-label { - margin-bottom: .2em; -} - .pmpro_admin-pmpro-userfields .pmpro_userfield-group-field-expand li.pmpro_userfield-group-column-label, .pmpro_admin-pmpro-userfields .pmpro_userfield-group-field-expand li.pmpro_userfield-group-column-name, .pmpro_admin-pmpro-userfields .pmpro_userfield-group-field-expand li.pmpro_userfield-group-column-type { display: none; } -.pmpro_admin-pmpro-userfields .pmpro_userfield-group-field .pmpro_userfield-group-options { +.pmpro_admin-pmpro-userfields .pmpro_userfield-field-options { color: #DDD; font-size: 13px; - left: -9999em; position: relative; } -.pmpro_admin-pmpro-userfields .pmpro_userfield-group-field .pmpro_userfield-group-options a { - text-decoration: none; -} - -.pmpro_admin-pmpro-userfields .pmpro_userfield-group-field .pmpro_userfield-group-options a:hover { - text-decoration: underline; -} - -.pmpro_admin-pmpro-userfields .pmpro_userfield-group-field-collapse:hover .pmpro_userfield-group-options { - left: 0; -} - -.pmpro_admin-pmpro-userfields .pmpro_userfield-group-field-expand .pmpro_userfield-group-options { - display: none; -} - -.pmpro_admin-pmpro-userfields .pmpro_userfield-group-field-expand .pmpro_userfield-group-options a { - color: #FFF; -} - .pmpro_admin-pmpro-userfields .pmpro_userfield-group-field { border-bottom: 1px solid var(--pmpro--border--color); } @@ -2041,38 +2020,30 @@ table.pmprommpu_levels tr.remove_level td {background: #F2DEDE; } align-items: center; display: flex; flex-grow: 1; - margin: 0 0 0 12px; + margin: 0 0 0 var(--pmpro--spacing--small); } .pmpro_admin-pmpro-userfields .pmpro_userfield-group-header h3 label { display: inline-block; - margin-right: 5px; + margin-right: calc( var(--pmpro--spacing--small) / 2 ); } .pmpro_admin-pmpro-userfields .pmpro_userfield-field-settings { - padding: 0 12px; + padding: 0 var(--pmpro--spacing--small); } .pmpro_admin-pmpro-userfields .pmpro_userfield-field-settings .pmpro_userfield-field-setting { - margin: 12px 0; -} - -.pmpro_admin-pmpro-userfields .pmpro_userfield-field-settings .pmpro_userfield-field-setting-dual { - column-count: 2; -} - -.pmpro_admin-pmpro-userfields .pmpro_userfield-field-settings .pmpro_userfield-field-setting-dual .pmpro_userfield-field-setting { - margin: 0; + margin: var(--pmpro--spacing--small) 0; } .pmpro_admin-pmpro-userfields .pmpro_userfield-field-settings .pmpro_userfield-field-setting-radio span { display: inline-block; - margin-right: 12px; + margin-right: var(--pmpro--spacing--small); } .pmpro_admin-pmpro-userfields .pmpro_userfield-field-actions { width: 100%; - padding: 8px 12px; + padding: var(--pmpro--spacing--small); } .pmpro_admin-pmpro-userfields .pmpro_userfield-group-actions { @@ -2087,13 +2058,19 @@ table.pmprommpu_levels tr.remove_level td {background: #F2DEDE; } width: 500px; } - .pmpro_admin-pmpro-userfields .pmpro_userfield-field-settings { + .pmpro_admin-pmpro-userfields .pmpro_userfield-field-settings, + .pmpro_admin-pmpro-userfields .pmpro_userfield-row-settings { display: flex; flex-wrap: wrap; + column-gap: var(--pmpro--spacing--medium); + } + + .pmpro_admin-pmpro-userfields .pmpro_userfield-field-setting_1-2 { + flex: 0 0 calc( 50% - var(--pmpro--spacing--medium) ); } - .pmpro_admin-pmpro-userfields .pmpro_userfield-field-setting { - flex: 50%; + .pmpro_admin-pmpro-userfields .pmpro_userfield-field-setting_1-4 { + flex: 0 0 calc( 25% - var(--pmpro--spacing--medium) ); } } diff --git a/css/frontend/base.css b/css/frontend/base.css index 3482fc922..d9160ab41 100644 --- a/css/frontend/base.css +++ b/css/frontend/base.css @@ -176,6 +176,11 @@ margin-right: var(--pmpro--base--spacing--small); } +.pmpro_form_field-file-preview img { + height: auto; + width: 200px; +} + /* Checkout and Billing forms */ #pmpro_payment_information_fields .pmpro_form_fields label { display: block; diff --git a/includes/fields.php b/includes/fields.php index b176e077f..5728c5e70 100644 --- a/includes/fields.php +++ b/includes/fields.php @@ -1,15 +1,4 @@ name = 'checkout_boxes'; -$cb->label = apply_filters( 'pmpro_default_field_group_label', 'More Information' ); -$cb->order = 0; -$pmpro_field_groups = array( 'checkout_boxes' => $cb ); - /** * Check if a variable is a PMPro_Field. * Also checks for PMProRH_Field. @@ -36,37 +25,22 @@ function pmpro_is_field( $var ) { * - just_profile (make sure you set the profile attr of the field to true or admins) */ function pmpro_add_user_field( $where, $field ) { - global $pmpro_user_fields; - /** * Filter the group to add the field to. * * @since 2.9.3 + * @deprecated TBD * * @param string $where The name of the group to add the field to. * @param PMPro_Field $field The field being added. */ - $where = apply_filters( 'pmpro_add_user_field_where', $where, $field ); - - /** - * Filter the field to add. - * - * @since 2.9.3 - * - * @param PMPro_Field $field The field being added. - * @param string $where The name of the group to add the field to. - */ - $field = apply_filters( 'pmpro_add_user_field', $field, $where ); - - if(empty($pmpro_user_fields[$where])) { - $pmpro_user_fields[$where] = array(); - } - if ( ! empty( $field ) && pmpro_is_field( $field ) ) { - $pmpro_user_fields[$where][] = $field; - return true; - } + $where = apply_filters_deprecated( 'pmpro_add_user_field_where', array( $where, $field ), 'TBD', 'pmpro_add_user_field' ); + + // Get the field group. + $field_group = PMPro_Field_Group::get( $where ); - return false; + // Add the field to the group. + $field_group->add_field( $field ); } /** @@ -77,34 +51,7 @@ function pmpro_add_user_field( $where, $field ) { * Name must contain no spaces or special characters. */ function pmpro_add_field_group( $name, $label = NULL, $description = '', $order = NULL ) { - global $pmpro_field_groups; - // Bail if the group already exists. - foreach ( $pmpro_field_groups as $group ) { - if ( $group->name === $name ) { - // Group already exists. - return false; - } - } - - $temp = new stdClass(); - $temp->name = $name; - $temp->label = $label; - $temp->description = $description; - $temp->order = $order; - - //defaults - if( empty( $temp->label ) ) { - $temp->label = ucwords($temp->name); - } - if( ! isset( $order ) ) { - $lastbox = pmpro_array_end( $pmpro_field_groups ); - $temp->order = $lastbox->order + 1; - } - - $pmpro_field_groups[$name] = $temp; - usort( $pmpro_field_groups, 'pmpro_sort_by_order' ); - - return true; + return PMPro_Field_Group::add( $name, $label, $description ); } /** @@ -208,17 +155,12 @@ function pmpro_add_user_taxonomy( $name, $name_plural ) { /** * Get a field group by name. + * + * @deprecated TBD Use PMPro_Field_Group::get instead. */ function pmpro_get_field_group_by_name( $name ) { - global $pmpro_field_groups; - if( ! empty( $pmpro_field_groups ) ) { - foreach( $pmpro_field_groups as $group ) { - if( $group->name == $name ) { - return $group; - } - } - } - return false; + _deprecated_function( __FUNCTION__, 'TBD', 'PMPro_Field_Group::get' ); + return PMPro_Field_Group::get( $name ); } /** @@ -254,29 +196,58 @@ function pmpro_check_field_for_level( $field, $scope = 'default', $args = NULL ) return true; } +/** + * Get a list of all fields that are only shown when creating a user at checkout. + */ +function pmpro_get_user_creation_field_groups() { + return array( + 'after_username', + 'after_password', + 'after_email', + ); +} + /** * Find fields in a group and display them at checkout. + * This function is only used for the following fields at checkout: + * - after_username + * - after_password + * - after_email + * - after_captcha + * - checkout_boxes + * - after_billing_fields + * - before_submit_button + * - after_tos_fields */ function pmpro_display_fields_in_group( $group, $scope = 'checkout' ) { - global $pmpro_user_fields; - - if( ! empty( $pmpro_user_fields[$group] ) ) { - foreach( $pmpro_user_fields[$group] as $field ) { - if ( ! pmpro_is_field( $field ) ) { - continue; - } - - if ( ! pmpro_check_field_for_level( $field ) ) { - continue; - } - - if ( $scope == 'checkout' ) { - if( ! isset( $field->profile ) || $field->profile !== 'only' && $field->profile !== 'only_admin' ) { - $field->displayAtCheckout(); - } - } - } + $valid_groups = array( + 'after_username', + 'after_password', + 'after_pricing_fields', + 'after_email', + 'after_captcha', + 'after_billing_fields', + 'before_submit_button', + 'after_tos_fields', + ); + if ( ! in_array( $group, $valid_groups ) ) { + _doing_it_wrong( __FUNCTION__, sprintf( __( 'The group %s should not be passed into %s. Use PMPro_Field_Group::display() instead.', 'paid-memberships-pro' ), $group, __FUNCTION__ ), '2.9.3' ); } + if ( $scope !== 'checkout' ) { + _doing_it_wrong( __FUNCTION__, sprintf( __( 'The scope %s should not be passed into %s. Use PMPro_Field_Group::display() instead.', 'paid-memberships-pro' ), $scope, __FUNCTION__ ), '2.9.3' ); + } + + // Get the field group. + $field_group = PMPro_Field_Group::get( $group ); + $field_group->display( + array( + 'markup' => 'div', + 'scope' => 'checkout', + 'show_group_label' => false, + 'prefill_from_request' => true, + 'show_required' => true, + ) + ); } /** @@ -308,44 +279,30 @@ function pmpro_checkout_after_captcha_fields() { //checkout boxes function pmpro_checkout_boxes_fields() { - global $pmpro_user_fields, $pmpro_field_groups; + // Get all field groups. + $field_groups = PMPro_Field_Group::get_all(); - foreach($pmpro_field_groups as $cb) - { - //how many fields to show at checkout? - $n = 0; - if(!empty($pmpro_user_fields[$cb->name])) - foreach($pmpro_user_fields[$cb->name] as $field) - if(pmpro_is_field($field) && pmpro_check_field_for_level($field) && (!isset($field->profile) || (isset($field->profile) && $field->profile !== "only" && $field->profile !== "only_admin"))) $n++; + $checkout_level = pmpro_getLevelAtCheckout(); + $chekcout_level_id = ! empty( $checkout_level->id ) ? (int)$checkout_level->id : NULL; + if ( empty( $chekcout_level_id ) ) { + return; + } - if($n > 0) { - ?> -
-
-
- label ) ) { ?> - -

label ); ?>

-
- -
- description ) ) { ?> -
description ); ?>
- - - name] as $field) { - if( pmpro_is_field($field) && pmpro_check_field_for_level($field) && (!isset($field->profile) || (isset($field->profile) && $field->profile !== "only" && $field->profile !== "only_admin"))) { - $field->displayAtCheckout(); - } - } - ?> -
-
-
-
- $field_group ) { + // If this is not a checkout box, skip it. + if ( in_array( $field_group_name, array( 'after_username', 'after_password', 'after_email', 'after_captcha', 'after_pricing_fields', 'after_billing_fields', 'before_submit_button', 'after_tos_fields' ) ) ) { + continue; } + + $field_group->display( + array( + 'markup' => 'card', + 'scope' => 'checkout', + 'prefill_from_request' => true, + 'show_required' => true, + ) + ); } } add_action( 'pmpro_checkout_boxes', 'pmpro_checkout_boxes_fields' ); @@ -375,94 +332,130 @@ function pmpro_checkout_after_tos_fields() { add_action( 'pmpro_checkout_before_submit_button', 'pmpro_checkout_after_tos_fields', 6 ); /** - * Update the fields at checkout. + * Update user creation fields at checkout after a user is created. + * + * Only runs for the after_username, after_email, and after_password field groups. + * + * @since TBD + * + * @param int $user_id The ID of the user that was created. */ -function pmpro_after_checkout_save_fields( $user_id, $order ) { - global $pmpro_user_fields; +function pmpro_checkout_before_user_auth_save_fields( $user_id ) { + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + $user_creation_field_groups = pmpro_get_user_creation_field_groups(); + foreach($field_groups as $group_name => $group) { + if ( ! in_array( $group_name, $user_creation_field_groups ) ) { + continue; + } - //any fields? - if(!empty($pmpro_user_fields)) - { - //cycle through groups - foreach($pmpro_user_fields as $where => $fields) - { - //cycle through fields - foreach($fields as $field) - { - if( ! pmpro_is_field( $field ) ) { - continue; - } - - if ( ! pmpro_check_field_for_level( $field, "profile", $user_id ) ) { - continue; - } + // Save the fields. + $group->save_fields( + array( + 'user_id' => $user_id, + 'scope' => 'checkout', + ) + ); + } +} +add_action( 'pmpro_checkout_before_user_auth', 'pmpro_checkout_before_user_auth_save_fields' ); - if(!empty($field->profile) && ($field->profile === "only" || $field->profile === "only_admin")) { - continue; //wasn't shown at checkout - } +/** + * Require required fields before creating a user at checkout. + * + * Only runs for the after_username, after_email, and after_password field groups. + */ +function pmpro_checkout_user_creation_checks_user_fields( $okay ) { + // Arrays to store fields that were required and missed. + $required = array(); + $required_labels = array(); - //assume no value - $value = NULL; + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + $user_creation_field_groups = pmpro_get_user_creation_field_groups(); + foreach($field_groups as $group_name => $group) { + if ( ! in_array( $group_name, $user_creation_field_groups ) ) { + continue; + } - // Where are we getting the value from? We sanitize $value right after this. - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - if(isset($_REQUEST[$field->name])) - { - //request - $value = $_REQUEST[$field->name]; - } - elseif(isset($_REQUEST[$field->name . '_checkbox']) && $field->type == 'checkbox') - { - //unchecked checkbox - $value = 0; - } - elseif(!empty($_POST[$field->name . "_checkbox"]) && in_array( $field->type, array( 'checkbox', 'checkbox_grouped', 'select2' ) ) ) //handle unchecked checkboxes - { - //unchecked checkbox - $value = array(); + // Loop through all the fields in the group. + $fields = $group->get_fields_to_display( + array( + 'scope' => 'checkout', + ) + ); + foreach($fields as $field) { + // If this is a file upload, check whether the file is allowed. + if ( isset( $_FILES[ $field->name ] ) && ! empty( $_FILES[$field->name]['name'] ) ) { + $upload_check = pmpro_check_upload( $field->name ); + if ( is_wp_error( $upload_check ) ) { + pmpro_setMessage( $upload_check->get_error_message(), 'pmpro_error' ); + return false; } - elseif(isset($_SESSION[$field->name])) - { - //file or value? - if(is_array($_SESSION[$field->name]) && isset($_SESSION[$field->name]['name'])) - { - //add to files global - $_FILES[$field->name] = $_SESSION[$field->name]; - - //set value to name - $value = $_SESSION[$field->name]['name']; - } - else - { - //session - $value = $_SESSION[$field->name]; - } + } - //unset - unset($_SESSION[$field->name]); - } - elseif(isset($_FILES[$field->name])) - { - //file - $value = $_FILES[$field->name]['name']; - } - // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + // If the field was filled if needed, skip it. + if ( $field->was_filled_if_needed() ) { + continue; + } - //update user meta - if(isset($value)) - { - if ( ! empty( $field->sanitize ) ) { - $value = pmpro_sanitize( $value, $field ); - } + // The field was not filled. + $required[] = $field->name; + $required_labels[] = $field->label; + } + } - //callback? - if(!empty($field->save_function)) - call_user_func( $field->save_function, $user_id, $field->name, $value, $order ); - else - update_user_meta($user_id, $field->meta_key, $value); - } - } + if(!empty($required)) + { + $required = array_unique($required); + + //add them to error fields + global $pmpro_error_fields; + $pmpro_error_fields = array_merge((array)$pmpro_error_fields, $required); + + if( count( $required ) == 1 ) { + $pmpro_msg = sprintf( __( 'The %s field is required.', 'paid-memberships-pro' ), implode(", ", $required_labels) ); + $pmpro_msgt = 'pmpro_error'; + } else { + $pmpro_msg = sprintf( __( 'The %s fields are required.', 'paid-memberships-pro' ), implode(", ", $required_labels) ); + $pmpro_msgt = 'pmpro_error'; } + + if($okay) + pmpro_setMessage($pmpro_msg, $pmpro_msgt); + + return false; + } + + //return whatever status was before + return $okay; +} +add_filter( 'pmpro_checkout_user_creation_checks', 'pmpro_checkout_user_creation_checks_user_fields' ); + +/** + * Update the fields after a checkout is completed. + * + * Does not run for the after_username, after_email, and after_password field groups. + * + * @param int $user_id The ID of the user that was created. + * @param object $order The order object. + */ +function pmpro_after_checkout_save_fields( $user_id, $order ) { + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + $user_creation_field_groups = pmpro_get_user_creation_field_groups(); + foreach($field_groups as $group_name => $group) { + if ( in_array( $group_name, $user_creation_field_groups ) ) { + continue; + } + + // Save the fields. + $group->save_fields( + array( + 'user_id' => $user_id, + 'scope' => 'checkout', + ) + ); } } add_action( 'pmpro_after_checkout', 'pmpro_after_checkout_save_fields', 10, 2 ); @@ -472,55 +465,47 @@ function pmpro_after_checkout_save_fields( $user_id, $order ) { add_action( 'pmpro_before_send_to_payfast', 'pmpro_after_checkout_save_fields', 20, 2 ); //for the Payfast Gateway Add On /** - * Require required fields. + * Require required fields before creating an order at checkout. + * + * Does not run for the after_username, after_email, and after_password field groups. */ function pmpro_registration_checks_for_user_fields( $okay ) { - global $current_user; - - //arrays to store fields that were required and missed + // Arrays to store fields that were required and missed. $required = array(); $required_labels = array(); - //any fields? - global $pmpro_user_fields; - if(!empty($pmpro_user_fields)) - { - //cycle through groups - foreach($pmpro_user_fields as $where => $fields) - { - //cycle through fields - foreach($fields as $field) - { - //handle arrays - $field->name = preg_replace('/\[\]$/', '', $field->name); - - //if the field is not for this level, skip it - if( ! pmpro_is_field( $field ) ) { - continue; - } - - if ( ! pmpro_check_field_for_level( $field ) ) { - continue; - } - - if(!empty($field->profile) && ($field->profile === "only" || $field->profile === "only_admin")) { - continue; //wasn't shown at checkout - } + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + $user_creation_field_groups = pmpro_get_user_creation_field_groups(); + foreach($field_groups as $group_name => $group) { + if ( in_array( $group_name, $user_creation_field_groups ) ) { + continue; + } - // If this is a file upload, check whether the file is allowed. - if ( isset( $_FILES[ $field->name ] ) && ! empty( $_FILES[$field->name]['name'] ) ) { - $upload_check = pmpro_check_upload( $field->name ); - if ( is_wp_error( $upload_check ) ) { - pmpro_setMessage( $upload_check->get_error_message(), 'pmpro_error' ); - return false; - } + // Loop through all the fields in the group. + $fields = $group->get_fields_to_display( + array( + 'scope' => 'checkout', + ) + ); + foreach($fields as $field) { + // If this is a file upload, check whether the file is allowed. + if ( isset( $_FILES[ $field->name ] ) && ! empty( $_FILES[$field->name]['name'] ) ) { + $upload_check = pmpro_check_upload( $field->name ); + if ( is_wp_error( $upload_check ) ) { + pmpro_setMessage( $upload_check->get_error_message(), 'pmpro_error' ); + return false; } + } - if( ! $field->was_filled_if_needed() ) { - $required[] = $field->name; - $required_labels[] = $field->label; - } + // If the field was filled if needed, skip it. + if ( $field->was_filled_if_needed() ) { + continue; } + + // The field was not filled. + $required[] = $field->name; + $required_labels[] = $field->label; } } @@ -557,68 +542,63 @@ function pmpro_registration_checks_for_user_fields( $okay ) { * @deprecated 2.12.4 Use pmpro_after_checkout_save_fields instead to save fields immediately or pmpro_save_checkout_data_to_order for delayed checkouts. */ function pmpro_paypalexpress_session_vars_for_user_fields() { - global $pmpro_user_fields; - _deprecated_function( __FUNCTION__, '2.12.4', 'pmpro_after_checkout_save_fields' ); - //save our added fields in session while the user goes off to PayPal - if(!empty($pmpro_user_fields)) - { - //cycle through groups - foreach($pmpro_user_fields as $where => $fields) + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + foreach($field_groups as $group_name => $group) { + // Loop through all the fields in the group. + $fields = $group->get_fields(); + foreach($fields as $field) { - //cycle through fields - foreach($fields as $field) - { - if( ! pmpro_is_field( $field ) ) { - continue; - } - - if ( ! pmpro_check_field_for_level( $field ) ) { - continue; - } + if( ! pmpro_is_field( $field ) ) { + continue; + } + + if ( ! pmpro_check_field_for_level( $field ) ) { + continue; + } - if( isset( $_REQUEST[$field->name] ) ) { - $_SESSION[$field->name] = pmpro_sanitize( $_REQUEST[$field->name], $field ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - } elseif ( isset( $_FILES[$field->name] ) ) { - /* - We need to save the file somewhere and save values in $_SESSION - */ - // Make sure the file is allowed. - $upload_check = pmpro_check_upload( $field->name ); - if ( is_wp_error( $upload_check ) ) { - continue; - } + if( isset( $_REQUEST[$field->name] ) ) { + $_SESSION[$field->name] = pmpro_sanitize( $_REQUEST[$field->name], $field ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + } elseif ( isset( $_FILES[$field->name] ) ) { + /* + We need to save the file somewhere and save values in $_SESSION + */ + // Make sure the file is allowed. + $upload_check = pmpro_check_upload( $field->name ); + if ( is_wp_error( $upload_check ) ) { + continue; + } - // Get $file and $filetype. - $file = array_map( 'sanitize_text_field', $_FILES[ $field->name ] ); - $filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] ); + // Get $file and $filetype. + $file = array_map( 'sanitize_text_field', $_FILES[ $field->name ] ); + $filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] ); - // Make sure file was uploaded during this page load. - if ( ! is_uploaded_file( sanitize_text_field( $file['tmp_name'] ) ) ) { - continue; - } + // Make sure file was uploaded during this page load. + if ( ! is_uploaded_file( sanitize_text_field( $file['tmp_name'] ) ) ) { + continue; + } - //check for a register helper directory in wp-content - $upload_dir = wp_upload_dir(); - $pmprorh_dir = $upload_dir['basedir'] . "/pmpro-register-helper/tmp/"; + //check for a register helper directory in wp-content + $upload_dir = wp_upload_dir(); + $pmprorh_dir = $upload_dir['basedir'] . "/pmpro-register-helper/tmp/"; - //create the dir and subdir if needed - if(!is_dir($pmprorh_dir)) - { - wp_mkdir_p($pmprorh_dir); - } + //create the dir and subdir if needed + if(!is_dir($pmprorh_dir)) + { + wp_mkdir_p($pmprorh_dir); + } - //move file - $new_filename = $pmprorh_dir . basename( sanitize_file_name( $file['name'] ) ); - move_uploaded_file( sanitize_text_field( $$file['tmp_name'] ), $new_filename ); + //move file + $new_filename = $pmprorh_dir . basename( sanitize_file_name( $file['name'] ) ); + move_uploaded_file( sanitize_text_field( $$file['tmp_name'] ), $new_filename ); - //update location of file - $_FILES[$field->name]['tmp_name'] = $new_filename; + //update location of file + $_FILES[$field->name]['tmp_name'] = $new_filename; - //save file info in session - $_SESSION[$field->name] = array_map( 'sanitize_text_field', $file ); - } + //save file info in session + $_SESSION[$field->name] = array_map( 'sanitize_text_field', $file ); } } } @@ -626,65 +606,41 @@ function pmpro_paypalexpress_session_vars_for_user_fields() { /** * Show user fields in profile. + * + * @deprecated TBD */ function pmpro_show_user_fields_in_profile( $user, $withlocations = false ) { - global $pmpro_user_fields; - - //which fields are marked for the profile - $profile_fields = pmpro_get_user_fields_for_profile($user->ID, $withlocations); - - //show the fields - if(!empty($profile_fields) && $withlocations) - { - foreach($profile_fields as $where => $fields) - { - $box = pmpro_get_field_group_by_name($where); - - if ( !empty($box->label) ) { - ?> -

label ); ?>

- description ) ) { - ?> -

description ); ?>

- - - - - displayInProfile($user->ID); - } - ?> -
- - - displayInProfile($user->ID); - } - } - ?> -
- display( + array( + 'markup' => 'table', + 'scope' => 'profile', + 'show_group_label' => $withlocations, + 'user_id' => $user->ID, + ) + ); + } } + +/** + * Show user fields in the backend profile. + */ function pmpro_show_user_fields_in_profile_with_locations( $user ) { - pmpro_show_user_fields_in_profile($user, true); + $groups = PMPro_Field_Group::get_all(); + foreach( $groups as $group ) { + $group->display( + array( + 'markup' => 'table', + 'scope' => 'profile', + 'user_id' => $user->ID, + ) + ); + } } add_action( 'show_user_profile', 'pmpro_show_user_fields_in_profile_with_locations' ); add_action( 'edit_user_profile', 'pmpro_show_user_fields_in_profile_with_locations' ); @@ -693,69 +649,43 @@ function pmpro_show_user_fields_in_profile_with_locations( $user ) { * Show Profile fields on the frontend "Member Profile Edit" page. * * @since 2.3 + * @deprecated TBD */ function pmpro_show_user_fields_in_frontend_profile( $user, $withlocations = false ) { - //which fields are marked for the profile - $profile_fields = pmpro_get_user_fields_for_profile($user->ID, $withlocations); - - //show the fields - if ( ! empty( $profile_fields ) && $withlocations ) { - foreach( $profile_fields as $where => $fields ) { - $box = pmpro_get_field_group_by_name( $where ); - - // Only show on front-end if there are fields to be shown. - $show_fields = array(); - foreach( $fields as $key => $field ) { - if ( pmpro_is_field( $field ) && $field->profile !== 'only_admin' && $field->profile !== 'admin' && $field->profile !== 'admins' ) { - $show_fields[] = $field; - } - } - - // Bail if there are no fields to show on the front-end profile. - if ( empty( $show_fields ) ) { - continue; - } - ?> -
-
- label ) ) { ?> - -

label ); ?>

-
- -
- description ) ) { ?> -
description ); ?>
- - - displayAtCheckout( $user->ID ); - } - ?> -
-
- -
-
- profile !== 'only_admin' ) { - $field->displayAtCheckout( $user->ID ); - } - } - ?> -
-
- display( + array( + 'markup' => 'div', + 'scope' => 'profile', + 'show_group_label' => $withlocations, + 'user_id' => $user->ID, + ) + ); + } } + +/** + * Show Profile fields on the frontend "Member Profile Edit" page. + * + * @since 2.3 + */ function pmpro_show_user_fields_in_frontend_profile_with_locations( $user ) { - pmpro_show_user_fields_in_frontend_profile( $user, true ); + $groups = PMPro_Field_Group::get_all(); + foreach( $groups as $group ) { + $group->display( + array( + 'markup' => 'div', + 'scope' => 'profile', + 'user_id' => $user->ID, + ) + ); + } } add_action( 'pmpro_show_user_profile', 'pmpro_show_user_fields_in_frontend_profile_with_locations' ); @@ -764,47 +694,63 @@ function pmpro_show_user_fields_in_frontend_profile_with_locations( $user ) { * when using the Add Member Admin Add On. */ // Add fields to form. -function pmpro_add_member_admin_fields( $user = null, $user_id = null) -{ - global $pmpro_user_fields; - - $addmember_fields = array(); - if(!empty($pmpro_user_fields)) - { - //cycle through groups - foreach($pmpro_user_fields as $where => $fields) - { - //cycle through fields - foreach($fields as $field) - { - if(pmpro_is_field($field) && isset($field->addmember) && !empty($field->addmember) && ( in_array( strtolower( $field->addmember ), array( 'true', 'yes' ) ) || true == $field->addmember ) ) - { - $addmember_fields[] = $field; - } - } - } +function pmpro_add_member_admin_fields( $user = null, $user_id = null) { + $addmember_fields = array(); + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + foreach($field_groups as $group_name => $group) { + // Loop through all the fields in the group. + $fields = $group->get_fields(); + foreach($fields as $field) + { + if(pmpro_is_field($field) && isset($field->addmember) && !empty($field->addmember) && ( in_array( strtolower( $field->addmember ), array( 'true', 'yes' ) ) || true == $field->addmember ) ) + { + $addmember_fields[] = $field; + } + } } //show the fields - if(!empty($addmember_fields)) - { - ?> - ID)) { - $user_id = $user->ID; - } + if(!empty($addmember_fields)) { + //cycle through groups + foreach($addmember_fields as $field) + { + if(empty($user_id) && !empty($user) && !empty($user->ID)) { + $user_id = $user->ID; + } - if( pmpro_is_field( $field ) ) { - $field->displayInProfile($user_id); - } - - } - ?> - meta_key)) + { + $value = get_user_meta($user_id, $field->meta_key, true); + } else { + $value = ""; + } + ?> + + + showmainlabel ) ) { ?> + + + + + display($value); + else + echo "
" . wp_kses_post( $field->displayValue($value) ) . "
"; + ?> + hint)) { ?> +

hint );?>

+ + + + $fields) - { - //cycle through fields - foreach($fields as $field) - { - if(pmpro_is_field($field) && isset($field->addmember) && !empty($field->addmember) && ( in_array( strtolower( $field->addmember ), array( 'true', 'yes' ) ) || true == $field->addmember ) ) - { - $addmember_fields[] = $field; - } - } - } + $addmember_fields = array(); + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + foreach($field_groups as $group_name => $group) { + // Loop through all the fields in the group. + $fields = $group->get_fields(); + foreach($fields as $field) + { + if(pmpro_is_field($field) && isset($field->addmember) && !empty($field->addmember) && ( in_array( strtolower( $field->addmember ), array( 'true', 'yes' ) ) || true == $field->addmember ) ) + { + $addmember_fields[] = $field; + } + } } //save our added fields in session while the user goes off to PayPal @@ -861,41 +804,7 @@ function pmpro_add_member_admin_save_user_fields( $uid = null, $user = null ) { //cycle through fields foreach($addmember_fields as $field) { - if(pmpro_is_field($field) && isset($_POST[$field->name]) || isset($_FILES[$field->name])) - { - // Sanitize by default, or not. Some fields may have custom save functions/etc. - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - if ( ! empty( $field->sanitize ) && isset( $_POST[ $field->name ] ) ) { - $value = pmpro_sanitize( $_POST[ $field->name ], $field ); - } elseif( isset($_POST[$field->name]) ) { - $value = $_POST[ $field->name ]; - } else { - $value = $_FILES[$field->name]; - } - // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - - //callback? - if(!empty($field->save_function)) - call_user_func($field->save_function, $user_id, $field->name, $value ); - else - update_user_meta($user_id, $field->meta_key, $value ); - } - elseif(pmpro_is_field($field) && !empty($_POST[$field->name . "_checkbox"]) && $field->type == 'checkbox') //handle unchecked checkboxes - { - //callback? - if(!empty($field->save_function)) - call_user_func($field->save_function, $user_id, $field->name, 0); - else - update_user_meta($user_id, $field->meta_key, 0); - } - elseif(!empty($_POST[$field->name . "_checkbox"]) && in_array( $field->type, array( 'checkbox', 'checkbox_grouped', 'select2' ) ) ) //handle unchecked checkboxes - { - //callback? - if(!empty($field->save_function)) - call_user_func($field->save_function, $user_id, $field->name, array()); - else - update_user_meta($user_id, $field->meta_key, array()); - } + $field->save_field_for_user( $user_id ); } } } @@ -905,22 +814,17 @@ function pmpro_add_member_admin_save_user_fields( $uid = null, $user = null ) { * Get user fields which are set to show up in the Members List CSV Export. */ function pmpro_get_user_fields_for_csv() { - global $pmpro_user_fields; - $csv_fields = array(); - if(!empty($pmpro_user_fields)) - { - //cycle through groups - foreach($pmpro_user_fields as $where => $fields) + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + foreach($field_groups as $group_name => $group) { + // Loop through all the fields in the group. + $fields = $group->get_fields(); + foreach($fields as $field) { - //cycle through fields - foreach($fields as $field) + if(pmpro_is_field($field) && !empty($field->memberslistcsv) && ($field->memberslistcsv == "true")) { - if(pmpro_is_field($field) && !empty($field->memberslistcsv) && ($field->memberslistcsv == "true")) - { - $csv_fields[] = $field; - } - + $csv_fields[] = $field; } } } @@ -931,45 +835,31 @@ function pmpro_get_user_fields_for_csv() { /** * Get user fields which are marked to show in the profile. * If a $user_id is passed in, get fields based on the user's level. + * + * @deprecated TBD Use PMPro_Field_Group::get_fields_to_display instead. */ function pmpro_get_user_fields_for_profile( $user_id, $withlocations = false ) { - global $pmpro_user_fields; - + _deprecated_function( __FUNCTION__, 'TBD', 'PMPro_Field_Group::get_fields_to_display' ); $profile_fields = array(); - if(!empty($pmpro_user_fields)) - { - //cycle through groups - foreach($pmpro_user_fields as $where => $fields) - { - //cycle through fields - foreach($fields as $field) - { - if( ! pmpro_is_field( $field ) ) { - continue; - } - - if ( ! pmpro_check_field_for_level( $field, "profile", $user_id ) ) { - continue; - } + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + foreach($field_groups as $group_name => $group) { + // Get the fields to display. + $fields_to_display = $group->get_fields_to_display( + array( + 'scope' => 'profile', + 'user_id' => $user_id, + ) + ); - if(!empty($field->profile) && ($field->profile === "admins" || $field->profile === "admin" || $field->profile === "only_admin")) - { - if( current_user_can( 'manage_options' ) || current_user_can( 'pmpro_membership_manager' ) ) - { - if($withlocations) - $profile_fields[$where][] = $field; - else - $profile_fields[] = $field; - } - } - elseif(!empty($field->profile)) - { - if($withlocations) - $profile_fields[$where][] = $field; - else - $profile_fields[] = $field; - } - } + if ( empty( $fields_to_display ) ) { + continue; + } + + if ( $withlocations ) { + $profile_fields[ $group_name ] = $fields_to_display; + } else { + $profile_fields = array_merge( $profile_fields, $fields_to_display ); } } @@ -992,54 +882,16 @@ function pmpro_save_user_fields_in_profile( $user_id ) if ( !current_user_can( 'edit_user', $user_id ) ) return false; - $profile_fields = pmpro_get_user_fields_for_profile($user_id); - - //save our added fields in session while the user goes off to PayPal - if(!empty($profile_fields)) - { - //cycle through fields - foreach($profile_fields as $field) - { - if( ! pmpro_is_field( $field ) ) { - continue; - } - - if(isset($_POST[$field->name]) || isset($_FILES[$field->name])) - { - // Sanitize by default, or not. Some fields may have custom save functions/etc. - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - if ( ! empty( $field->sanitize ) && isset( $_POST[ $field->name ] ) ) { - $value = pmpro_sanitize( $_POST[ $field->name ], $field ); - } elseif( isset($_POST[$field->name]) ) { - $value = $_POST[ $field->name ]; - } else { - $value = $_FILES[$field->name]; - } - // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - - //callback? - if(!empty($field->save_function)) - call_user_func($field->save_function, $user_id, $field->name, $value); - else - update_user_meta($user_id, $field->meta_key, $value); - } - elseif(!empty($_POST[$field->name . "_checkbox"]) && $field->type == 'checkbox') //handle unchecked checkboxes - { - //callback? - if(!empty($field->save_function)) - call_user_func($field->save_function, $user_id, $field->name, 0); - else - update_user_meta($user_id, $field->meta_key, 0); - } - elseif(!empty($_POST[$field->name . "_checkbox"]) && in_array( $field->type, array( 'checkbox', 'checkbox_grouped', 'select2' ) ) ) //handle unchecked checkboxes - { - //callback? - if(!empty($field->save_function)) - call_user_func($field->save_function, $user_id, $field->name, array()); - else - update_user_meta($user_id, $field->meta_key, array()); - } - } + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + foreach($field_groups as $group_name => $group) { + // Save the fields. + $group->save_fields( + array( + 'scope' => 'profile', + 'user_id' => $user_id, + ) + ); } } add_action( 'personal_options_update', 'pmpro_save_user_fields_in_profile' ); @@ -1050,63 +902,31 @@ function pmpro_save_user_fields_in_profile( $user_id ) * Add user fields to confirmation email. */ function pmpro_add_user_fields_to_email( $email ) { - global $wpdb, $pmpro_user_fields, $pmpro_field_groups; + global $wpdb; //only update admin confirmation emails if ( ! empty( $email ) && strpos( $email->template, "checkout" ) !== false && strpos( $email->template, "admin" ) !== false ) { //get the user_id from the email $user_id = $wpdb->get_var( "SELECT ID FROM $wpdb->users WHERE user_email = '" . esc_sql( $email->data['user_email'] ) . "' LIMIT 1" ); - $level_id = empty( $email->data['membership_id'] ) ? null : intval( $email->data['membership_id'] ); if ( ! empty( $user_id ) ) { - - //add to bottom of email - if ( ! empty( $pmpro_field_groups ) ) { + $field_groups = PMPro_Field_Group::get_all(); + if ( ! empty( $field_groups ) ) { $fields_content = "

" . __( 'Extra Fields:', 'paid-memberships-pro' ) . "
"; $added_field = false; - //cycle through groups - foreach( $pmpro_field_groups as $group ) { - - // Get the groups name so we can grab it from the associative array. - $group_name = $group->name; - - // Skip if there are no fields in this group. - if ( empty( $pmpro_user_fields[$group_name] ) ) { - continue; - } - - //cycle through groups and fields associated with that group. - foreach( $pmpro_user_fields[$group_name] as $field ) { - - if ( ! pmpro_is_field( $field ) ) { - continue; - } - - // If the field is showing only in the profile or to admins we can skip it. - if ( ! empty( $field->profile ) && ( $field->profile === "only" || $field->profile === "only_admin" ) ) { - continue; - } - - // Let's make sure the field level ID's are the same as the one they checked out for. - if ( ! empty( $field->levels ) && ( empty( $level_id) || ! in_array( $level_id, $field->levels ) ) ) { - continue; - } - + // Loop through all the field groups. + foreach( $field_groups as $group_name => $group ) { + // Loop through all the fields in the group. + $fields = $group->get_fields_to_display( + array( + 'scope' => 'checkout', + 'user_id' => $user_id, + ) + ); + foreach( $fields as $field ) { $fields_content .= "- " . esc_html( $field->label ) . ": "; - $value = get_user_meta( $user_id, $field->name, true); - - // Get the label value for field types that have labels. - $value = pmpro_get_label_for_user_field_value( $field->name, $value ); - - if ( $field->type == "file" && is_array( $value ) && ! empty( $value['fullurl'] ) ) { - $fields_content .= pmpro_sanitize( $value['fullurl'], $field ); - } elseif( is_array( $value ) ) { - $fields_content .= implode(", ", pmpro_sanitize( $value, $field ) ); - } else { - $fields_content .= pmpro_sanitize( $value, $field ); - } - + $fields_content .= $field->displayValue( get_user_meta( $user_id, $field->name, true), false ); $fields_content .= "
"; $added_field = true; } @@ -1157,37 +977,14 @@ function pmpro_csv_columns_for_user_fields( $user, $column ) { } } -/** - * Delete old files in wp-content/uploads/pmpro-register-helper/tmp every day. - */ -function pmpro_cron_delete_tmp() { - $upload_dir = wp_upload_dir(); - $pmprorh_dir = $upload_dir['basedir'] . "/paid-memberships-pro/tmp/"; - - if(file_exists($pmprorh_dir) && $handle = opendir($pmprorh_dir)) - { - while(false !== ($file = readdir($handle))) - { - $file = $pmprorh_dir . $file; - $filelastmodified = filemtime($file); - if(is_file($file) && (time() - $filelastmodified) > 3600) - { - unlink($file); - } - } - - closedir($handle); - } - - exit; -} -add_action( 'pmpro_cron_delete_tmp', 'pmpro_cron_delete_tmp' ); - /** * Get user fields from global. * @since 2.9.3 + * @deprecated TBD */ function pmpro_get_user_fields() { + _deprecated_function( __FUNCTION__, 'TBD' ); + global $pmpro_user_fields; return (array)$pmpro_user_fields; @@ -1198,349 +995,14 @@ function pmpro_get_user_fields() { * Get field group HTML for settings. */ function pmpro_get_field_group_html( $group = null ) { - if ( ! empty( $group ) ) { - // Assume group stdClass in format we save to settings. - $group_name = $group->name; - $group_show_checkout = $group->checkout; - $group_show_profile = $group->profile; - $group_description = $group->description; - $group_levels = $group->levels; - $group_fields = $group->fields; - } else { - // Default group settings. - $group_name = ''; - $group_show_checkout = 'yes'; - $group_show_profile = 'yes'; - $group_description = ''; - $group_levels = array(); - $group_fields = array(); - } - - // Other vars - $levels = pmpro_sort_levels_by_order( pmpro_getAllLevels( true, true ) ); - - // Render field group HTML. - ?> -

-
-
- - - - - -
-

- -

- - -
- -
-
- -
- -
- -
- -
- -
- -
- -
-
-
3 ) { ?>style="height: 90px; overflow: auto;"> - -
- -
- -
-
- -
- -

- -
    -
  • -
  • -
  • -
  • -
- -
- fields ) ) { - foreach ( $group->fields as $field ) { - pmpro_get_field_html( $field ); - } - } - ?> - - - -
- -
- - -
- -
-
- label; - $field_name = $field->name; - $field_type = $field->type; - $field_required = $field->required; - $field_readonly = $field->readonly; - $field_profile = $field->profile; - $field_wrapper_class = $field->wrapper_class; - $field_element_class = $field->element_class; - $field_hint = $field->hint; - $field_options = $field->options; - $field_allowed_file_types = $field->allowed_file_types; - $field_max_file_size = $field->max_file_size; - $field_default = $field->default; - } else { - // Default field values - $field_label = ''; - $field_name = ''; - $field_type = ''; - $field_required = false; - $field_readonly = false; - $field_profile = ''; - $field_wrapper_class = ''; - $field_element_class = ''; - $field_hint = ''; - $field_options = ''; - $field_allowed_file_types = ''; - $field_max_file_size = ''; - $field_default = ''; - } - - // Other vars - $levels = pmpro_sort_levels_by_order( pmpro_getAllLevels( true, true ) ); - ?> -
-
    -
  • -
    - - - - - -
    -
  • -
  • - -
    - | - | - -
    -
  • -
  • -
  • -
- - -
- name, $group->name, $group->description ); + $group_obj = PMPro_field_Group::add( $group->name, $group->name, $group->description ); // Figure out profile value. Change 2 settings values into 1 field value. if ( $group->checkout === 'yes' ) { @@ -1678,7 +1139,7 @@ function pmpro_load_user_fields_from_settings() { 'default' => $settings_field->default, ) ); - pmpro_add_user_field( $group->name, $field ); + $group_obj->add_field( $field ); } } } @@ -1688,11 +1149,13 @@ function pmpro_load_user_fields_from_settings() { * Check if user is adding custom user fields with code. * * @since 2.9 + * @deprecated TBD * * @return bool True if user is adding custom user fields with code. */ function pmpro_has_coded_user_fields() { - global $pmpro_user_fields, $pmprorh_registration_fields; + _deprecated_function( __FUNCTION__, 'TBD' ); + global $pmprorh_registration_fields; // Check if coded fields are being added using the PMPro Register Helper Add On active. if ( ! empty( $pmprorh_registration_fields ) ) { @@ -1700,15 +1163,16 @@ function pmpro_has_coded_user_fields() { } // Check if coded fields are being added using the PMPro Register Helper Add On inactive. - $num_db_fields = array_sum( array_map( function ($group) { return count( $group->fields ); }, pmpro_get_user_fields_settings() ) ); // Fields from UI settings page. - $num_global_fields = array_sum( array_map( 'count', $pmpro_user_fields ) ); // Total loaded fields. - return $num_global_fields > $num_db_fields; + $num_fields_from_settings = array_sum( array_map( function ($group) { return count( $group->fields ); }, pmpro_get_user_fields_settings() ) ); // Fields from UI settings page. + $total_registered_fields = array_sum( array_map( function ($group) { return count( $group->get_fields() ); }, PMPro_Field_Group::get_all() ) ); // All registered fields. + return $total_registered_fields > $num_fields_from_settings; } /** * Gets the label(s) for a passed user field value. * * @since 2.11 + * @deprecated TBD Use PMProField::displayValue instead. * * @param string $field_name The name of the field that the value belongs to. * @param string|array $field_value The value to get the label for. @@ -1716,9 +1180,14 @@ function pmpro_has_coded_user_fields() { * @return string|array The label(s) for the passed value. Will be same type as $field_value. */ function pmpro_get_label_for_user_field_value( $field_name, $field_value ) { - global $pmpro_user_fields; - foreach ( $pmpro_user_fields as $user_field_group ) { // Loop through each user field group. - foreach ( $user_field_group as $user_field ) { // Loop through each user field in the group. + _deprecated_function( __FUNCTION__, 'TBD', 'PMProField::displayValue' ); + + // Loop through all the field groups. + $field_groups = PMPro_Field_Group::get_all(); + foreach($field_groups as $group_name => $group) { + // Loop through all the fields in the group. + $fields = $group->get_fields(); + foreach( $fields as $user_field ) { // Check if this is the user field that we are displaying. if ( $user_field->name !== $field_name ) { continue; @@ -1740,42 +1209,21 @@ function pmpro_get_label_for_user_field_value( $field_name, $field_value ) { } // Replace meta values with their corresponding labels. - if ( is_array( $field_value ) ) { - foreach ( $field_value as $key => $value ) { - if ( isset( $user_field->options[ $value ] ) ) { - $field_value[ $key ] = $user_field->options[ $value ]; - } - } - } else { - if ( isset( $user_field->options[ $field_value ] ) ) { - $field_value = $user_field->options[ $field_value ]; - } - } + $field_value = $user_field->displayValue( $field_value, false ); } } return $field_value; } /** - * Get a single field from the global $pmpro_user_fields array. + * Get a single user field. * @since 3.0 + * @deprecated TBD * @param string $field_name The name of the field to get. * @return bool|object The field object if found, false otherwise. */ function pmpro_get_user_field( $field_name ) { - global $pmpro_user_fields; - - if ( empty( $pmpro_user_fields ) ) { - return false; - } - - foreach ( $pmpro_user_fields as $group ) { - foreach ( $group as $field ) { - if ( $field->name === $field_name ) { - return $field; - } - } - } - - return false; + _deprecated_function( __FUNCTION__, 'TBD', 'PMPro_Field_Group::get_field' ); + $field = PMPro_Field_Group::get_field( $field_name ); + return empty( $field ) ? false : $field; } diff --git a/includes/functions.php b/includes/functions.php index 1da6088ee..395710621 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -3809,12 +3809,17 @@ function pmpro_sanitize_with_safelist( $needle, $safelist ) { * Sanitizes the passed value. * Default sanitizing for things like user fields. * + * @since TBD Marking the $field argument as deprecated. + * * @param array|int|null|string|stdClass $value The value to sanitize - * @param PMPro_Field $field (optional) Field to check type. * * @return array|int|string|object Sanitized value */ function pmpro_sanitize( $value, $field = null ) { + if ( null !== $field ) { + // This argument is deprecated. User fields now have sanitization logic in the field class. + _deprecated_argument( __FUNCTION__, 'TBD', __( 'The $field argument is deprecated. The sanitization logic is now built into the PMPro_Field class.', 'paid-memberships-pro' ) ); + } if ( is_array( $value ) ) { @@ -4759,8 +4764,6 @@ function pmpro_set_expiration_date( $user_id, $level_id, $enddate ) { * @return true|WP_Error True if the file is allowed, otherwise a WP_Error object. */ function pmpro_check_upload( $file_index ) { - global $pmpro_user_fields; - // Check if the file was uploaded. if ( empty( $_FILES[ $file_index ] ) ) { return new WP_Error( 'pmpro_upload_error', __( 'No file was uploaded.', 'paid-memberships-pro' ) ); @@ -4781,51 +4784,40 @@ function pmpro_check_upload( $file_index ) { } // If this is an upload for a user field, we need to perform additional checks. - $is_user_field = false; - if ( ! empty( $pmpro_user_fields ) && is_array( $pmpro_user_fields ) ) { - foreach ( $pmpro_user_fields as $checkout_box ) { - foreach ( $checkout_box as $field ) { - if ( $field->name == $file_index ) { - // This file is being uploaded for a user field. - $is_user_field = true; - - // First, make sure that this is a 'file' field. - if ( $field->type !== 'file' ) { - return new WP_Error( 'pmpro_upload_error', __( 'Invalid field input.', 'paid-memberships-pro' ) ); - } + $field = PMPro_Field_Group::get_field( $file_index ); + if ( ! empty( $field) ) { + // First, make sure that this is a 'file' field. + if ( $field->type !== 'file' ) { + return new WP_Error( 'pmpro_upload_error', __( 'Invalid field input.', 'paid-memberships-pro' ) ); + } - // If there are allowed file types, check if the file is an allowed file type. - // It does not look like the ext property is documented anywhere, but keeping it in case sites are using it. - if ( ! empty( $field->ext ) && is_array( $field->ext ) && ! in_array( $filetype['ext'], $field->ext ) ) { - return new WP_Error( 'pmpro_upload_error', __( 'Invalid file type.', 'paid-memberships-pro' ) ); - } + // If there are allowed file types, check if the file is an allowed file type. + // It does not look like the ext property is documented anywhere, but keeping it in case sites are using it. + if ( ! empty( $field->ext ) && is_array( $field->ext ) && ! in_array( $filetype['ext'], $field->ext ) ) { + return new WP_Error( 'pmpro_upload_error', __( 'Invalid file type.', 'paid-memberships-pro' ) ); + } - // Check the file type against the allowed types. - $allowed_mime_types = ! empty( $field->allowed_file_types ) ? array_map( 'sanitize_text_field', explode( ',', $field->allowed_file_types ) ) : array(); + // Check the file type against the allowed types. + $allowed_mime_types = ! empty( $field->allowed_file_types ) ? array_map( 'sanitize_text_field', explode( ',', $field->allowed_file_types ) ) : array(); - //Remove fullstops from the beginning of the allowed file types. - $allowed_mime_types = array_map( function( $type ) { - return ltrim( $type, '.' ); - }, $allowed_mime_types ); + //Remove fullstops from the beginning of the allowed file types. + $allowed_mime_types = array_map( function( $type ) { + return ltrim( $type, '.' ); + }, $allowed_mime_types ); - // Check the file type against the allowed types. If empty allowed mimes, assume any file upload is okay. - if ( ! empty( $allowed_mime_types ) && ! in_array( $filetype['ext'], $allowed_mime_types ) ) { - return new WP_Error( 'pmpro_upload_file_type_error', sprintf( esc_html__( 'Invalid file type. Please try uploading the file type(s): %s', 'paid-memberships-pro' ), implode( ',' ,$allowed_mime_types ) ) ); - } - - // Check if the file upload is too big to upload. - if ( $field->max_file_size > 0 ) { - $upload_max_file_size_in_bytes = $field->max_file_size * 1024 * 1024; - if ( $file['size'] > $upload_max_file_size_in_bytes ) { - return new WP_Error( 'pmpro_upload_file_size_error', sprintf( esc_html__( 'File size is too large for %s. Please upload files smaller than %dMB.', 'paid-memberships-pro' ), $field->label, $field->max_file_size ) ); - } - } - } + // Check the file type against the allowed types. If empty allowed mimes, assume any file upload is okay. + if ( ! empty( $allowed_mime_types ) && ! in_array( $filetype['ext'], $allowed_mime_types ) ) { + return new WP_Error( 'pmpro_upload_file_type_error', sprintf( esc_html__( 'Invalid file type. Please try uploading the file type(s): %s', 'paid-memberships-pro' ), implode( ',' ,$allowed_mime_types ) ) ); + } + + // Check if the file upload is too big to upload. + if ( $field->max_file_size > 0 ) { + $upload_max_file_size_in_bytes = $field->max_file_size * 1024 * 1024; + if ( $file['size'] > $upload_max_file_size_in_bytes ) { + return new WP_Error( 'pmpro_upload_file_size_error', sprintf( esc_html__( 'File size is too large for %s. Please upload files smaller than %dMB.', 'paid-memberships-pro' ), $field->label, $field->max_file_size ) ); } } - } - - if ( ! $is_user_field ) { + } else { /** * Filter whether a file not associated with a user field can be uploaded. * diff --git a/js/pmpro-admin.js b/js/pmpro-admin.js index 16467385d..c7a669134 100644 --- a/js/pmpro-admin.js +++ b/js/pmpro-admin.js @@ -315,11 +315,11 @@ function pmpro_userfields_prep_click_events() { } }); - // Toggle groups. + // Toggle groups. jQuery('button.pmpro_userfield-group-buttons-button-toggle-group, div.pmpro_userfield-group-header h3').unbind('click').on('click', function (event) { event.preventDefault(); - // Ignore if the text field was clicked. + // Ignore if the text field was clicked. if (jQuery(event.target).prop('nodeName') === 'INPUT') { return; } @@ -418,15 +418,29 @@ function pmpro_userfields_prep_click_events() { pmpro_userfields_made_a_change(); }); + // Toggle required at checkout field settings based on group settings. + jQuery('select[name="pmpro_userfields_group_checkout"]').unbind('change').on('change', function () { + var groupContainer = jQuery(this).closest('.pmpro_userfield-inside'); + var fieldSettings = groupContainer.find('.pmpro_userfield-group-fields'); + var requiredFields = fieldSettings.find('#pmpro_userfield-field-setting_required'); + + // Toggle visibility based on group setting. + if (jQuery(this).val() === 'yes') { + requiredFields.show(); + } else { + requiredFields.hide(); + } + }).trigger('change'); + // Toggle field settings based on type. jQuery('select[name=pmpro_userfields_field_type]').on('change', function (event) { var fieldcontainer = jQuery(this).parents('.pmpro_userfield-group-field'); var fieldsettings = fieldcontainer.children('.pmpro_userfield-field-settings'); var fieldtype = jQuery(this).val(); - var fieldoptions = fieldsettings.find('textarea[name=pmpro_userfields_field_options]').parents('.pmpro_userfield-field-setting'); - var fieldfiles = fieldsettings.find('input[name=pmpro_userfields_field_max_file_size]').parents('.pmpro_userfield-field-setting'); - var fielddefault = fieldsettings.find('input[name=pmpro_userfields_field_default]').parents('.pmpro_userfield-field-setting'); + var fieldoptions = fieldsettings.find('#pmpro_userfield-field-setting_options'); + var fieldfiles = fieldsettings.find('#pmpro_userfield-row-settings_files'); + var fielddefault = fieldsettings.find('#pmpro_userfield-field-setting_default'); // Hide all the field settings. fieldoptions.hide(); @@ -496,7 +510,7 @@ function pmpro_userfields_prep_click_events() { let group_profile = jQuery(this).find('select[name=pmpro_userfields_group_profile]').val(); let group_description = jQuery(this).find('textarea[name=pmpro_userfields_group_description]').val(); - // Get level ids. + // Get level ids. let group_levels = []; jQuery(this).find('input[name="pmpro_userfields_group_membership[]"]:checked').each(function () { group_levels.push(parseInt(jQuery(this).attr('id').replace('pmpro_userfields_group_membership_', ''))); @@ -519,7 +533,7 @@ function pmpro_userfields_prep_click_events() { let field_max_file_size = jQuery(this).find('input[name=pmpro_userfields_field_max_file_size]').val(); let field_default = jQuery(this).find('input[name=pmpro_userfields_field_default]').val(); - // Get level ids. + // Get level ids. let field_levels = []; jQuery(this).find('input[name="pmpro_userfields_field_levels[]"]:checked').each(function () { field_levels.push(parseInt(jQuery(this).attr('id').replace('pmpro_userfields_field_levels_', ''))); diff --git a/paid-memberships-pro.php b/paid-memberships-pro.php index 38efe602f..59c5ba4a3 100644 --- a/paid-memberships-pro.php +++ b/paid-memberships-pro.php @@ -46,6 +46,7 @@ require_once( PMPRO_DIR . '/classes/class.memberorder.php' ); // class to process and save orders require_once( PMPRO_DIR . '/classes/class.pmproemail.php' ); // setup and filter emails sent by PMPro require_once( PMPRO_DIR . '/classes/class-pmpro-field.php' ); +require_once( PMPRO_DIR . '/classes/class-pmpro-field-group.php' ); require_once( PMPRO_DIR . '/classes/class-pmpro-levels.php' ); require_once( PMPRO_DIR . '/classes/class-pmpro-subscription.php' ); require_once( PMPRO_DIR . '/classes/class-pmpro-admin-activity-email.php' ); // setup the admin activity email diff --git a/scheduled/crons.php b/scheduled/crons.php index ff3720787..6457927c6 100644 --- a/scheduled/crons.php +++ b/scheduled/crons.php @@ -377,3 +377,29 @@ function pmpro_cron_recurring_payment_reminders() { $previous_days = $days; } } + +/** + * Delete old files in wp-content/uploads/pmpro-register-helper/tmp every day. + */ +function pmpro_cron_delete_tmp() { + $upload_dir = wp_upload_dir(); + $pmprorh_dir = $upload_dir['basedir'] . "/paid-memberships-pro/tmp/"; + + if(file_exists($pmprorh_dir) && $handle = opendir($pmprorh_dir)) + { + while(false !== ($file = readdir($handle))) + { + $file = $pmprorh_dir . $file; + $filelastmodified = filemtime($file); + if(is_file($file) && (time() - $filelastmodified) > 3600) + { + unlink($file); + } + } + + closedir($handle); + } + + exit; +} +add_action( 'pmpro_cron_delete_tmp', 'pmpro_cron_delete_tmp' ); diff --git a/shortcodes/pmpro_member.php b/shortcodes/pmpro_member.php index 07ce3be76..a9c9ea3f7 100644 --- a/shortcodes/pmpro_member.php +++ b/shortcodes/pmpro_member.php @@ -185,21 +185,12 @@ function pmpro_member_shortcode( $atts, $content = null, $shortcode_tag = '' ) { } } - // Check for files to reformat them. - if ( is_array( $r ) && ! empty( $r['fullurl'] ) ) { - $file_field = pmpro_get_user_field( $field ); - if ( ! empty( $file_field ) ) { - $file_field->file = $r; - $file_field->readonly = true; - $r = $file_field->displayValue( $r['fullurl'], false ); // False to not echo. - } else { - $r = '' . esc_html( basename($r['fullurl'] ) ) . ''; - } + // If this is a user field, get the display value. + $user_field = PMPro_Field_Group::get_field( $field ); + if ( ! empty( $user_field ) ) { + $r = $user_field->displayValue( $r, false ); } - // If this is a user field with an associative array of options, get the label(s) for the value(s). - $r = pmpro_get_label_for_user_field_value( $field, $r ); - // Check for arrays to reformat them. if ( is_array( $r ) ) { $r = implode( ', ', $r );