gocodebox/lifterlms-rest

View on GitHub
includes/server/class-llms-rest-sections-controller.php

Summary

Maintainability
C
1 day
Test Coverage
A
96%
<?php
/**
 * REST Sections Controller
 *
 * @package LifterLMS_REST/Classes/Controllers
 *
 * @since 1.0.0-beta.1
 * @version 1.0.0-beta.27
 */

defined( 'ABSPATH' ) || exit;


/**
 * LLMS_REST_Sections_Controller class.
 *
 * @since 1.0.0-beta.1
 * @since 1.0.0-beta.7 `prepare_objects_query()` renamed to `prepare_collection_query_args()`.
 *                     Fix the way we get the section's parent course object when building the resource links.
 * @since 1.0.0-beta.9 Removed `create_llms_post()` and `get_object()` methods, now abstracted in `LLMS_REST_Posts_Controller` class.
 * @since 1.0.0-beta.12 Updated `$this->prepare_collection_query_args()` to reflect changes in the parent class.
 * @since 1.0.0-beta.14 Update `prepare_links()` to accept a second parameter, `WP_REST_Request`.
 */
class LLMS_REST_Sections_Controller extends LLMS_REST_Posts_Controller {

    /**
     * Route base.
     *
     * @var string
     */
    protected $rest_base = 'sections';

    /**
     * Post type.
     *
     * @var string
     */
    protected $post_type = 'section';

    /**
     * Parent id.
     *
     * @var int
     */
    protected $parent_id;

    /**
     * Schema properties available for ordering the collection.
     *
     * @var string[]
     */
    protected $orderby_properties = array(
        'id',
        'title',
        'date_created',
        'date_updated',
        'order',
        'relevance',
    );

    /**
     * Lessons controller class.
     *
     * @var string
     */
    protected $content_controller_class;

    /**
     * Lessons controller.
     *
     * @var LLMS_REST_Lessons_Controller
     */
    protected $content_controller;

    /**
     * Constructor.
     *
     * @since 1.0.0-beta.1
     * @since 1.0.0-beta.27 Call parent constructor.
     *
     * @param string $content_controller_class Optional. The class name of the content controller. Default 'LLMS_REST_Lessons_Controller'.
     * @return void
     */
    public function __construct( $content_controller_class = 'LLMS_REST_Lessons_Controller' ) {

        parent::__construct();

        $this->collection_params        = $this->build_collection_params();
        $this->content_controller_class = $content_controller_class;

        if ( $this->content_controller_class && class_exists( $this->content_controller_class ) ) {
            $this->content_controller = new $this->content_controller_class();
            $this->content_controller->set_collection_params( $this->get_content_collection_params() );
        }

    }

    /**
     * Register routes.
     *
     * @since 1.0.0-beta.1
     *
     * @return void
     */
    public function register_routes() {

        parent::register_routes();

        if ( isset( $this->content_controller ) ) {
            register_rest_route(
                $this->namespace,
                '/' . $this->rest_base . '/(?P<id>[\d]+)/content',
                array(
                    'args'   => array(
                        'id' => array(
                            // translators: %1$s the post type name.
                            'description' => sprintf( __( 'Unique %1$s Identifier. The WordPress Post ID', 'lifterlms' ), $this->post_type ),
                            'type'        => 'integer',
                        ),
                    ),
                    array(
                        'methods'             => WP_REST_Server::READABLE,
                        'callback'            => array( $this, 'get_content_items' ),
                        'permission_callback' => array( $this->content_controller, 'get_items_permissions_check' ),
                        'args'                => $this->content_controller->get_collection_params(),
                    ),
                    'schema' => array( $this->content_controller, 'get_public_item_schema' ),
                )
            );
        }
    }

    /**
     * Retrieves an array of arguments for the delete endpoint.
     *
     * @since 1.0.0-beta.1
     *
     * @return array Delete endpoint arguments.
     */
    public function get_delete_item_args() {
        return array();
    }

    /**
     * Whether the delete should be forced.
     *
     * @since 1.0.0-beta.1
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return bool True if the delete should be forced, false otherwise.
     */
    protected function is_delete_forced( $request ) {
        return true;
    }

    /**
     * Whether the trash is supported.
     *
     * @since 1.0.0-beta.1
     *
     * @return bool True if the trash is supported, false otherwise.
     */
    protected function is_trash_supported() {
        return false;
    }

    /**
     * Set parent id.
     *
     * @since 1.0.0-beta.1
     *
     * @param int $parent_id Course parent id.
     * @return void
     */
    public function set_parent_id( $parent_id ) {
        $this->parent_id = $parent_id;
    }

    /**
     * Get parent id.
     *
     * @since 1.0.0-beta.1
     *
     * @return int|null Course parent id. Null if not set.
     */
    public function get_parent_id() {
        return isset( $this->parent_id ) ? $this->parent_id : null;
    }

    /**
     * Prepares a single post for create or update.
     *
     * @since 1.0.0-beta.1
     *
     * @param WP_REST_Request $request Request object.
     * @return array|WP_Error Array of llms post args or WP_Error.
     */
    protected function prepare_item_for_database( $request ) {

        $prepared_item = parent::prepare_item_for_database( $request );

        $schema = $this->get_item_schema();

        // LLMS Section parent id.
        if ( ! empty( $schema['properties']['parent_id'] ) && isset( $request['parent_id'] ) ) {

            $parent_course = llms_get_post( $request['parent_id'] );

            if ( ! $parent_course || ! is_a( $parent_course, 'LLMS_Course' ) ) {
                return llms_rest_bad_request_error( __( 'Invalid parent_id param. It must be a valid Course ID.', 'lifterlms' ) );
            }

            $prepared_item['parent_course'] = $request['parent_id'];
        }

        // LLMS Section order.
        if ( ! empty( $schema['properties']['order'] ) && isset( $request['order'] ) ) {

            // order must be > 0. It's sanitized as absint so it cannot come as negative value.
            if ( 0 === $request['order'] ) {
                return llms_rest_bad_request_error( __( 'Invalid order param. It must be greater than 0.', 'lifterlms' ) );
            }

            $prepared_item['order'] = $request['order'];
        }

        return $prepared_item;

    }

    /**
     * Get the Section's schema base, conforming to JSON Schema.
     *
     * @since 1.0.0-beta.27
     *
     * @return array
     */
    public function get_item_schema_base() {

        $schema = parent::get_item_schema_base();

        // Section's title.
        $schema['properties']['title']['description'] = __( 'Section Title', 'lifterlms' );

        // Section's parent id.
        $schema['properties']['parent_id'] = array(
            'description' => __( 'WordPress post ID of the parent item. Must be a Course ID.', 'lifterlms' ),
            'type'        => 'integer',
            'context'     => array( 'view', 'edit' ),
            'arg_options' => array(
                'sanitize_callback' => 'absint',
            ),
            'required'    => true,
        );

        // Section order.
        $schema['properties']['order'] = array(
            'description' => __( 'Order of the section within the course.', 'lifterlms' ),
            'type'        => 'integer',
            'default'     => 1,
            'context'     => array( 'view', 'edit' ),
            'arg_options' => array(
                'sanitize_callback' => 'absint',
            ),
            'required'    => true,
        );

        // remove unnecessary properties.
        $unnecessary_properties = array(
            'permalink',
            'slug',
            'content',
            'menu_order',
            'excerpt',
            'featured_media',
            'status',
            'password',
            'featured_media',
            'comment_status',
            'ping_status',
        );

        foreach ( $unnecessary_properties as $unnecessary_property ) {
            unset( $schema['properties'][ $unnecessary_property ] );
        }

        return $schema;

    }

    /**
     * Retrieves the query params for the objects collection.
     *
     * @since 1.0.0-beta.1
     *
     * @return array The Enrollments collection parameters.
     */
    public function get_collection_params() {
        return $this->collection_params;
    }

    /**
     * Retrieves the query params for the objects collection.
     *
     * @since 1.0.0-beta.1
     *
     * @param array $collection_params The Enrollments collection parameters to be set.
     * @return void
     */
    public function set_collection_params( $collection_params ) {
        $this->collection_params = $collection_params;
    }

    /**
     * Retrieves the query params for the objects collection.
     *
     * @since 1.0.0-beta.1
     *
     * @return array Collection parameters.
     */
    public function build_collection_params() {

        $query_params = parent::get_collection_params();

        $query_params['parent'] = array(
            'description'       => __( 'Filter sections by the parent post (course) ID.', 'lifterlms' ),
            'type'              => 'integer',
            'validate_callback' => 'rest_validate_request_arg',
        );

        return $query_params;
    }

    /**
     * Prepare a single object output for response.
     *
     * @since 1.0.0-beta.1
     * @since 1.0.0-beta.23 Replaced call to deprecated `LLMS_Section::get_parent_course()` with `LLMS_Section::get( 'parent_course' )`.
     *
     * @param LLMS_Section    $section Section object.
     * @param WP_REST_Request $request Full details about the request.
     * @return array
     */
    protected function prepare_object_for_response( $section, $request ) {

        $data = parent::prepare_object_for_response( $section, $request );

        // Parent course.
        $data['parent_id'] = $section->get( 'parent_course' );

        // Order.
        $data['order'] = $section->get( 'order' );

        return $data;

    }

    /**
     * Format query arguments to retrieve a collection of objects.
     *
     * @since 1.0.0-beta.7
     * @since 1.0.0-beta.12 Updated to reflect changes in the parent class.
     * @since 1.0.0-beta.18 Correctly return errors.
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return array|WP_Error
     */
    protected function prepare_collection_query_args( $request ) {

        $query_args = parent::prepare_collection_query_args( $request );
        if ( is_wp_error( $query_args ) ) {
            return $query_args;
        }

        // Orderby 'order' requires a meta query.
        if ( isset( $query_args['orderby'] ) && 'order' === $query_args['orderby'] ) {
            $query_args = array_merge(
                $query_args,
                array(
                    'meta_key' => '_llms_order',
                    'orderby'  => 'meta_value_num',
                )
            );
        }

        if ( isset( $this->parent_id ) ) {
            $parent_id = $this->parent_id;
        } elseif ( ! empty( $request['parent'] ) && $request['parent'] > 1 ) {
            $parent_id = $request['parent'];
        }

        // Filter by parent.
        if ( ! empty( $parent_id ) ) {
            $query_args = array_merge(
                $query_args,
                array(
                    'meta_query' => array(
                        array(
                            'key'     => '_llms_parent_course',
                            'value'   => $parent_id,
                            'compare' => '=',
                        ),
                    ),
                )
            );
        }

        return $query_args;
    }

    /**
     * Prepare links for the request.
     *
     * @since 1.0.0-beta.1
     * @since 1.0.0-beta.7 Fix the way we get the section's parent course object.
     * @since 1.0.0-beta.14 Added `$request` parameter.
     * @since 1.0.0-beta.23 Replaced call to deprecated `LLMS_Section::get_parent_course()` with `LLMS_Section::get( 'parent_course' )`.
     *
     * @param LLMS_Section    $section LLMS Section.
     * @param WP_REST_Request $request Request object.
     * @return array Links for the given object.
     */
    protected function prepare_links( $section, $request ) {

        $links            = parent::prepare_links( $section, $request );
        $parent_course_id = $section->get( 'parent_course' );

        // If the section has no course parent return earlier.
        if ( ! $parent_course_id ) {
            return $links;
        }

        $parent_course = llms_get_post( $parent_course_id );
        if ( ! is_a( $parent_course, 'LLMS_Course' ) ) {
            return $links;
        }

        $section_id    = $section->get( 'id' );
        $section_links = array();

        // Parent (course).
        $section_links['parent'] = array(
            'type' => 'course',
            'href' => rest_url( sprintf( '/%s/%s/%d', 'llms/v1', 'courses', $parent_course_id ) ),
        );

        // Siblings.
        $section_links['siblings'] = array(
            'href' => add_query_arg(
                'parent',
                $parent_course_id,
                $links['collection']['href']
            ),
        );

        // Next.
        $next_section = $section->get_next();
        if ( $next_section ) {
            $section_links['next'] = array(
                'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $next_section->get( 'id' ) ) ),
            );
        }

        // Previous.
        $previous_section = $section->get_previous();
        if ( $previous_section ) {
            $section_links['previous'] = array(
                'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $previous_section->get( 'id' ) ) ),
            );
        }

        return array_merge( $links, $section_links );
    }

    /**
     * Checks if a Section can be read
     *
     * @since 1.0.0-beta.1
     *
     * @param LLMS_Section $section The Section oject.
     * @return bool Whether the post can be read.
     */
    protected function check_read_permission( $section ) {

        /**
         * As of now, sections of password protected courses cannot be read
         */
        if ( post_password_required( $section->get( 'parent_course' ) ) ) {
            return false;
        }

        return parent::check_read_permission( $section );

    }

    /**
     * Retrieves the content controller.
     *
     * @since 1.0.0-beta.1
     *
     * @return  LLMS_REST_Lessons_Controller|null
     */
    public function get_content_controller() {
        return $this->content_controller;
    }

    /**
     * Retrieves the query params for the lessons objects collection.
     *
     * @since 1.0.0-beta.1
     *
     * @return array Collection parameters.
     */
    public function get_content_collection_params() {

        $query_params = $this->content_controller->get_collection_params();

        $query_params['orderby']['enum']    = array(
            'order',
            'id',
            'title',
        );
        $query_params['orderby']['default'] = 'order';

        unset( $query_params['parent'] );

        return $query_params;

    }

    /**
     * Get a collection of content items (lessons).
     *
     * @since 1.0.0-beta.1
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return WP_Error|WP_REST_Response
     */
    public function get_content_items( $request ) {

        $this->content_controller->set_parent_id( $request['id'] );
        $result = $this->content_controller->get_items( $request );

        // Specs require 404 when no section's lessons are found.
        if ( ! is_wp_error( $result ) && empty( $result->data ) ) {
            return llms_rest_not_found_error();
        }

        return $result;

    }

}