closedquestion-8.x-3.x-dev/src/Question/CqQuestionAbstract.php

src/Question/CqQuestionAbstract.php
<?php

namespace Drupal\closedquestion\Question;

use Drupal\closedquestion\Question\Textlabel\TextLabel;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Markup;
use Drupal\file\Entity\File;

/**
 * Class CqQuestionAbstract.
 *
 * Abstract base implementation of the CqQuestionInterface interface.
 *
 * @package Drupal\closedquestion\Question
 */
abstract class CqQuestionAbstract implements CqQuestionInterface {

  /**
   * The CqUserAnswerInterface to use for storing the student's answer.
   *
   * @var \Drupal\closedquestion\Question\CqUserAnswerInterface
   */
  protected $userAnswer;

  /**
   * The drupal node object that contains this question.
   *
   * @var Object
   */
  protected $closedQuestion;

  /**
   * The list of CqListenerQuestionInterface listeners.
   *
   * @var CqListenerQuestionInterface[]
   */
  protected $listeners = array();

  /**
   * The html to add before the generated html of the question.
   *
   * @var string
   */
  protected $prefix = '';

  /**
   * The html to add after the generated html of the question.
   *
   * @var string
   */
  protected $postfix = '';

  /**
   * The path that was used to load this question.
   *
   * We need to store it because we can't trust the path if the object is
   * stored in a cached form and later used from a json call.
   *
   * @var string
   */
  public $usedPath = '';

  /**
   * Handeled tags.
   *
   * List of node names or tags in the text that are handled by this question
   * when it is used as $context parameter in XmlLib::getTextContent().
   * Tags should be in the form of [tagName|tagData].
   *
   * @var string[]
   *
   * @see XmlLib::getTextContent()
   * @see handleNode()
   * @see handleTag()
   */
  private $handledTags = array(
    'mathresult',
    'feedbackblock',
    'textlabelresult',
    'widget',
  );

  /**
   * Known elements.
   *
   * A list of XML elements that are known by the Question. If an element is
   * found that is not in this list the user probably made a typo and should be
   * warned.
   *
   * @var string[]
   */
  protected $knownElements = array(
    '#text',
    '#comment',
    'prefix',
    'postfix',
    'matheval',
    'mathimport',
    'textlabel',
    'widgetinfo',
  );

  /**
   * The EvalMath object used to evaluate math expressions.
   *
   * @var \Drupal\closedquestion\Utility\EvalMath
   */
  protected $evalMath;

  /**
   * The list of expressions found in this question.
   *
   * @var array
   *   Each item is an array with the fields:
   *   - expression: string containing the mathematical expression.
   *   - store: boolean indicating that after this expression is executed the
   *     state of the variables should be stored.
   */
  private $mathExpressions = [];

  /**
   * The text labels used in this question.
   *
   * @var \Drupal\closedquestion\Question\Textlabel\TextLabel[]
   */
  private $textLabels = [];

  /**
   * Additional css class/classes to add to the final HTML.
   *
   * @var string
   */
  private $cssClass = '';

  /**
   * Has the question been fully initialised?
   *
   * This has to happen during node_view, to give other modules the time to do
   * their thing.
   *
   * @var bool
   */
  private $initialised = FALSE;

  /**
   * List of supported widgets.
   *
   * @var array
   */
  public $supportedwidgets = [];

  /**
   * List of widget names found embedded in question.
   *
   * @var array
   */
  protected $widgetsfound = [];

  /**
   * Current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * Messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * XML helper service.
   *
   * @var \Drupal\closedquestion\Utility\XmlLib
   */
  protected $xmlLib;

  /**
   * Form builder.
   *
   * @var \Drupal\Core\Form\FormBuilderInterface
   */
  protected $formBuilder;

  /**
   * The renderer service.
   *
   * @var \Drupal\Core\Render\Renderer
   */
  protected $renderer;

  /**
   * Default constructor.
   */
  public function __construct() {
    $this->usedPath = isset($_GET['q']) ? $_GET['q'] : '';
    $this->currentUser = \Drupal::currentUser();
    $this->messenger = \Drupal::messenger();
    $this->evalMath = \Drupal::service('closedquestion.utility.eval_math');
    $this->xmlLib = \Drupal::service('closedquestion.utility.xml_lib');
    $this->formBuilder = \Drupal::formBuilder();
    $this->renderer = \Drupal::service('renderer');
  }

  /**
   * Initialises this question from the given DOMElement.
   *
   * @param \DOMNode $dom
   *   The XML DOMNode to use to initialise this question.
   */
  public function loadXml(\DOMNode $dom) {
    // In case loadXml is called by another means than initialise(), we don't
    // want initialise() to run.
    $this->initialised = TRUE;
    foreach ($dom->childNodes as $node) {
      switch ($node->nodeName) {
        case 'prefix':
          $this->prefix = $this->xmlLib->getTextContent($node, $this);
          break;

        case 'postfix':
          $this->postfix = $this->xmlLib->getTextContent($node, $this);
          break;

        case 'matheval':
          $this->handleNodeMathEval($node);
          break;

        case 'mathimport':
          $this->handleNodeMathImport($node);
          break;

        case 'textlabel':
          $this->handleNodeTextlabel($node);
          break;

        case 'widgetinfo':
          $this->handleNodeWidgetinfo($node);
          break;
      }
    }

    /* load css class? */
    $attribs = $dom->attributes;

    $item = $attribs->getNamedItem('cssclass');
    if ($item !== NULL) {
      $this->cssClass = $item->value;
    }

    /* execute math */
    $this->executeMath();
  }

  /**
   * Executes expression evaluation.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  private function executeMath() {
    if (count($this->mathExpressions) <= 0) {
      return;
    }
    $needs_store = FALSE;
    $debugOutput = '';

    $vars = $this->userAnswer->getData('emv');
    $oldVars = FALSE;
    if (!empty($vars)) {
      $oldVars = TRUE;
      $this->evalMath->setVars($vars);
    }

    foreach ($this->mathExpressions as $e) {
      switch ($e['type']) {
        case 'import':
          $importUserAnswer = closedquestion_get_useranswer($e['importnode'], $this->userAnswer->getUserId());
          $exports = $importUserAnswer->getData('export');
          if ($exports === NULL) {
            continue;
          }
          $vars = $this->evalMath->getVars();
          $vars[$e['varname']] = $exports[$e['importname']];
          $this->evalMath->setVars($vars);

          if ($this->currentUser->hasPermission(CLOSEDQUESTION_RIGHT_CREATE)) {
            $debugOutput .= t('Expression: %e set to %r', array('%e' => $e['varname'], '%r' => $vars[$e['varname']])) . '<br />';
          }
          break;

        default:
          if (!$e['store'] || !$oldVars) {
            $result = $this->evalMath->evaluate($e['expression']);
            if ($this->currentUser->hasPermission(CLOSEDQUESTION_RIGHT_CREATE)) {
              $debugOutput .= t('Expression: %e result: %r', array('%e' => $e['expression'], '%r' => $result)) . '<br />';
            }
            if ($e['store']) {
              $vars = $this->evalMath->getVars();
              $this->userAnswer->setData('emv', $vars);
              $needs_store = TRUE;
            }
          }
          break;
      }
    }

    if ($needs_store) {
      $this->userAnswer->store();
    }

    if ($this->currentUser->hasPermission(CLOSEDQUESTION_RIGHT_CREATE)) {
      $message = closedquestion_make_fieldset('Teacher only debug output', Markup::create('<p>' . $debugOutput . '</p>'), TRUE, TRUE);
      $this->messenger->addMessage($message);
    }
  }

  /**
   * Handles a node from the question xml, treating it as a MethEval node.
   *
   * @param \DOMNode $node
   *   The node to treat as a MathEval node.
   */
  public function handleNodeMathEval(\DOMNode $node) {
    $expression = array();
    $expression['type'] = 'expression';
    $expression['expression'] = '';
    $expression['store'] = FALSE;

    $attribs = $node->attributes;
    $item1 = $attribs->getNamedItem('expression');
    if ($item1 === NULL) {
      $item1 = $attribs->getNamedItem('e');
    }
    if ($item1 !== NULL) {
      $expression['expression'] = $item1->value;
    }
    $item2 = $attribs->getNamedItem('store');
    if ($item2 !== NULL) {
      $expression['store'] = (boolean) $item2->value;
    }
    $this->mathExpressions[] = $expression;
  }

  /**
   * Handles a node from the question xml, treating it as a MathExport node.
   *
   * @param \DOMNode $node
   *   The node to treat as a MathExport node.
   */
  public function handleNodeMathImport(\DOMNode $node) {
    $expression = array();
    $expression['type'] = 'import';
    $expression['varname'] = '';
    $expression['importname'] = '';
    $expression['importnode'] = '';

    $attribs = $node->attributes;
    $item1 = $attribs->getNamedItem('varname');
    $item2 = $attribs->getNamedItem('importname');
    $item3 = $attribs->getNamedItem('importnode');
    if ($item1 === NULL || $item2 === NULL || $item3 === NULL) {
      $this->messenger->addMessage(t('Attributes "varname", "importname" and "importnode" required for mathimport tags'));
      return;
    }

    $expression['varname'] = $item1->value;
    $expression['importname'] = $item2->value;
    $expression['importnode'] = (int) $item3->value;

    $this->mathExpressions[] = $expression;
  }

  /**
   * Handles a node from the question xml, treating it as a textlabel node.
   *
   * @param \DOMNode $node
   *   The node to treat as a textlabel node.
   */
  public function handleNodeTextlabel(\DOMNode $node) {
    $textlabel = new TextLabel();
    $textlabel->initFromNode($node, $this);
    $labelId = $textlabel->getId();
    if ($labelId !== NULL && strlen($labelId) > 0) {
      if (isset($this->textLabels[$labelId])) {
        $this->messenger->addMessage(t('Warning, TextLabels with duplicate ID: %id', array('%id' => $labelId)));
      }
      else {
        $this->textLabels[$labelId] = $textlabel;
      }
    }
  }

  /**
   * Handles a node from the question xml, treating it as a widgetinfo node.
   *
   * @param \DOMNode $node
   *   The node to treat as a widgetinfo node.
   */
  public function handleNodeWidgetinfo(\DOMNode $node) {
    $type = $node->getAttribute('type');
    $widgedId = $node->getAttribute('id');
    if ($type !== NULL && $widgedId !== NULL && array_key_exists($type, $this->supportedwidgets)) {
      $this->widgetsfound[$widgedId] = $type;
      $this->supportedwidgets[$type]->loadXML($node);
    }
  }

  /**
   * Do final initialisation of the question.
   *
   * This has to happen during entity_view, to give other modules the time to do
   * their thing.
   */
  public function initialise() {
    if (!$this->initialised) {
      $this->initialised = TRUE;

      $firstChild = NULL;

      if ($this->closedQuestion->get('field_question_xml')->value) {
        $xml = $this->closedQuestion->get('field_question_xml')->value;
        $xml = closedquestion_filter_content($this->closedQuestion, $xml);

        $dom = new \DomDocument();
        $dom->loadXML($xml);
        $xpath = new \DOMXPath($dom);
        $questions = $xpath->query('/question');
        $firstChild = $questions->item(0);
      }

      if ($firstChild) {
        $this->loadXML($firstChild);
      }
      else {
        $this->messenger->addMessage(t('No question found in XML of closed question %id.', array('%id' => $this->closedQuestion->id())));
      }
    }
  }

  /**
   * Check if the question is already initialised.
   *
   * @return bool
   *   TRUE if question is initialised.
   */
  public function isInitialised() {
    return $this->initialised;
  }

  /**
   * Get the form/html output of this question.
   *
   * @return string
   *   Themed html.
   */
  abstract public function getOutput();

  /**
   * Get all the text in the question, for easier reviewing for spelling, etc.
   *
   * @return array
   *   Drupal form-api compatible array.
   */
  abstract public function getAllText();

  /**
   * Get the last answer that the user gave to this question.
   *
   * The type of the returned data depends on the question implementation.
   *
   * @return mixed
   *   The last answer.
   */
  public function getAnswer() {
    return $this->userAnswer->getAnswer();
  }

  /**
   * Sets the user answer.
   *
   * The type of the data depends on the question implementation.
   *
   * @param mixed $answer
   *   The answer.
   */
  public function setAnswer($answer) {
    $this->userAnswer->setAnswer($answer);
  }

  /**
   * Get the css class.
   *
   * @return string
   *   The css class.
   */
  public function getCssClass() {
    return $this->cssClass;
  }

  /**
   * Get the list of hotspots defined in this question.
   *
   * To be overridden by question types that have hotspots.
   *
   * @return \Drupal\closedquestion\Question\Mapping\CqHotspotInterface[]
   *   Hotspots array.
   */
  public function getHotspots() {
    return [];
  }

  /**
   * Get the list of draggables defined in this question.
   *
   * To be overridden by question types that have draggables.
   *
   * @return \Drupal\closedquestion\Question\Mapping\CqDraggable[]
   *   Array of draggable objects.
   */
  public function getDraggables() {
    return [];
  }

  /**
   * Form builder for the question-form of this question.
   *
   * Since Form API requires a form class the QuestionForm is used,
   * which will call this method.
   *
   * @see \Drupal\closedquestion\Form\QuestionForm
   * @see submitAnswer()
   * @ingroup forms
   */
  public function getForm($formState) {
    $form = array();
    foreach ($this->widgetsfound as $widgetname) {
      $this->supportedwidgets[$widgetname]->getForm($form);
    }
    return $form;
  }

  /**
   * Form submission handler for getForm()
   *
   * Since the form submit handler can not be the method of an object instance,
   * ClosedQuesion will receive the form_submit, fetch the object from the form
   * cache and forward the submit to the object.
   *
   * @see hook_form_submit()
   */
  abstract public function submitAnswer($form, FormStateInterface $form_state);

  /**
   * Run any checks to see if the last given answer is correct.
   *
   * This method is called by isCorrect() and the result is cached.
   *
   * @return bool
   *   TRUE if the user answered correctly.
   */
  abstract public function checkCorrect();

  /**
   * Implements CqQuestionInterface::isCorrect()
   */
  public function isCorrect($force = FALSE) {
    if ($force || $this->userAnswer->isCorrect() == -1) {
      $wasCorrect = $this->userAnswer->onceCorrect();
      $this->userAnswer->setCorrect($this->checkCorrect());
      if (!$wasCorrect && $this->userAnswer->isCorrect()) {
        $this->fireFirstSolutionFound();
      }
    }
    return ($this->userAnswer->isCorrect() > 0);
  }

  /**
   * Implements CqQuestionInterface::onceCorrect()
   */
  public function onceCorrect() {
    return $this->userAnswer->onceCorrect();
  }

  /**
   * Implements CqQuestionInterface::getTries()
   */
  public function getTries() {
    return $this->userAnswer->getTries();
  }

  /**
   * Implements CqQuestionInterface::reset()
   */
  public function reset() {
    $this->userAnswer->reset();
  }

  /**
   * Add a tag to the list of tags handled by this object.
   *
   * @param string $tag
   *   The tag to add.
   *
   * @see XmlLib::getTextContent()
   * @see handled_tags()
   */
  public function registerTag($tag) {
    $this->handledTags[] = $tag;
  }

  /**
   * Get the list of tags handled by this object.
   *
   * @return string[]
   *   The list of tags.
   *
   * @see XmlLib::getTextContent()
   * @see handled_tags()
   */
  public function getHandledTags() {
    return $this->handledTags;
  }

  /**
   * Get the html required for implementing the given XML node.
   *
   * XmlLib::getTextContent() will call this method when this object is the
   * $context and it finds an XML node that is listed in $this->handled_tags.
   *
   * @param \DOMNode $node
   *   The XML node to handle.
   * @param bool $delay
   *   If true, some XML nodes are replaced with a [] tag so they can be
   *   processed later. This can be used when not all data needed for full
   *   processing is available yet.
   *
   * @return string
   *   The html that implements the needed functionality.
   *
   * @see XmlLib::getTextContent()
   * @see handled_tags()
   */
  public function handleNode(\DOMNode $node, $delay = FALSE) {

    $retval = '';
    switch (mb_strtolower($node->nodeName)) {
      case 'mathresult':
        $attribs = $node->attributes;
        $item = $attribs->getNamedItem('expression');
        if ($item === NULL) {
          $item = $attribs->getNamedItem('e');
        }
        if ($item !== NULL) {
          $expression = $item->value;
          if ($delay) {
            $retval .= '[mathresult|' . $expression . ']';
          }
          else {
            $retval .= $this->evalMath->e($expression);
          }
        }
        break;

      case 'feedbackblock':
        $attribs = $node->attributes;
        $item = $attribs->getNamedItem('identifier');
        if ($item === NULL) {
          $item = $attribs->getNamedItem('id');
        }
        if ($item === NULL) {
          $this->messenger->addMessage(t('FeedbackBlock without id'));
        }
        else {
          $id = $item->value;
          $retval .= '<span class="cqFbBlock cqFb-' . $id . '" ></span>';
        }
        break;

      case 'textlabelresult':
        $attribs = $node->attributes;
        $item = $attribs->getNamedItem('identifier');
        if ($item === NULL) {
          $item = $attribs->getNamedItem('id');
        }
        if ($item === NULL) {
          $this->messenger->addMessage(t('TextLabelResult requested without id.'));
        }
        else {
          $labelId = $item->value;
          $item = $attribs->getNamedItem('mathvariable');
          if ($item === NULL) {
            $item = $attribs->getNamedItem('variable');
          }
          if ($item === NULL) {
            $this->messenger->addMessage(t('TextLabelResult requested without variable name.'));
          }
          else {
            $variable = $item->value;
            if ($delay) {
              $retval .= '[textlabelresult|' . $labelId . '|' . $variable . ']';
            }
            else {
              $retval .= $this->handleTag('textlabelresult', $labelId . '|' . $variable);
            }
          }
        }
        break;

      case 'widget':
        $attribs = $node->attributes;
        $item = $attribs->getNamedItem('id');
        if ($item !== NULL) {
          $widgetid = $item->value;
          if (array_key_exists($widgetid, $this->widgetsfound)) {
            $type = $this->widgetsfound[$widgetid];
            if (array_key_exists($type, $this->supportedwidgets)) {
              $retval .= '<div type="' . $type . '" id="' . $widgetid . '" ></div>';
            }
          }
          else {
            $this->messenger->addMessage(t('No matching widgetinfo found for widget with id: %id', ['%id' => $widgetid]), 'warning');
          }
        }
        break;
    }
    return $retval;
  }

  /**
   * Get the html required for implementing the given tag.
   *
   * Function cq_replace_tags() will call this method when this object
   * is the $context and it finds a tag that is listed in $this->handled_tags.
   *
   * Tags have to be in the form [tagName|tagData]
   *
   * @param string $tagName
   *   The name of the tag that was found.
   * @param string $tagData
   *   The data of the tag that was found.
   *
   * @return string
   *   HTML string.
   */
  public function handleTag($tagName, $tagData) {
    $retval = '';
    switch ($tagName) {
      case 'mathresult':
        $retval .= $this->evalMath->e($tagData);
        break;

      case 'textlabelresult':
        $data = explode('|', $tagData);
        $id = $data[0];
        $variable = $data[1];
        if (isset($this->textLabels[$id])) {
          $value = $this->evaluateMath($variable);
          $retval .= $this->textLabels[$id]->getValue($value);
        }
        else {
          $retval .= t('Unknown TextLabel: %s.', array('%s' => $data[0]));
        }
        break;

      case 'feedbackblock':
        $retval .= '<span class="cqFbBlock cqFb-' . $tagData . '" ></span>';
        break;
    }
    return $retval;
  }

  /**
   * Evaluate the given expression in the current evalMath context.
   *
   * @param string $expression
   *   The expression to evaluate.
   *
   * @return string
   *   The result of the evaluation of the expression.
   */
  public function evaluateMath($expression) {
    if (!empty($expression)) {
      return $this->evalMath->e($expression);
    }
  }

  /**
   * Check if this question has any math associated with it so far.
   *
   * @return bool
   *   TRUE if there is any math in the question so far, FALSE otherwise.
   */
  public function hasMath() {
    return ($this->evalMath !== NULL);
  }

  /**
   * Implements CqQuestionInterface::addListener()
   */
  public function addListener(CqListenerQuestionInterface $listener) {
    $this->listeners[] = & $listener;
  }

  /**
   * Return an array with all the feedback items that should be active.
   *
   * @return \Drupal\closedquestion\Question\Mapping\CqFeedback[]
   *   Array of feedback items.
   */
  abstract public function getFeedbackItems();

  /**
   * Inserts the form item that shows the feedback, into the given form.
   *
   * @param array $form
   *   The form into which the feedback should be inserted.
   * @param string $wrapper_id
   *   Array key and HTML id to give the feedback wrapper item. The same id
   *   must be passed to insertSubmit() for the standard feedback-replacement
   *   AHAH functionality.
   *
   * @see insertSubmit()
   */
  public function insertFeedback(array &$form, $wrapper_id) {
    // This wrapper will be used for AHAH replacement upon form submit.
    $form[$wrapper_id] = array(
      '#theme_wrappers' => array('container'),
      '#attributes' => array(
        'id' => $wrapper_id,
        'class' => array('cq-feedback-wrapper'),
      ),
    );

    $feedbackItems = $this->getFeedbackItems();
    if (count($feedbackItems) > 0) {
      $attribs = array();
      if (!$this->isCorrect()) {
        $attribs['class'] = array('cq_error');
      }
      else {
        $attribs['class'] = array('cq_correct');
      }

      $form[$wrapper_id]['feedback'] = array(
        '#type' => 'fieldset',
        '#title' => t('Feedback'),
        '#attributes' => $attribs,
      );
      foreach ($feedbackItems as $nr => $fb) {
        $form[$wrapper_id]['feedback']['cq-feedback-' . $nr] = array(
          '#type' => 'item',
          '#markup' => Markup::create($fb->getText()),
          '#weight' => $nr,
        );

        if ($fb->getImage() != '') {
          $form[$wrapper_id]['feedback']['cq-feedback-image' . $nr] = array(
            '#type' => 'item',
            '#markup' => '<img class="cqImageFeedback" src="' . $fb->getImage() . '" />',
            '#weight' => $nr,
          );
        }
      }
    }
  }

  /**
   * Inserts the standard form submit element.
   *
   * It uses Drupal AHAH to replace the feedback item built by insertFeedback().
   *
   * @param array $form
   *   The form into which the submit should be inserted.
   * @param string $feedback_wrapper_id
   *   The HTML id of the feedback's wrapper element.
   *
   * @see insertFeedback()
   */
  public function insertSubmit(array &$form, $feedback_wrapper_id) {
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Submit Answer'),
      '#ajax' => array(
        'callback' => 'closedquestion_submit_answer_js',
        'wrapper' => $feedback_wrapper_id,
        'method' => 'replace',
        'progress' => array('type' => 'throbber', 'message' => t('Please wait...')),
        'event' => 'mousedown',
        'prevent' => 'click',
      ),
    );
  }

  /**
   * Returns the node of this question.
   *
   * @return object
   *   Drupal node object.
   */
  public function getClosedQuestion() {
    return $this->closedQuestion;
  }

  /**
   * Returns a reference to the UserAnswer of this question.
   *
   * @return object
   *   cqUserAnswer object.
   */
  public function getUserAnswer() {
    return $this->userAnswer;
  }

  /**
   * Fires the 'first solution found' event.
   *
   * Inform any listeners that the student found the correct solution for the
   * first time.
   */
  public function fireFirstSolutionFound() {
    foreach ($this->listeners as $listener) {
      $listener->firstSolutionFound($this->userAnswer->getTries());
    }
  }

  /**
   * Fires the 'get extra feedback items' event.
   *
   * Ask any listeners if they want to add additional feedback items to the
   * question.
   *
   * @param \Drupal\closedquestion\Question\CqQuestionInterface $caller
   *   The question that requests extra feedback.
   * @param int $tries
   *   The number of incorrect tries the student did.
   *
   * @return \Drupal\closedquestion\Question\Mapping\CqFeedback[]
   *   Array of additional feedback items.
   */
  public function fireGetExtraFeedbackItems(CqQuestionInterface $caller, $tries) {
    $feedback = array();
    foreach ($this->listeners as $listener) {
      $feedback = array_merge($feedback, $listener->getExtraFeedbackItems($caller, $tries));
    }
    return $feedback;
  }

  /**
   * Registers new widget.
   *
   * @param object $widget
   *   The widget object to register.
   */
  public function registerWidget($widget) {
    $this->supportedwidgets[$widget::WIDGETTYPE] = new $widget();
  }

  /**
   * Returns the URL leading to a Media file by parsing a Media tag.
   *
   * @param string $tagString
   *   A Media Tag [[{"fid": <int> ... }]].
   *
   * @return string
   *   The URL, or $tagString in case no tag could be parsed.
   */
  public function getUrlFromMediaTag($tagString) {
    $matchImgUrlAsArray = json_decode($tagString, TRUE);
    if ($matchImgUrlAsArray !== NULL) {
      $file = File::load($matchImgUrlAsArray[0][0]['fid']);
      return file_create_url($file->getFileUri());
    }
    else {
      return $tagString;
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc