learnosity-1.0.x-dev/src/Element/LearnosityActivityEditor.php
src/Element/LearnosityActivityEditor.php
<?php
namespace Drupal\learnosity\Element;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\Core\Render\Markup;
/**
* Provides a Learnosity authoring item.
*
* @FormElement("learnosity_activity_editor")
*/
class LearnosityActivityEditor extends FormElement {
/**
* The entity fields to be stored on this form element.
*
* @var array
*/
public static $entityFields = [
'reference',
'data',
];
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
$info = [
'#pre_render' => [
[$class, 'preRenderLearnosityAuthoring'],
],
'#process' => [
[$class, 'processAuthoringItem'],
],
'#input' => TRUE,
'#user' => [],
'#editor' => 'default',
'#context' => [],
'#theme' => 'learnosity_activity_editor',
'#theme_wrappers' => ['form_element'],
];
$info['#element_validate'] = [[$class, 'validateLearnosityAuthoring']];
return $info;
}
/**
* Process the authoring item element.
*
* @param array $element
* The form element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state object.
* @param array $complete_form
* The complete form.
*
* @return array
* The modified element.
*/
public static function processAuthoringItem(array &$element, FormStateInterface $form_state, array &$complete_form) {
// The value callback has populated the #value array.
$value = !empty($element['#value']) ? $element['#value'] : NULL;
$element['#tree'] = TRUE;
// Hide the default input element.
$element['#attributes']['type'] = 'hidden';
// This is necessary to make sure we're always saving the right
// reference.
// @see learnosity.authorapi.js.
foreach (self::$entityFields as $field) {
$element[$field] = [
'#type' => 'hidden',
'#value' => $value[$field],
'#attributes' => ['id' => 'lrn-field--' . $field],
];
}
// @todo: Move this to the field widget.
// Fetch the current entity if applicable. This is passed as a argument
// to the event dispatcher.
if ($form_state->getFormObject() instanceof EntityFormInterface) {
$entity = $form_state->getFormObject()->getEntity();
$element['#context'] += [
'entity_type' => $entity->getEntityTypeId(),
'entity_id' => $entity->id(),
'langcode' => $entity->language()->getId(),
'editor' => $element['#editor'],
];
}
return $element;
}
/**
* Pre render callback.
*
* The is originally based on the prerender callback defined on the link
* element.
*
* @param array $element
* The element.
*
* @return array
* Element render array.
*/
public static function preRenderLearnosityAuthoring(array $element) {
$learnositySdk = \Drupal::service('learnosity.sdk');
$learnosityApiEventHandler = \Drupal::service('learnosity.api_event_handler');
$editor = \Drupal::entityTypeManager()->getStorage('learnosity_activity_editor')->load($element['#editor']);
// Get the current reference for this activity.
$reference = $element['#value']['reference'];
// Fetch the editor config.
$config = $editor->getConfig(TRUE);
// Attaching the service library to this render element. This will ensure
// that the proper learnosity api library gets loaded.
$element['#attached']['library'][] = 'learnosity/api.authorapi';
// Set current user id and email if none is provided.
if (empty($element['#user'])) {
/** @var \Drupal\user\Entity\User $user */
$user = \Drupal::service('entity_type.manager')->getStorage('user')->load(\Drupal::currentUser()->id());
$element['#user'] = [
'id' => $user->id(),
'email' => $user->getEmail(),
];
}
// See which features are enabled for this editor.
$features = $editor->getFeatures();
$featureManager = \Drupal::service('plugin.manager.learnosity_feature');
foreach ($features as $name) {
$feature = $featureManager->createInstance($name);
$config['dependencies']['question_editor_api']['init_options']['custom_feature_types'][] = $feature->build();
$attachments = $feature->buildAttachments();
if (!empty($attachments)) {
foreach ($attachments as $item) {
$element['#attached']['library'][] = $item;
}
}
}
if (!empty($reference) && !empty($element['#user'])) {
$signed_request = $learnositySdk->init('author', [
'mode' => 'activity_edit',
'reference' => $reference,
'user' => $element['#user'],
'config' => $config,
], $element['#context']);
}
if (!empty($signed_request)) {
// @todo need a better way to handle context.
// Prepare the context items to be attached.
// It will only attach scalar values because arrays and objects should not
// be passed through javascript.
$context = [];
foreach ($element['#context'] as $key => $value) {
if (is_scalar($value)) {
$context[$key] = $value;
}
}
// Load core learnosity drupal js file and pass signed request.
$element['#attached']['library'][] = 'learnosity/learnosity';
$element['#attached']['library'][] = 'learnosity/learnosity.authorapi';
$element['#attached']['drupalSettings']['learnosity'] = [
'service' => 'authorapi',
'signedRequest' => Markup::create($signed_request),
'events' => $learnosityApiEventHandler->getSubscribedEvents(),
'context' => $context,
'editor' => $element['#editor'],
// This is necessary in order to toggle between default and custom image
// upload.
'uploadWidget' => ($editor->getUploadWidget()) ? $editor->getUploadWidget()['plugin'] : '',
];
// Note: learnosity-author id is required by learnosity in order for the
// experience to load.
$element['item'] = [
'#markup' => Markup::create(' <div id="learnosity-author" data-learnosity="true"></div>'),
];
}
return $element;
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
// Process the #default_value property. If no input is provided then it
// will process any default value. If none, then it will generate one.
if ($input === FALSE) {
// If a value is provided for #default_value.
if (!empty($element['#default_value'])) {
// Convert the default value into an array for easier processing.
if (!is_array($element['#default_value'])) {
$element['#default_value'] = [$element['#default_value']];
}
if (!(reset($element['#default_value']) instanceof EntityInterface)) {
throw new \InvalidArgumentException('The #default_value property has to be an entity object or an array of entity objects.');
}
if (isset($element['#default_value'])) {
$default_value = $element['#default_value'];
$activity = reset($default_value);
return [
'reference' => $activity->getReference(),
'data' => urlencode($activity->getData()),
];
}
}
// If there is no value provided for #default_value, then generate a
// new one UUID.
else {
$learnositySdk = \Drupal::service('learnosity.sdk');
return [
'reference' => $learnositySdk->generateUuid(),
'data' => NULL,
];
}
}
// If an input is provided by the user then it will return that.
// This could happen if they decide to choose a different activity.
if ($input !== FALSE) {
return $input;
}
}
/**
* Form element validation handler for entity_autocomplete elements.
*/
public static function validateLearnosityAuthoring(array &$element, FormStateInterface $form_state, array &$complete_form) {
$value = NULL;
if (!empty($element['#value'])) {
$options = $element['#selection_settings'] + [
'target_type' => $element['#target_type'],
'handler' => $element['#selection_handler'],
];
/** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler */
$handler = \Drupal::service('plugin.manager.entity_reference_selection')->getInstance($options);
// Check to see if an entity exists.
$exists = \Drupal::service('entity_type.manager')->getStorage($element['#target_type'])->loadByProperties([
'reference' => $element['#value']['reference'],
]);
// Userialize data and prepare it to be saved.
$data = [];
if (!empty($element['#value']['data'])) {
$data = json_decode(urldecode($element['#value']['data']), TRUE) ?: [];
}
if ($exists) {
foreach ($exists as $entity) {
// Make sure that data is up-to-date.
$entity->setData($data);
$entity->save();
$value[] = [
'entity' => $entity,
];
}
}
// If it doesn't exist then create it and reset the value.
else {
$input_values = [$element['#value']];
foreach ($input_values as $input) {
/** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler */
$entity = $handler->createNewEntity($element['#target_type'], 'learnosity_activity', $input['reference'], $element['#autocreate']['uid']);
$entity->setData($data);
$value[] = [
'entity' => $entity,
];
}
}
}
// Use only the last value if the form element does not support multiple
// matches.
if (!empty($value)) {
$last_value = $value[count($value) - 1];
$value = isset($last_value['target_id']) ? $last_value['target_id'] : $last_value;
}
$form_state->setValueForElement($element, $value);
}
}
