gocodebox/lifterlms

View on GitHub
includes/admin/class.llms.admin.import.php

Summary

Maintainability
A
3 hrs
Test Coverage
A
100%
<?php
/**
 * Admin Import Screen and form submission handling
 *
 * @package LifterLMS/Admin/Classes
 *
 * @since 3.3.0
 * @version 6.0.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Admin Import Screen and form submission handling class
 *
 * @since 3.3.0
 * @since 3.30.1 Explicitly include template functions during imports.
 * @since 3.35.0 Initialize at `admin_init` instead of `init`.
 *               Import template from the admin views directory instead of the frontend templates directory.
 *               Improve error handling.
 * @since 3.36.3 Fixed a typo where "$generator" was spelled "$generater".
 * @since 3.37.3 Don't unslash uploaded file `tmp_name`.
 * @since 6.0.0 Removed the deprecated `LLMS_Admin_Import::localize_stat()` method.
 */
class LLMS_Admin_Import {

    /**
     * Constructor
     *
     * @since 3.3.0
     * @since 3.35.0 Initialize at `admin_init` instead of `init`.
     * @since 4.8.0 Added hooks handling cloud imports and outputting WP help tabs.
     *
     * @return void
     */
    public function __construct() {

        add_action( 'admin_init', array( $this, 'cloud_import' ) );
        add_action( 'admin_init', array( $this, 'upload_import' ) );

        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );

        add_action( 'current_screen', array( $this, 'add_help_tabs' ) );

    }

    /**
     * Add WP_Screen help tabs
     *
     * @since 4.8.0
     *
     * @return WP_Screen|boolean Returns the WP_Screen on success or false if called outside of the intended screen context.
     */
    public function add_help_tabs() {

        $screen = $this->get_screen();
        if ( ! $screen ) {
            return false;
        }

        $screen->add_help_tab(
            array(
                'id'      => 'llms_import_overview',
                'title'   => __( 'Overview', 'lifterlms' ),
                'content' => $this->get_view( 'help-tab-overview' ),
            )
        );

        $screen->set_help_sidebar( $this->get_view( 'help-sidebar' ) );

        return $screen;

    }

    /**
     * Handle form submission of a cloud import file
     *
     * @since 4.8.0
     *
     * @return WP_Error|boolean Returns `false` for nonce or user permission errors, `true` on success, or an error object.
     */
    public function cloud_import() {

        if ( ! llms_verify_nonce( 'llms_cloud_importer_nonce', 'llms-cloud-importer' ) || ! current_user_can( 'manage_lifterlms' ) ) {
            return false;
        }

        $course_id = llms_filter_input( INPUT_POST, 'llms_cloud_import_course_id', FILTER_SANITIZE_NUMBER_INT );
        if ( ! $course_id ) {
            return $this->show_error( new WP_Error( 'llms-cloud-import-missing-id', __( 'Error: Missing course ID.', 'lifterlms' ) ) );
        }

        $res = LLMS_Export_API::get( array( $course_id ) );
        if ( is_wp_error( $res ) ) {
            return $this->show_error( $res );
        }

        return $this->handle_generation( $res );

    }

    /**
     * Enqueue static assets used on the screen
     *
     * @since 4.8.0
     *
     * @return null|boolean Returns `null` when called outside of the intended screen context, `true` on success, or `false` on error.
     */
    public function enqueue() {

        if ( ! $this->get_screen() ) {
            return null;
        }

        return llms()->assets->enqueue_style( 'llms-admin-importer' );

    }

    /**
     * Convert an array of generated content IDs to a list of anchor tags to edit the generated content
     *
     * @since 4.7.0
     *
     * @param int[]  $ids  Array of object IDs. Either WP_Post IDs or WP_User IDs.
     * @param string $type Object's type. Either "post" or "user".
     * @return string A comma-separated list of HTML anchor tags.
     */
    protected function get_generated_content_list( $ids, $type ) {

        $list = array();
        foreach ( $ids as $id ) {

            if ( 'post' === $type ) {
                $link = get_edit_post_link( $id );
                $text = get_the_title( $id );
            } elseif ( 'user' === $type ) {
                $link = get_edit_user_link( $id );
                $text = get_user_by( 'ID', $id )->display_name;
            }

            $list[] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $link ), $text );

        }

        return implode( ', ', $list );

    }

    /**
     * Retrieve an instance of the WP_Screen for the import screen
     *
     * @since 4.8.0
     *
     * @return WP_Screen|boolean Returns a `WP_Screen` object when on the import screen, otherwise returns `false`.
     */
    protected function get_screen() {

        $screen = get_current_screen();
        if ( $screen instanceof WP_Screen && 'lifterlms_page_llms-import' === $screen->id ) {
            return $screen;
        }

        return false;

    }

    /**
     * Retrieves the HTML of a view from the views/import directory.
     *
     * @since 4.8.0
     *
     * @param string $file The file basename of the view to retrieve.
     * @return string The HTML content of the view.
     */
    protected function get_view( $file ) {

        ob_start();
        include 'views/import/' . $file . '.php';
        return ob_get_clean();

    }

    /**
     * Retrieves a "Success" message providing information about the imported content.
     *
     * @since 4.7.0
     *
     * @param LLMS_Generator $generator Generator instance.
     * @return string
     */
    protected function get_success_message( $generator ) {

        $msg  = '<strong>' . __( 'Import Successful!', 'lifterlms' ) . '</strong><br>';
        $msg .= '<ul>';

        $generated = $generator->get_generated_content();

        if ( ! empty( $generated['course'] ) ) {
            // Translators: %s = comma-separated list of anchors to the imported courses.
            $msg .= '<li>' . sprintf( __( 'Imported courses: %s', 'lifterlms' ), $this->get_generated_content_list( $generated['course'], 'post' ) ) . '</li>';
        }
        if ( ! empty( $generated['user'] ) ) {
            // Translators: %s = comma-separated list of anchors to the imported users.
            $msg .= '<li>' . sprintf( __( 'Imported users: %s', 'lifterlms' ), $this->get_generated_content_list( $generated['user'], 'user' ) ) . '</li>';
        }

        $msg .= '</ul>';

        return $msg;

    }

    /**
     * Instantiate and generate raw data via LLMS_Generator
     *
     * @since 4.8.0
     *
     * @param string|array $raw A JSON string or array or raw data which can be parsed by an LLMS_Generator instance.
     * @return WP_Error|boolean On success, returns true or an error object on failure.
     */
    protected function handle_generation( $raw ) {

        $generator = new LLMS_Generator( $raw );
        if ( is_wp_error( $generator->set_generator() ) ) {
            return $this->show_error( $generator->error );
        }

        $generator->generate();
        if ( $generator->is_error() ) {
            return $this->show_error( $generator->error );
        }

        LLMS_Admin_Notices::flash_notice( $this->get_success_message( $generator ), 'success' );
        return true;

    }

    /**
     * Handle HTML output on the screen
     *
     * @since 3.3.0
     * @since 3.35.0 Import template from the admin views directory instead of the frontend templates directory.
     * @since 4.7.0 Moved logic for generating success message into its own method.
     *
     * @return void
     */
    public static function output() {
        include 'views/import.php';
    }

    /**
     * Output an admin notice from a WP_Error object
     *
     * @since 4.8.0
     *
     * @param WP_Error $error WP_Error object.
     * @return WP_Error Returns the same error passed into the object.
     */
    protected function show_error( $error ) {
        LLMS_Admin_Notices::flash_notice( $error->get_error_message(), 'error' );
        return $error;
    }

    /**
     * Handle form submission
     *
     * @since 3.3.0
     * @since 3.30.1 Explicitly include template functions.
     * @since 3.35.0 Validate nonce and user permissions before processing import data.
     *               Moved statistic localization into its own function.
     *               Updated return signature.
     * @since 3.36.3 Fixed a typo where "$generator" was spelled "$generater".
     * @since 3.37.3 Don't unslash uploaded file `tmp_name`.
     * @since 4.8.0 Use helper methods `show_error()` and `handle_generation()`.
     *
     * @return boolean|WP_Error false for nonce or permission errors, WP_Error when an error is encountered, true on success.
     */
    public function upload_import() {

        if ( ! llms_verify_nonce( 'llms_importer_nonce', 'llms-importer' ) || ! current_user_can( 'manage_lifterlms' ) || empty( $_FILES['llms_import'] ) ) {
            return false;
        }

        // Fixes an issue where hooks are loaded in an unexpected order causing template functions required to parse an import aren't available.
        llms()->include_template_functions();

        // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
        // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
        $validate = $this->validate_upload( $_FILES['llms_import'] );

        // File upload error.
        if ( is_wp_error( $validate ) ) {
            return $this->show_error( $validate );
        }

        $raw = ! empty( $_FILES['llms_import']['tmp_name'] ) ? file_get_contents( sanitize_text_field( $_FILES['llms_import']['tmp_name'] ) ) : array(); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
        // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
        // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitizedr

        return $this->handle_generation( $raw );

    }

    /**
     * Validate the uploaded file
     *
     * @since 3.3.0
     * @since 3.35.0 Fix undefined variable error.
     *
     * @link https://www.php.net/manual/en/features.file-upload.errors.php
     *
     * @param array $file  array of file data.
     * @return WP_Error|true
     */
    private function validate_upload( $file ) {

        if ( ! empty( $file['error'] ) ) {

            switch ( $file['error'] ) {

                case UPLOAD_ERR_INI_SIZE:
                    $msg = __( 'The uploaded file exceeds the upload_max_filesize directive in php.ini.', 'lifterlms' );
                    break;

                case UPLOAD_ERR_FORM_SIZE:
                    $msg = __( 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.', 'lifterlms' );
                    break;

                case UPLOAD_ERR_PARTIAL:
                    $msg = __( 'The uploaded file was only partially uploaded.', 'lifterlms' );
                    break;

                case UPLOAD_ERR_NO_FILE:
                    $msg = __( 'No file was uploaded.', 'lifterlms' );
                    break;

                case UPLOAD_ERR_NO_TMP_DIR:
                    $msg = __( 'Missing a temporary folder.', 'lifterlms' );
                    break;

                case UPLOAD_ERR_CANT_WRITE:
                    $msg = __( 'Failed to write file to disk.', 'lifterlms' );
                    break;

                case UPLOAD_ERR_EXTENSION:
                    $msg = __( 'File upload stopped by extension.', 'lifterlms' );
                    break;

                default:
                    $msg = __( 'Unknown upload error.', 'lifterlms' );

            }
        } else {

            $info = pathinfo( $file['name'] );

            if ( 'json' !== strtolower( $info['extension'] ) ) {
                $msg = __( 'Only valid JSON files can be imported.', 'lifterlms' );
            }
        }

        if ( ! empty( $msg ) ) {
            return new WP_Error( 'llms_import_file_error', $msg );
        }

        return true;

    }

}

return new LLMS_Admin_Import();