You are here

abstract class CourseObject in Course 7.2

Same name and namespace in other branches
  1. 6 includes/course_object.core.inc \CourseObject
  2. 7 includes/CourseObject.inc \CourseObject

Parent abstract base class of all course objects.

Represents a course object in the database.

Also holds a fulfillment record if a user is given.

Hierarchy

Expanded class hierarchy of CourseObject

1 string reference to 'CourseObject'
course_entity_info in ./course.module
Implements hook_entity_info().

File

includes/CourseObject.inc, line 10

View source
abstract class CourseObject extends CourseHandler {
  protected $accessMessages = array();
  protected $readOnlyOptionsCache = array();

  /**
   * Override navigation links.
   *
   * @return array
   *   An array of navigation links. Keyed values will override matching items
   *   in Course::getNavigation().
   */
  public function overrideNavigation() {
    return array();
  }

  /**
   * Specify whether fulfillment uses asynchronous polling.
   *
   * @return bool
   *   Whether this object uses polling. Defaults to FALSE.
   */
  public function hasPolling() {
    return FALSE;
  }

  /**
   * Overrides a course outline list item.
   *
   * @param array $item
   *   A course outline list item. The structure mirrors an array element from
   *   the $items param from theme_item_list().
   */
  public function overrideOutlineListItem(&$item) {
  }

  /**
   * Access functionality for course objects.
   *
   * Possible values for $op are 'see', 'view', 'take'.
   *
   * "see" means see it in a course outline. For example, a conditional survey
   * should not be seen in the course outline. A quiz at the end of the course,
   * should show up, but the user must not be able to take it.
   *
   * "view" means view and interact with the object, but nothing would be
   * recorded. For example, accessing a quiz but not being able to submit
   * responses.
   *
   * "take" means interact with the object in a way that records data.
   *
   * Subclasses may override this functionality.
   *
   * @param string $op
   *   The operation. See above.
   * @param stdClass $account
   *   User to check.
   *
   * @return boolean
   *   If the user can perform the operation on this object.
   */
  public function access($op = 'view', $account = NULL) {
    ctools_include('plugins');
    $access = FALSE;
    if (!in_array($op, array(
      'see',
      'take',
      'view',
    ), TRUE)) {

      // If there was no node to check against, or the $op was not one of the
      // supported ones, we return access denied.
      return FALSE;
    }

    // If no user object is supplied, the access check is for the current user.
    if (empty($account)) {
      $account = $GLOBALS['user'];
    }
    if (!$this
      ->isEnabled() || !$this
      ->isVisible()) {

      // Object is disabled or hidden so it should never be visible.
      return FALSE;
    }
    switch ($op) {
      case 'see':

        // User can see this object in the outline.
        $access = TRUE;
        break;
      case 'take':
      case 'view':
        if (!$account->uid) {

          // Not logged in. Should never be accessible.
          return FALSE;
        }

        // Stock access: check for completion of previous object.
        // Get a copy of the course, so we can run setActive() without changing
        // the global course.
        $course = clone $this
          ->getCourse();
        $course
          ->setActive($this
          ->getId());
        $courseObjects = $course
          ->getObjects();

        // Deny object access to non-enrolled users or users who cannot take
        // this course.
        $result = course_access_course('take', $this
          ->getCourse()
          ->getNode(), $account);
        if (!course_enrollment_check($this
          ->getCourseNid(), $account->uid) || !$result['success']) {
          return FALSE;
        }
        else {
          if (reset($courseObjects)
            ->getId() == $this
            ->getId()) {

            // User is enrolled. Grant access if first object.
            $access = TRUE;
          }
        }
        if (!$course
          ->getPrev()) {

          // There wasn't a previous object.
          $access = TRUE;
        }
        elseif (!$course
          ->getPrev()
          ->isRequired() || $course
          ->getPrev()
          ->isSkippable()) {

          // Previous step was not required, or was skippable.
          $access = TRUE;

          // But we need to see if at least one required step was completed (or the start of the course).
          $objects = array_reverse($course
            ->getObjects());
          $check = FALSE;
          foreach ($objects as $object) {
            if (!$object
              ->isEnabled()) {

              // Do not check this object.
              // Note that hidden objects are still counted when doing
              // fulfillment checks.
              continue;
            }
            if ($check) {
              if ($object
                ->isRequired() && !$object
                ->isSkippable()) {

                // Object is required.
                if (!$object
                  ->getFulfillment($account)
                  ->isComplete()) {

                  // Found a required object that was not complete.
                  $access = FALSE;
                  break;
                }
                else {

                  // The last required object was completed.
                  $access = TRUE;
                  break;
                }
              }
            }
            if ($object
              ->getId() == $this
              ->getId()) {

              // We found the object we are trying to check access on.
              // Now we want to go backwards.
              $check = 1;
            }
          }
        }
        elseif ($course
          ->getPrev()
          ->getFulfillment($account)
          ->isComplete()) {

          // If last object was complete, and we are on the current object,
          // grant access.
          $access = TRUE;
        }
    }

    // Plugin access.
    foreach (ctools_get_plugins('course', 'course_object_access') as $key => $plugin) {
      $class = ctools_plugin_get_class($plugin, 'handler');
      $accessPlugin = new $class();
      $accessPlugin
        ->setCourseObject($this);
      $accessPlugin
        ->setType($key);

      // Run access check.
      $ret = $accessPlugin
        ->{$op}($account);
      if ($ret === FALSE) {

        // If previous access was granted, revoke it.
        $access = $ret;
      }
    }
    return $access;
  }
  public function isActive() {
    return $this
      ->getCourse()
      ->current()
      ->getId() == $this
      ->getId();
  }

  /**
   * Define configuration elements and their defaults.
   *
   * Extended classes should call parent::optionsDefinition first to get the
   * parent's configuration.
   */
  public function optionsDefinition() {
    $defaults = parent::optionsDefinition();
    $defaults += array(
      'uniqid' => NULL,
      'nid' => NULL,
      'title' => NULL,
      'enabled' => 1,
      'hidden' => 0,
      'required' => 1,
      'skippable' => 0,
      'delete' => 0,
      'delete_instance' => 0,
      'grade_include' => 0,
      'instance' => NULL,
      'data' => '',
      'plugins' => array(),
    );
    return $defaults;
  }

  /**
   * Default options form for all course objects.
   */
  public function optionsForm(&$form, &$form_state) {
    ctools_include('plugins');
    parent::optionsForm($form, $form_state);
    $config = $this
      ->getOptions();
    $form['header']['#markup'] = t("<h2>Settings for %t</h2>", array(
      '%t' => $this
        ->getTitle(),
    ));
    $form['uniqid'] = array(
      '#type' => 'value',
      '#value' => $this
        ->getId(),
    );
    $form['nid'] = array(
      '#type' => 'value',
      '#value' => $this
        ->getCourseNid(),
    );
    $form['course_tabs']['#type'] = 'vertical_tabs';
    $form['title'] = array(
      '#title' => t('Title & description'),
      '#type' => 'fieldset',
      '#group' => 'course_tabs',
      '#weight' => 0,
    );
    $form['outline'] = array(
      '#type' => 'fieldset',
      '#title' => t('Settings'),
      '#group' => 'course_tabs',
      '#weight' => 1,
    );
    $form['plugins']['access'] = array(
      '#type' => 'fieldset',
      '#title' => 'Access',
      '#group' => 'course_tabs',
      '#weight' => 4,
    );
    $form['delete'] = array(
      '#type' => 'fieldset',
      '#title' => 'Delete',
      '#group' => 'course_tabs',
      '#weight' => 5,
    );
    $form['title']['title'] = array(
      '#title' => t('Title'),
      '#type' => 'textfield',
      '#description' => t('The title of this course object as it will appear to users in the course outline.'),
      '#size' => 100,
      '#default_value' => $config['title'],
      '#group' => 'description',
      '#required' => TRUE,
    );
    $form['outline']['enabled'] = array(
      '#title' => t('Enabled'),
      '#type' => 'checkbox',
      '#description' => t('Enabled course objects will become part of the course. Uncheck this box if you are not using this course object.'),
      '#default_value' => $config['enabled'],
    );
    $form['outline']['hidden'] = array(
      '#title' => t('Visible in outline'),
      '#type' => 'checkbox',
      '#description' => t('Objects that are not visible will not be seen by the learner. Uncheck this box for course objects that you do not want the learner to see.'),
      '#default_value' => !$config['hidden'],
      '#group' => 'course',
    );
    $form['outline']['required'] = array(
      '#title' => t('Completion required'),
      '#type' => 'checkbox',
      '#description' => t('Users must complete required objects. Uncheck this box if this is an optional course object.'),
      '#default_value' => $config['required'],
    );
    $form['outline']['skippable'] = array(
      '#title' => t('Skippable'),
      '#type' => 'checkbox',
      '#default_value' => $config['skippable'],
      '#states' => array(
        'visible' => array(
          '#edit-required' => array(
            'checked' => TRUE,
          ),
        ),
      ),
      '#description' => t('Users can proceed past this object but it will still be required for course completion.'),
    );

    // Delete object
    $form['delete']['delete_button'] = array(
      '#value' => t('Delete'),
      '#weight' => 999,
      '#type' => 'submit',
      '#submit' => array(
        array(
          $this,
          'setDelete',
        ),
        array(
          $this,
          'optionsSubmit',
        ),
      ),
      '#limit_validation_errors' => array(),
    );

    // Only allow deletion of existing instances.
    if (!empty($config['instance'])) {
      $form['delete']['delete_instance'] = array(
        '#title' => t('Also delete the referenced content.'),
        '#type' => 'checkbox',
        '#default_value' => $config['delete_instance'],
        '#stats' => array(
          'visible' => array(
            '#edit-delete' => array(
              'checked' => TRUE,
            ),
          ),
        ),
        '#group' => 'delete',
      );

      // Check for multiple instances.
      if (db_query("SELECT count(coid) FROM {course_outline} WHERE module = :module AND object_type = :object_type AND instance = :instance", array(
        ':module' => $config['module'],
        ':object_type' => $config['object_type'],
        ':instance' => $config['instance'],
      ))
        ->fetchField() > 1) {
        $form['delete']['delete_instance']['#description'] = t('<span class="error"><strong>WARNING</strong></span>: multiple course objects link to this instance. Deleting the instance might break the other course objects that use it.');
      }
    }
    if ($this
      ->isGraded()) {
      $form['grading'] = array(
        '#title' => t('Grading'),
        '#type' => 'fieldset',
        '#description' => t('Settings for graded objects.'),
        '#group' => 'course_tabs',
        '#weight' => 2,
      );
      $form['grading']['grade_include'] = array(
        '#title' => t('Include in final course grade'),
        '#description' => t('Include the learner\'s result as an input into the final course grade. The final course grade is an average of all the learner\'s included results.'),
        '#default_value' => $config['grade_include'],
        '#type' => 'checkbox',
      );
    }

    // Bring in access plugin configuration.
    $form['plugins']['#tree'] = TRUE;
    $form['plugins']['access']['#title'] = t('Access');
    $form['plugins']['access']['#description'] = t('By default, all required objects appearing before this object in the course outline must be completed before the user may access this object. Conditional access allows for additional conditions to be applied.');
    $form['plugins']['access']['#type'] = 'fieldset';
    foreach (ctools_get_plugins('course', 'course_object_access') as $key => $plugin) {
      $form['plugins']['access']['#tree'] = TRUE;
      $form['plugins']['access'][$key] = array(
        '#title' => $plugin['title'],
        '#type' => 'fieldset',
        '#tree' => TRUE,
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );

      // Initialize access class.
      $class = ctools_plugin_get_class($plugin, 'handler');
      $courseAccess = new $class();
      $courseAccess
        ->setCourseObject($this);
      $courseAccess
        ->setType($key);

      // Add access plugin form to our form.
      $access_form = $access_form_state = array();
      $form['plugins']['access'][$key] += $courseAccess
        ->optionsForm($access_form, $access_form_state);
    }

    // Update settings
    $form['actions']['update'] = array(
      '#value' => t('Update'),
      '#weight' => 999,
      '#type' => 'submit',
      '#validate' => array(
        array(
          $this,
          'optionsValidate',
        ),
      ),
      '#submit' => array(
        array(
          $this,
          'optionsSubmit',
        ),
      ),
    );
  }

  /**
   * Mark this object for deletion. This is not a submit button so we set the
   * values manually.
   */
  public function setDelete(&$form, &$form_state) {
    $form_state['values']['delete'] = 1;
    $form_state['values']['delete_instance'] = !empty($form_state['input']['delete_instance']);
  }
  public function optionsValidate(&$form, &$form_state) {

    // Pass validation to plugins.
    ctools_include('plugins');
    foreach (ctools_get_plugins('course', 'course_object_access') as $key => $plugin) {
      $values =& $form_state['values']['plugins']['access'][$key];
      $class = ctools_plugin_get_class($plugin, 'handler');
      $instance = new $class($values);
      $instance
        ->setCourseObject($this);
      $instance
        ->setType($key);
      $instance
        ->optionsValidate($form['plugins']['access'][$key], $form_state['values']['plugins']['access'][$key]);
    }
  }

  /**
   * Save object configs to cache.
   */
  public function optionsSubmit(&$form, &$form_state) {
    ctools_include('plugins');
    $uniqid = $this
      ->getId();
    $nid = $this
      ->getCourseNid();

    // Start editing session.
    course_editing_start($this
      ->getCourse());

    // Flip 'visible' so it behaves like 'hidden'.
    if (isset($form_state['values']['hidden'])) {
      $form_state['values']['hidden'] = $form_state['values']['hidden'] != 1;
    }

    // Object-specific settings
    foreach ($form_state['values'] as $key => $value) {
      if (isset($form_state['values'][$key]) && !is_null($form_state['values'][$key])) {
        $_SESSION['course'][$nid]['editing'][$uniqid][$key] = $form_state['values'][$key];
      }
    }

    // Save plugin info.
    if (isset($form_state['values']['plugins'])) {
      foreach (ctools_get_plugins('course', 'course_object_access') as $key => $plugin) {
        $_SESSION['course'][$nid]['editing'][$uniqid]['plugins']['access'][$key] = $form_state['values']['plugins']['access'][$key];
      }
    }

    // Update the options.
    $this
      ->setOptions($_SESSION['course'][$nid]['editing'][$uniqid]);
  }

  /**
   * Get core options summary.
   *
   * @return array
   *   An associative array of summary keys and values.
   */
  public function getOptionsSummary() {
    $summary = parent::getOptionsSummary();

    // Get options.
    $options = $this
      ->getOptions();

    // Add course object core options to summary individually, because there are
    // options we don't want to display, and others that require special logic.
    $uniqid = $options['uniqid'];

    // Enabled.
    if ($options['enabled']) {
      $summary['enabled'] = t('Enabled');
    }
    else {
      $summary['enabled'] = '<span class="warning">' . t('Not enabled') . '</span>';
    }

    // Hidden.
    if (!$options['hidden']) {
      $summary['hidden'] = t('Visible in outline');
    }
    else {
      $summary['hidden'] = '<span class="warning">' . t('Not visible in outline') . '</span>';
    }

    // Required.
    if ($options['required']) {
      $summary['required'] = t('Completion required');
      if ($options['skippable']) {
        $summary['skippable'] = '<span class="warning">' . t('Skippable') . '</span>';
      }
    }
    else {
      $summary['required'] = '<span class="warning">' . t('Completion not required') . '</span>';
    }

    // Instance edit link.
    $editUrl = $this
      ->getEditUrl();
    if (!empty($editUrl)) {
      $text = t('Edit instance');
      $summary['instance'] = l($text, $editUrl, array(
        'external' => TRUE,
        'query' => drupal_get_destination(),
      ));
    }
    elseif ($this
      ->isTemporary()) {
      $summary['instance'] = '<span class="warning">' . t('Save course to edit object') . '</span>';
    }

    // Instance view link.
    $viewUrl = $this
      ->getViewUrl();
    if (!empty($viewUrl)) {
      $text = t('View instance');
      $summary['instance_view'] = l($text, $viewUrl, array(
        'external' => TRUE,
        'query' => drupal_get_destination(),
      ));
    }

    // Required.
    if (!empty($options['delete'])) {
      $dest = "node/{$options['nid']}/course-object/nojs/{$this->getId()}/restore";
      $text = t('Object will be removed from outline');
      $restore_text = t('Restore this object to the course outline.');
      if ($options['delete_instance']) {
        $text = t('Object will be removed from outline, and related instance(s) will be deleted');
        $restore_text = t('Restore this object and related instance(s) to the course outline.');
      }
      $restore = ctools_ajax_text_button(t('Restore'), $dest, $restore_text);
      $summary['delete'] = '<span class="error">' . $text . '</span>' . ' ' . $restore;
    }
    return $summary;
  }

  /**
   * Get all course object implementations of getOptionsSummary().
   */
  public function renderOptionsSummary() {
    ctools_include('plugins');
    $summary = $this
      ->getOptionsSummary();

    // Get summaries from plugins, and merge into the summary.
    foreach (ctools_get_plugins('course', 'course_object_access') as $key => $plugin) {

      // @todo how do we get these?
    }

    // @todo make this a themeable function.

    //return theme('course_object_summary');
    $rendered_summary = '';
    if (!empty($summary)) {
      $rendered_summary = $html = '<div class="description">' . theme('item_list', array(
        'title' => '',
        'items' => $summary,
      )) . '</div>';
    }
    return $rendered_summary;
  }

  /**
   * Get options, with session options, except weight, having precedence.
   */
  public function getOptions() {
    $options = parent::getOptions();
    $sessionDefaults = array();
    if (isset($options['nid']) && isset($options['coid']) && isset($_SESSION['course'][$options['nid']]['editing'][$options['coid']])) {
      $sessionDefaults += array_filter((array) $_SESSION['course'][$options['nid']]['editing'][$options['coid']], array(
        $this,
        'optionFilter',
      ));
      unset($sessionDefaults['weight']);
    }
    return array_merge($options, (array) $sessionDefaults);
  }

  /**
   * Get read-only options. These options have been processed by plugins and may
   * have changed from their definitions.
   *
   * Only use this to get options when evaluating access or completion.
   */
  public function getReadOnlyOptions() {
    global $user;
    $account = $user;
    if (!isset($this->readOnlyOptionsCache[$account->uid])) {
      $options = $this
        ->getOptions();

      // Allow plugins to alter options on the fly if we are not editing the
      // course.
      ctools_include('plugins');
      foreach (ctools_get_plugins('course', 'course_object_access') as $key => $plugin) {
        $class = ctools_plugin_get_class($plugin, 'handler');
        $accessPlugin = new $class();
        $accessPlugin
          ->setCourseObject($this);
        $accessPlugin
          ->setType($key);
        $accessPlugin
          ->alterOptions($options, $account);
      }
      $this->readOnlyOptionsCache[$account->uid] = $options;
    }
    return $this->readOnlyOptionsCache[$account->uid];
  }
  public function getReadOnlyOption($key) {
    return $this
      ->getReadOnlyOptions()[$key];
  }
  private function optionFilter($a) {
    return !is_null($a);
  }

  /**
   * Take a course object.
   *
   * - Set the session of this course object being taken. This allows for
   *   non-node objects to be tracked.
   * - Delegate the course object take functionality
   *
   * @return mixed
   *   HTML content or a redirect.
   */
  public final function takeCourseObject() {
    global $user;
    $_SESSION['course']['active'] = $this
      ->getCourseNid();
    $_SESSION['course'][$this
      ->getCourseNid()]['taking']['active'] = $this
      ->getId();

    // Run access checks.
    if ($this
      ->access('take')) {

      // Grant access to external course object.
      $this
        ->getFulfillment($user)
        ->grant();

      // Record start date.
      if (!$this
        ->getFulfillment($user)
        ->getOption('date_started')) {
        $this
          ->getFulfillment($user)
          ->setOption('date_started', REQUEST_TIME)
          ->save();
      }
    }
    else {

      // User can't access this object, revoke access.
      $this
        ->getFulfillment($user)
        ->revoke();
      return FALSE;
    }

    // If we're not displaying any content but we want to fire take() anyway, to
    // let the course object know we sent the user.
    $out = $this
      ->take();
    $url = $this
      ->getTakeUrl();
    switch ($this
      ->getTakeType()) {
      case 'iframe':
        return course_iframe($url);
      case 'popup':
        return "will popup {$url}";
      case 'content':
        return $out;
      case 'redirect':
      default:

        // This URL should have already been url()'d (it might be external).
        drupal_goto($url, array(
          'external' => TRUE,
        ));
    }
  }

  /**
   * How should this course object be executed?
   *
   * - iframe: display an iframe with getTakeUrl() in it
   * - popup: launch getTakeUrl() in a popup
   * - modal: launch getTakeUrl() in a modal
   * - content: print the value from take() (or do whatever the module wants to
   *   do)
   */
  public function getTakeType() {
    return 'content';
  }

  /**
   * Course object entry point for taking. This method should return a value
   * corresponding to the type set in getTakeType().
   */
  public function take() {
    return t('This should be overridden by the module to return course content.');
  }

  /**
   * Return the URL to the course object router.
   */
  public function getUrl() {
    return 'node/' . $this
      ->getCourseNid() . '/course-object/' . $this
      ->getId();
  }

  /**
   * Get the URL to take this course object, if any.
   *
   * Outline handlers or subclasses should use getUrl().
   *
   * @return string
   */
  protected function getTakeUrl() {
  }

  /**
   * Get the URL to edit this course object, if any.
   *
   * @return string
   */
  public function getEditUrl() {
  }

  /**
   * Get the URL to view this course object, if any.
   *
   * @return string
   */
  public function getViewUrl() {
  }

  /**
   * Is this course object required for course completion?
   *
   * @return bool
   */
  public function isRequired() {
    return (bool) $this
      ->getReadOnlyOption('required');
  }

  /**
   * If this course object is required, can be it skipped?
   *
   * @return bool
   */
  public function isSkippable() {
    return (bool) $this
      ->getReadOnlyOption('skippable');
  }

  /**
   * Check if the course object is enabled.
   *
   * @return bool
   */
  public function isEnabled() {
    return (bool) $this
      ->getReadOnlyOption('enabled');
  }

  /**
   * If this course object is required, can be it skipped?
   *
   * @return bool
   */
  public function isVisible() {
    return (bool) (!$this
      ->getReadOnlyOption('hidden'));
  }

  /**
   * Is this object graded?
   *
   * Returning TRUE here will cause some more configurations to show on the
   * object's form.
   *
   * @return bool
   */
  function isGraded() {
    return FALSE;
  }

  /**
   * Get the user's status in this course object.
   *
   * This is how an object would notify the user why they cannot proceed to the
   * next step from the course outline. For example, if this was a quiz and
   * they failed, this should let them know.
   */
  public function getStatus() {
  }

  /**
   * Get a user's fulfillment for this course object. If the user has not
   * started this course object, a new, unsaved fulfillment will be return.
   *
   * @param stdClass $account
   *   User account to get fulfillment for.
   *
   * @return CourseObjectFulfillment
   */
  public function getFulfillment($account) {
    if ($entities = entity_load('course_object_fulfillment', FALSE, array(
      'coid' => $this
        ->identifier(),
      'uid' => $account->uid,
    ))) {
      return reset($entities);
    }
    else {
      return entity_create('course_object_fulfillment', array(
        'coid' => $this
          ->identifier(),
        'uid' => $account->uid,
        'module' => $this->module,
        'object_type' => $this->object_type,
      ));
    }
  }

  /**
   * Get the instance ID. This could be the external component ID, a Node ID...
   *
   * @return string
   */
  function getInstanceId() {
    return $this
      ->getOption('instance');
  }

  /**
   * Set this object's instance ID.
   *
   * @param string $id The external ID of this course object.
   */
  function setInstanceId($id) {
    return $this
      ->setOption('instance', $id);
  }

  /**
   * Get the course node ID this CourseObject belongs to.
   *
   * @return int
   */
  function getCourseNid() {
    return intval($this
      ->getOption('nid'));
  }

  /**
   * Set the Course for this CourseObject.
   *
   * @param Course|int $course
   *   A Course or node ID.
   *
   * @return CourseObject
   */
  public function setCourse($course) {
    if (is_numeric($course)) {
      $this
        ->setOption('nid', $course);
    }
    else {
      $this
        ->setOption('nid', $course
        ->getNode()->nid);
    }
    return $this;
  }

  /**
   * Get the Course that contains this CourseObject.
   *
   * @return Course
   */
  function getCourse() {
    $course = entity_load_single('course', $this->nid);
    return $course;
  }

  /**
   * Get the module that provides this course object.
   *
   * @return string
   */
  function getModule() {
    return $this
      ->getOption('module');
  }

  /**
   * Get the object component title for this course object.
   *
   * @return string
   */
  function getComponentName() {
    $handlers = course_get_handlers('object');
    return $handlers[$this
      ->getModule()][$this
      ->getComponent()]['name'];
  }

  /**
   * Get the object component for this course object.
   *
   * @return string
   */
  function getComponent() {
    return $this
      ->getOption('object_type');
  }

  /**
   * Set the module that provides this course object.
   *
   * @param string $module
   *   The object's module.
   *
   * @return CourseObject
   */
  function setModule($module) {
    return $this
      ->setOption('module', $module);
  }

  /**
   * Set the object component for this course object.
   *
   * @param string $component
   *   The object's component.
   *
   * @return CourseObject
   */
  function setComponent($component) {
    return $this
      ->setOption('object_type', $component);
  }

  /**
   * Set the internal course object ID.
   *
   * @param int $coid
   *   ID of the course object.
   */
  function setId($coid) {
    return $this
      ->setOption('coid', $coid);
  }

  /**
   * Creates a course object.
   *
   * For example, this would create the new node and return the node ID if this
   * was a CourseObjectNode.
   *
   * Do not confuse this with save(), which saves the course outline record for
   * tracking.
   *
   * Course objects should call setInstanceId() if this is a course object
   * that creates external resources.
   */
  public function create() {

    //$this->setInstanceId($id);
  }

  /**
   * Deletes a course object's external resources.
   *
   * For example, this would delete the associated node (if this was a
   * CourseObjectNode) and delete all other associated data.
   */
  public function delete() {

    //something_delete($this->getInstanceId());
  }
  function getTitle() {
    $object_info = course_get_handlers('object');

    // If title is not specified, set title from component.
    if (!$this
      ->getOption('title')) {

      // Get the component name from object info.
      $title = $object_info[$this
        ->getOption('module')][$this
        ->getOption('object_type')]['name'];
      $this
        ->setOption('title', $title);
    }
    return $this
      ->getOption('title');
  }

  /**
   * Give the course object a chance do asynchronous polling and set completion
   * on demand.
   *
   * Useful for external objects.
   */
  function poll() {
  }

  /**
   * Let the course object provide its own reports.
   *
   * @return array
   *   An array indexed by report key, containing 'title' which is the menu link
   *   in the course object reports.
   *
   * @see CourseObjectQuiz::getReports()
   */
  function getReports() {
    return array(
      'default' => array(
        'title' => 'Overview',
      ),
    );
  }

  /**
   * Let the course object provide its own reports.
   *
   * @return array
   *   An array containing:
   *     - title: The title of this report as show on the page
   *     - content: Content to be displayed.
   *     - url: URL to be loaded in an iframe.
   *   Reports should return either 'content' or 'url'.
   *
   * @see CourseObjectQuiz::getReports()
   */
  function getReport($key) {
    if ($key == 'default') {
      return array(
        'title' => 'Overview',
        'content' => views_embed_view('course_object_report', 'default', $this
          ->getCourseNid(), $this
          ->getId()),
      );
    }
  }
  function freeze() {
  }
  function thaw($ice) {
  }

  /**
   * Returns an translated error message if this object has issues with cloning.
   *
   * @return mixed
   *   TRUE if cloning is supported, FALSE if cloning is not supported. A string
   *   if the object can clone but with caveats.
   */
  function getCloneAbility() {
    return FALSE;
  }

  /**
   * Let objects create their instances before saving the course object.
   */
  public function save() {

    // If there is no title, set it.
    $this
      ->getTitle();
    if ($ice = $this
      ->getOption('freeze')) {

      // Found frozen data.
      $this
        ->setInstanceId($this
        ->thaw($ice));
      $this
        ->setOption('freeze', NULL);
    }

    // If there is no instance ID, create one.
    if (!$this
      ->getInstanceId()) {
      $this
        ->create();
    }

    // Set the ID to NULL because this is a temporary course object being
    // created for the first time.
    if (strpos($this
      ->getId(), 'course_object_') !== FALSE) {
      $this
        ->setId(NULL);
    }
    if (is_object($this
      ->getCourse())) {
      $this
        ->getCourse()
        ->resetCache();
    }
    return parent::save();
  }

  /**
   * Checks the temporary status of a course object.
   */
  function isTemporary() {
    return strpos($this
      ->getId(), 'course_object_') === 0;
  }

  /**
   * Return the number of occurances that can be in a course at the same time.
   * For example, the design of the Certificate module can only have 1 set of
   * mappings per node. The same goes for Course Credit. We may also want a
   * course object that can only be added twice (for example, a before/after
   * comparison).
   *
   * This method is static because we might have to call it without an object
   * being instantiated.
   */
  public static function getMaxOccurences() {
    return FALSE;
  }

  /**
   * Set the context of which course this course object belongs to.
   *
   * The return parameters should be compliant with course_determine_context().
   */
  public static function context() {
  }
  function buildContent($view_mode = 'full', $langcode = NULL) {

    // When viewing, we use the current user.
    global $user;
    $content = parent::buildContent($view_mode, $langcode);
    $step = array();
    $step['id'] = 'course-object-' . $this
      ->getId();
    $step['image'] = '';
    $step['status'] = '';
    if ($this
      ->access('see', $user)) {
      if ($this
        ->access('take', $user)) {

        // User can take this course object.
        $step['link'] = $this
          ->getUrl();
        $step['class'][] = 'accessible';
        $step['status'] = t('Not started');

        // Step is complete.
        if ($this
          ->getFulfillment($user)
          ->isComplete()) {
          $step['class'][] = 'completed';
          $step['status'] = t('Complete');
          $step['image'] = 'misc/watchdog-ok.png';
        }
        elseif ($this
          ->getFulfillment($user)
          ->getId()) {
          $step['status'] = t('In progress');
          $step['class'][] = 'in-progress';
          $step['image'] = '';
        }
        if ($this
          ->getCourse()
          ->getActive() === $this) {
          $step['class'][] = 'active';
        }
      }
      else {

        // User cannot access this step yet.
        $step['class'] = array(
          'not-accessible',
        );
        $step['status'] = implode('<br/>', $this
          ->getAccessMessages());
      }
      if ($this
        ->isRequired()) {
        $step['class'][] = 'required';
      }
      $step['class'][] = drupal_html_class($this
        ->getComponent() . '_' . $this
        ->getModule());
      $img = !empty($step['image']) ? theme('image', array(
        'path' => $step['image'],
        'alt' => strip_tags($step['status']),
      )) : '';
      $content['course_outline_image'] = array(
        '#markup' => $img,
      );
      $content['course_outline_link'] = array(
        '#markup' => $this
          ->access('take', $user) ? l($this
          ->getTitle(), $this
          ->getUrl()) : $this
          ->getTitle(),
      );
      $content['course_outline_status'] = array(
        '#markup' => $step['status'],
        '#prefix' => '<div>',
        '#suffix' => '</div>',
      );
    }
    return $content;
  }

  /**
   * Generate URI from course object.
   */
  public function uri() {
    return array(
      'path' => 'node/' . $this->nid . '/course-object/' . $this
        ->identifier(),
    );
  }

  /**
   * Clear the read only options cache before changing an option.
   */
  public function setOption($option, $value) {
    $this->readOnlyOptionsCache = [];
    return parent::setOption($option, $value);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CourseHandler::addOptions final public function Merge an array of options onto the existing options.
CourseHandler::getAccessMessages public function Get an array of access messages.
CourseHandler::getDatabaseFields protected function Return an array of database fields. This determines what fields should be serialized instead of stored.
CourseHandler::getId function
CourseHandler::getOption public function Get an handler option's value.
CourseHandler::getWarnings public function Return a list of warning strings about this handler. 1
CourseHandler::optionsMerge private function Merge arrays with replace, not append.
CourseHandler::setAccessMessage public function Set an access message to be displayed along with the course object when it is in the outline. For example, "This activity will open on XYZ" or "Please complete Step 1 to take this activity."
CourseHandler::setOptions final public function Set this entire handler's options.
CourseHandler::__construct function Overrides Entity::__construct 1
CourseObject::$accessMessages protected property
CourseObject::$readOnlyOptionsCache protected property
CourseObject::access public function Access functionality for course objects. 1
CourseObject::buildContent function Builds a structured array representing the entity's content. Overrides Entity::buildContent
CourseObject::context public static function Set the context of which course this course object belongs to. 1
CourseObject::create public function Creates a course object. 1
CourseObject::delete public function Deletes a course object's external resources. Overrides Entity::delete 1
CourseObject::freeze function 1
CourseObject::getCloneAbility function Returns an translated error message if this object has issues with cloning. 2
CourseObject::getComponent function Get the object component for this course object.
CourseObject::getComponentName function Get the object component title for this course object.
CourseObject::getCourse function Get the Course that contains this CourseObject.
CourseObject::getCourseNid function Get the course node ID this CourseObject belongs to.
CourseObject::getEditUrl public function Get the URL to edit this course object, if any. 1
CourseObject::getFulfillment public function Get a user's fulfillment for this course object. If the user has not started this course object, a new, unsaved fulfillment will be return.
CourseObject::getInstanceId function Get the instance ID. This could be the external component ID, a Node ID...
CourseObject::getMaxOccurences public static function Return the number of occurances that can be in a course at the same time. For example, the design of the Certificate module can only have 1 set of mappings per node. The same goes for Course Credit. We may also want a course object that can only be… 2
CourseObject::getModule function Get the module that provides this course object.
CourseObject::getOptions public function Get options, with session options, except weight, having precedence. Overrides CourseHandler::getOptions
CourseObject::getOptionsSummary public function Get core options summary. Overrides CourseHandler::getOptionsSummary 1
CourseObject::getReadOnlyOption public function
CourseObject::getReadOnlyOptions public function Get read-only options. These options have been processed by plugins and may have changed from their definitions.
CourseObject::getReport function Let the course object provide its own reports. 4
CourseObject::getReports function Let the course object provide its own reports. 4
CourseObject::getStatus public function Get the user's status in this course object. 2
CourseObject::getTakeType public function How should this course object be executed? 2
CourseObject::getTakeUrl protected function Get the URL to take this course object, if any. 1
CourseObject::getTitle function 1
CourseObject::getUrl public function Return the URL to the course object router.
CourseObject::getViewUrl public function Get the URL to view this course object, if any. 1
CourseObject::hasPolling public function Specify whether fulfillment uses asynchronous polling.
CourseObject::isActive public function
CourseObject::isEnabled public function Check if the course object is enabled.
CourseObject::isGraded function Is this object graded? 2
CourseObject::isRequired public function Is this course object required for course completion?
CourseObject::isSkippable public function If this course object is required, can be it skipped?
CourseObject::isTemporary function Checks the temporary status of a course object.
CourseObject::isVisible public function If this course object is required, can be it skipped?
CourseObject::optionFilter private function
CourseObject::optionsDefinition public function Define configuration elements and their defaults. Overrides CourseHandler::optionsDefinition 4
CourseObject::optionsForm public function Default options form for all course objects. Overrides CourseHandler::optionsForm 3
CourseObject::optionsSubmit public function Save object configs to cache. Overrides CourseHandler::optionsSubmit 1
CourseObject::optionsValidate public function Validate? Overrides CourseHandler::optionsValidate 1
CourseObject::overrideNavigation public function Override navigation links. 1
CourseObject::overrideOutlineListItem public function Overrides a course outline list item. 1
CourseObject::poll function Give the course object a chance do asynchronous polling and set completion on demand.
CourseObject::renderOptionsSummary public function Get all course object implementations of getOptionsSummary().
CourseObject::save public function Let objects create their instances before saving the course object. Overrides CourseHandler::save 1
CourseObject::setComponent function Set the object component for this course object.
CourseObject::setCourse public function Set the Course for this CourseObject.
CourseObject::setDelete public function Mark this object for deletion. This is not a submit button so we set the values manually.
CourseObject::setId function Set the internal course object ID.
CourseObject::setInstanceId function Set this object's instance ID.
CourseObject::setModule function Set the module that provides this course object.
CourseObject::setOption public function Clear the read only options cache before changing an option. Overrides CourseHandler::setOption
CourseObject::take public function Course object entry point for taking. This method should return a value corresponding to the type set in getTakeType(). 6
CourseObject::takeCourseObject final public function Take a course object.
CourseObject::thaw function 1
CourseObject::uri public function Generate URI from course object. Overrides Entity::uri
Entity::$defaultLabel protected property 1
Entity::$entityInfo protected property
Entity::$entityType protected property
Entity::$idKey protected property
Entity::$wrapper protected property
Entity::bundle public function Returns the bundle of the entity. Overrides EntityInterface::bundle
Entity::defaultLabel protected function Defines the entity label if the 'entity_class_label' callback is used. 1
Entity::defaultUri protected function Override this in order to implement a custom default URI and specify 'entity_class_uri' as 'uri callback' hook_entity_info().
Entity::entityInfo public function Returns the info of the type of the entity. Overrides EntityInterface::entityInfo
Entity::entityType public function Returns the type of the entity. Overrides EntityInterface::entityType
Entity::export public function Exports the entity. Overrides EntityInterface::export
Entity::getTranslation public function Gets the raw, translated value of a property or field. Overrides EntityInterface::getTranslation
Entity::hasStatus public function Checks if the entity has a certain exportable status. Overrides EntityInterface::hasStatus
Entity::identifier public function Returns the entity identifier, i.e. the entities name or numeric id. Overrides EntityInterface::identifier
Entity::internalIdentifier public function Returns the internal, numeric identifier. Overrides EntityInterface::internalIdentifier
Entity::isDefaultRevision public function Checks whether the entity is the default revision. Overrides EntityInterface::isDefaultRevision
Entity::label public function Returns the label of the entity. Overrides EntityInterface::label
Entity::setUp protected function Set up the object instance on construction or unserializiation.
Entity::view public function Generate an array for rendering the entity. Overrides EntityInterface::view
Entity::wrapper public function Returns the EntityMetadataWrapper of the entity. Overrides EntityInterface::wrapper
Entity::__sleep public function Magic method to only serialize what's necessary.
Entity::__wakeup public function Magic method to invoke setUp() on unserialization.