gocodebox/lifterlms

View on GitHub
includes/admin/class.llms.admin.user.custom.fields.php

Summary

Maintainability
A
3 hrs
Test Coverage
F
0%
<?php
/**
 * LLMS_Admin_User_Custom_Fields class file
 *
 * @package LifterLMS/Admin/Classes
 *
 * @since 2.7.0
 * @version 5.9.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Add custom user fields to user admin panel screens
 *
 * Applies to edit-user.php, user-new.php, & profile.php.
 *
 * @since 2.7.0
 * @since 3.35.0 Sanitize input data.
 * @since 3.37.15 Fix error encountered when errors encountered validating custom fields.
 */
class LLMS_Admin_User_Custom_Fields {

    private $fields = array();

    /**
     * Constructor
     *
     * @since 2.7.0
     * @since 3.13.0 Unknown.
     * @since 4.14.0 Add personal options hook.
     * @since 5.0.0 Custom fields (legacy), are now printed with priority 11 instead of 10.
     * @return void
     */
    public function __construct() {

        // Output custom fields on edit screens.
        $field_actions = array(
            'show_user_profile',
            'edit_user_profile',
            'user_new_form',
        );

        foreach ( $field_actions as $action ) {
            add_action( $action, array( $this, 'output_custom_fields' ), 11, 1 );
            add_action( $action, array( $this, 'output_instructors_assistant_fields' ), 10, 1 );
        }

        // Allow errors to be output before saving field data.
        // Save the data if no errors are encountered.
        add_action( 'user_profile_update_errors', array( $this, 'add_errors' ), 10, 3 );

        // Save data when a new user is created.
        add_action( 'edit_user_created_user', array( $this, 'save' ) );

        // Add personal options.
        add_action( 'personal_options', array( $this, 'output_personal_options' ) );

    }


    /**
     * Validate custom fields
     *
     * During updates will save data, creation is saved during a different action.
     *
     * @since 2.7.0
     * @since 3.13.0 Unknown.
     * @since 3.37.15 Correctly pass `$user` to `$this->save()`.
     *
     * @param obj     $errors Instance of WP_Error, passed by reference.
     * @param bool    $update `true` if updating a profile, `false` if a new user.
     * @param WP_User $user   Instance of WP_User for the user being updated.
     * @return void
     */
    public function add_errors( &$errors, $update, $user ) {

        $this->get_fields();

        $error = $this->validate_fields( $user );

        if ( $error ) {

            $errors->add( '', $error, '' );

            if ( $update ) {
                $this->save( $user );
            }

            // Don't save.
            remove_action( 'edit_user_created_user', array( $this, 'save' ) );

            return;

        }

        // If updating, save here since there's no other save specific admin action (that I could find).
        if ( $update ) {
            $this->save( $user );
        }

    }

    /**
     * Retrieve an associative array of custom fields and custom field data
     *
     * @since 2.7.0
     * @since 3.13.0 Unknown.
     * @since 5.0.0 Removed LLMS core fields and deprecate the filter usage.
     *
     * @return array
     */
    public function get_fields() {

        $this->fields = apply_filters_deprecated(
            'lifterlms_get_user_custom_fields',
            array(
                array(),
            ),
            '5.0.0',
            'llms_admin_profile_fields'
        );

        return $this->fields;

    }

    /**
     * Load usermeta data into the array of fields retrieved from $this->get_fields
     *
     * Meta data is added to the array under the key "value" for each field.
     *
     * If no data is found for a particular field the value is still added as an empty string.
     *
     * @since 2.7.0
     *
     * @param WP_User|int $user Instance of WP_User or WP User ID
     * @return array
     */
    public function get_fields_with_data( $user ) {

        if ( is_numeric( $user ) ) {
            $user = new WP_User( $user );
        }

        $this->get_fields();

        foreach ( $this->fields as $field => $data ) {

            $this->fields[ $field ]['value'] = apply_filters( 'lifterlms_get_user_custom_field_value_' . $field, $user->get( $field ), $user, $field );

        }

        return $this->fields;

    }

    /**
     * Output custom field data fields as HTML inputs
     *
     * @since 2.7.0
     * @since 3.24.0 Unknown.
     * @since 5.0.0 Do not include user-edit template if no fields to show.
     *
     * @param WP_User|int $user Instance of WP_User or WP User ID.
     * @return void
     */
    public function output_custom_fields( $user ) {

        if ( is_numeric( $user ) || is_a( $user, 'WP_User' ) ) {
            $this->get_fields_with_data( $user );
        } else {
            $this->get_fields();
        }

        if ( empty( $this->fields ) ) {
            return;
        }

        llms_get_template(
            'admin/user-edit.php',
            array(
                'section_title' => __( 'LifterLMS Profile (legacy fields)', 'lifterlms' ),
                'fields'        => $this->fields,
            )
        );

    }

    /**
     * Output personal option fields
     *
     * Currently adds a single option row for controlling auto-save behavior on the course builder.
     *
     * @since 4.14.0
     *
     * @param WP_User $user Viewed user object.
     * @return void
     */
    public function output_personal_options( $user ) {

        if ( ! user_can( $user, 'edit_courses' ) ) {
            return;
        }

        $autosave = get_user_option( 'llms_builder_autosave', $user->ID );
        $autosave = empty( $autosave ) ? 'no' : $autosave;

        ?>
        <tr class="llms-builder-autosave llms-builder-autosave-wrap">
            <th scope="row"><?php _e( 'Course Builder Autosave', 'lifterlms' ); ?></th>
            <td>
                <label for="llms_builder_autosave">
                    <input name="llms_builder_autosave" type="checkbox" id="llms_builder_autosave" value="yes"<?php checked( 'yes', $autosave ); ?>>
                    <?php _e( 'Automatically save changes when using the course builder', 'lifterlms' ); ?>
                </label><br>
            </td>
        </tr>
        <?php

    }

    /**
     * Add instructor parent fields for use when creating instructor's assistants
     *
     * @since 3.13.0
     * @since 3.23.0 Unknown.
     * @since 3.37.15 Use strict comparisons.
     *
     * @param WP_User|int $user Instance of WP_User or WP User ID
     * @return void
     */
    public function output_instructors_assistant_fields( $user ) {

        if ( is_numeric( $user ) || is_a( $user, 'WP_User' ) ) {
            $instructor = llms_get_instructor( $user );
            $selected   = $instructor->get( 'parent_instructors' );
            if ( empty( $selected ) && ! is_array( $selected ) ) {
                $selected = array();
            }
        } else {
            $selected = array( get_current_user_id() );
        }

        $selected = array_map( 'absint', $selected );

        // Only let admins & lms managers select the parent for an instructor's assistant.
        if ( current_user_can( 'manage_lifterlms' ) ) {

            $users = get_users(
                array(
                    'role__in' => array( 'administrator', 'lms_manager', 'instructor' ),
                )
            );
            ?>
            <table class="form-table" id="llms-parent-instructors-table" style="display:none;">
                <tr class="form-field">
                    <th scope="row"><label for="llms-parent-instructors"><?php _e( 'Parent Instructor(s)', 'lifterlms' ); ?></label></th>
                    <td>
                        <select class="regular-text" id="llms-parent-instructors" name="llms_parent_instructors[]" multiple="multiple">
                            <?php foreach ( $users as $user ) : ?>
                                <option value="<?php echo $user->ID; ?>"<?php selected( in_array( $user->ID, $selected, true ) ); ?>>
                                    <?php echo $user->display_name; ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </td>
                </tr>
            </table>
            <?php

            add_action( 'admin_print_footer_scripts', array( $this, 'output_instructors_assistant_scripts' ) );

        } elseif ( 'add-new-user' === $user ) {
            /**
             * This will be the case for Instructors only:
             *
             * Show a hidden field with the current user's info
             *
             * When saving it will only save if the created user's role is instructor's assistant.
             */
            echo '<input type="hidden" name="llms_parent_instructors[]" value="' . get_current_user_id() . '">';
        }

    }

    /**
     * Output JS to handle user interaction with the instructor's parent field
     *
     * Display custom field ONLY when creating/editing an instructor's assistant.
     *
     * @since 3.13.0
     *
     * @return void
     */
    public function output_instructors_assistant_scripts() {
        ?>
        <script>
            ( function( $ ) {
                var $role = $( '#role' ),
                    $parent = $( '#llms-parent-instructors-table' );
                $role.closest( '.form-table' ).after( $parent );
                $role.on( 'change', function() {
                    if ( 'instructors_assistant' === $( this ).val() ) {
                        $parent.show();
                    } else {
                        $parent.hide();
                    }
                } ).trigger( 'change' );
            } )( jQuery );
        </script>
        <?php
    }

    /**
     * Save custom field data for a user
     *
     * @since 3.13.0
     * @since 3.35.0 Sanitize input data.
     * @since 3.37.15 Use strict comparisons.
     * @since 4.14.0 Save builder autosave personal options.
     * @since 5.9.0 Stop using deprecated `FILTER_SANITIZE_STRING`.
     *
     * @param WP_User|int|obj $user User object or id.
     * @return void
     */
    public function save( $user ) {

        if ( is_numeric( $user ) ) {

            // Numeric ID is passed in during creations.
            $user   = new WP_User( $user );
            $action = 'create';

        } elseif ( isset( $user->ID ) ) {

            // An object that's not a WP_User gets passed in during updates.
            $user   = new WP_User( $user->ID );
            $action = 'update';
        }

        // Saves custom fields.
        foreach ( $this->fields as $field => $data ) {

            $value = apply_filters( 'lifterlms_save_custom_user_field_' . $field, llms_filter_input_sanitize_string( INPUT_POST, $field ), $user, $field );
            update_user_meta( $user->ID, $field, $value );

        }

        // Save instructor assistant's parent instructor.
        if ( in_array( 'instructors_assistant', $user->roles, true ) && ! empty( $_POST['llms_parent_instructors'] ) ) { // phpcs:disable WordPress.Security.NonceVerification.Missing

            $instructor = llms_get_instructor( $user );
            $instructor->add_parent( llms_filter_input( INPUT_POST, 'llms_parent_instructors', FILTER_SANITIZE_NUMBER_INT, FILTER_REQUIRE_ARRAY ) );

        }

        // Save personal options.
        if ( user_can( $user, 'edit_courses' ) && 'create' !== $action ) {
            $autosave = empty( $_POST['llms_builder_autosave'] ) ? 'no' : 'yes';
            update_user_meta( $user->ID, 'llms_builder_autosave', $autosave );
        }

    }

    /**
     * Validate custom fields
     *
     * By default only checks for valid as core fields don't have any special validation.
     *
     * If adding custom fields, hook into the action run after required validation
     * to add special validation rules for your field.
     *
     * @since 2.7.0
     *
     * @param WP_User|int $user Instance of WP_User or WP User ID.
     * @return string|bool `false` if no validation errors or the error message (as a sttring) if validation errors occurred.
     */
    public function validate_fields( $user ) {

        // Ensure there's no missing required fields.
        foreach ( $this->fields as $field => $data ) {

            // Return an error message for empty required fields.
            if ( empty( $_POST[ $field ] ) && $data['required'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing

                return sprintf( __( 'Required field "%s" is missing.', 'lifterlms' ), $data['label'] );

            } else {

                /**
                 * Run custom validation against the field
                 *
                 * If filter function returns a truthy, validation will stop, fields will not be saved,
                 * and an error message will be displayed on screen.
                 *
                 * This should return `false` or a string which will be used as the error message.
                 *
                 * @since 2.7.0
                 *
                 * @param boolean     $error_message The error message when validation issues are encountered. Return `false` when no validation issues.
                 * @param string      $field         Field id.
                 * @param WP_User|int $user          Instance of WP_User or WP User ID.
                 */
                $error_msg = apply_filters( "lifterlms_validate_custom_user_field_{$field}", false, $field, $user );

                if ( $error_msg ) {

                    return $error_msg;

                }
            }
        }

        return false;

    }

}

return new LLMS_Admin_User_Custom_Fields();