Error message

  • Warning: count(): Parameter must be an array or an object that implements Countable in _api_make_match_member_link() (line 1170 of /var/www/api-visualn-org/htdocs/sites/all/modules/contrib/api/
  • Warning: count(): Parameter must be an array or an object that implements Countable in _api_make_match_member_link() (line 1170 of /var/www/api-visualn-org/htdocs/sites/all/modules/contrib/api/

class EmbedDrawingFilter

Provides a filter to display embedded VisualN drawings based on data attributes.

Plugin annotation

  id = "visualn_drawing_embed",
  title = @Translation("VisualN Drawing Embedded"),
  description = @Translation("Embeds VisualN drawings using data attributes: data-entity-type, data-entity-uuid, and data-view-mode."),
  type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE


  • class \Drupal\visualn_embed\Plugin\Filter\EmbedDrawingFilter extends \Drupal\filter\Plugin\FilterBase

Expanded class hierarchy of EmbedDrawingFilter

Related topics


modules/visualn_embed/src/Plugin/Filter/EmbedDrawingFilter.php, line 27


View source
class EmbedDrawingFilter extends FilterBase {

  // @todo: move to iframe content provider
  const IFRAME_HANDLER_KEY = 'visualn_embed_key';

  // @todo: implement tips() method
  //   see core/modules/filter/src/Plugin/Filter/FilterAlign.php

  //use DomHelperTrait;

   * {@inheritdoc}
  public function process($text, $langcode) {
    $result = new FilterProcessResult($text);

    // @todo: entity_embed uses attributes
    if (strpos($text, 'data-visualn-drawing-id')) {
      $dom = Html::load($text);
      $xpath = new \DOMXPath($dom);

      //foreach ($xpath->query('//drupal-entity[@data-entity-type and (@data-entity-uuid or @data-entity-id) and (@data-entity-embed-display or @data-view-mode)]') as $node) {
      foreach ($xpath
        ->query('//drupal-visualn-drawing[@data-visualn-drawing-id]') as $node) {
        $drawing_id = $node

        // @todo: trim width and height if needed
        $width = $node
        $height = $node

        // @todo: make sure that it is array or empty
        $settings = $node

        // @todo: check if settings is an array
        $settings = json_decode($settings, TRUE);
        $shared = is_array($settings) && isset($settings['shared']) ? $settings['shared'] : FALSE;

        // @todo: maybe check if ::hasAttirbute() before

        // @todo: review attribute name
        $hash = $node

        // @todo: if additional classes set in attributes, they should be added to existing ones
        //   but not replace them. Also try to reuse existing ckeditor buttons
        //   instead of adding it to the properties form.
          ->changeNodeName($node, 'div');
        $entity = \Drupal::entityTypeManager()

        // @todo: add cachePerPermissions() to the drawing build
        if (!empty($entity) && $entity
          ->access('view')) {

          // @todo: use only required parameters (width and height)
          //   implement getWindowParamtersFromSettings($settings)
          //   validate or clean values if needed
          //   @see \Drupal\visualn\Core\DrawerBase::setWindowParameters()
          $window_parameters = [
            'width' => $width,
            'height' => $height,
          $window_parameters = array_filter($window_parameters);
          $drawing_markup = $entity

          // @todo: check allow_drawings_sharing setting or maybe add an additional setting
          //   to show/hide already exposed share links
          //   reset only required cache tags then (e.g. changing default link title
          //   should not reset the cache here)
          if (\Drupal::service('module_handler')
            ->moduleExists('visualn_iframe')) {

            // @todo: convert into a service
            $share_link_builder = \Drupal::service('visualn_iframe.builder');
            if (!empty($shared)) {

              // @todo: also check first if hash is set in node attributes
              //   if hash not set or an id (iframe entry) doesn't exist for that hash,
              //   generate a new one (with default settings)
              //   and also (possibly) log an error (or warning) about empty hash used in ckeditor
              //   widget properties
              // @todo: get current path and possibly route, add column(s) to the
              //   visualn_iframe entity table structure
              //   see
              $additional_config = \Drupal::config('visualn_embed.iframe.settings');
              if (empty($hash)) {

                // Ignore settings and get the 'default' iframe entry
                // since on cache clear it will generate yet another hash (because
                // it is impossible to add it to source html tag attributes at Filter
                // plugin level) level.
                // @todo: also don't override path and route (of page displayed)
                //   or leave even empty for default entry ?
                // get or create a new 'default' iframe entry based on drawing_id
                $create = $additional_config

                // settings are not used for *default* iframe entries
                // even if set in editor since it would require to create
                // a new additional iframe *default* entry every time settings change
                $iframe_entity = \Drupal::service('visualn_iframe.builder')
                  ->getIFrameEntityByTargetId($drawing_id, $create);
                if ($iframe_entity == FALSE) {

                  // @todo: it also shows that drawing not found
                else {
                  $hash = $iframe_entity
              else {
                $iframe_entity = VisualNIFrame::getIFrameEntityByHash($hash);
                if (!$iframe_entity) {

                  // check if implicit_entries_restore is enabled for
                  //   visualn_embed iframe settings (see visualn_embed.module)
                  if ($additional_config
                    ->get('implicit_entries_restore')) {
                    $data = [
                      'drawing_id' => $drawing_id,
                    $params = [
                      'drawing_id' => $drawing_id,
                      'hash' => $hash,
                      // @todo: is status required?
                      'status' => 1,
                      // @todo: check
                      'langcode' => 'en',
                      // @todo: use drawing name with possibly some other info for the
                      //   iframe name here
                      //   every other handler creating iframes, may create iframe names
                      //   of its own choice
                      'name' => $entity
                      // @todo: actually the user should be the one who creates the entry
                      //   but not the one who views page with the entry (e.g. anon users)
                      // @todo: at least log this
                      //   set to the current user ?
                      'user_id' => 1,
                      // @todo: check
                      'settings' => $settings,
                      'data' => $data,
                      'displayed' => TRUE,
                      'location' => \Drupal::service('path.current')
                      'viewed' => FALSE,
                      'handler_key' => static::IFRAME_HANDLER_KEY,
                      'implicit' => TRUE,
                    $iframe_entity = \Drupal::service('visualn_iframe.builder')
                else {
                  $update = FALSE;

                  // compare settings array, update if necessary
                  // @todo: maybe store (and compare for update) only settings required for iframe
                  // @todo: use getSettings() and json_encode to compare
                  //   or just update in any way
                  //   though it doesn't allow to track who changes the iframe_entity since done via
                  //   html filter
                  //   also better compare by keys as arrays
                  $stored_settings = $iframe_entity

                  //$stored_settings = $iframe_entity->get('settings')->value;
                  $location = $iframe_entity
                  if (empty($location)) {
                    $location = \Drupal::service('path.current')
                    $update = TRUE;

                  // @todo: check visualn_iframe_stage table for a staged entry
                  //    usort and serialize arrays to compare
                  if ($stored_settings != $settings) {

                    // @todo: this would set inline settings without checking staged entry,
                    //   so if really needed a special options should be added to
                    //   Iframe Settings page or a permission


                    //$update = TRUE;
                    $staged_settings = $share_link_builder

                    // If staged settings entry exists, compare current
                    // inline settings the staged versions and if they are
                    // the same then allowed to use inline settings and update
                    // the visualn_iframe entry itself.
                    if (!empty($staged_settings)) {
                      if ($staged_settings == $settings) {
                        $update = TRUE;

                    // Remove the staged version in any case.
                    // It was either used to updated the visualn_iframe entry or
                    // not used at all so no need to keep it. It may happen e.g. when
                    // user chaged settings via embedded drawing config form
                    // but didn't save the changes.
                  else {

                    // remove staged entry (if any)
                  if ($update) {

              // do not create link if implicit_entries_restore is not allowed
              //   and thus there is no entry to use for users
              if ($iframe_entity) {
                $iframe_url = $share_link_builder
                $share_link = $share_link_builder
              else {
                $share_link = [];

              // This shouldn't block staged iframe entries checking
              //   otherwise the if() could be moved to the upper level
              //   (see visualn_block VisualNBlock::build())
              if ($additional_config
                ->get('allow_drawings_sharing')) {

                // @todo: use a template (possibly inline initially)
                $drawing_markup = [
                  'drawing_markup' => $drawing_markup,
                  'share_link' => $share_link,

              // It will not work in case visualn_iframe was enabled, a drawing
              // configured to use sharing, then visualn_iframe disabled, drawing
              // properties changed (here the tag doesn't get attached) and then
              // visualn_iframe re-enabled and allow_drawings_sharing setting
              // submitted - it won't reset the cache, though it is an edge case.
              $drawing_markup['#cache']['tags'][] = 'visualn_embed_iframe_settings';

              // invalidate cache tag e.g. on entity delete, to restore it if allowed
              if ($iframe_entity) {
                $drawing_markup['#cache']['tags'][] = 'visualn_iframe:' . $iframe_entity

          // @todo: add cache tags for the cases when drawing entity was changed
          // @todo: doesn't show in ckeditor window due to js and css libraries not being attached,
          //   should be fixed in plugin.js
          $entity_output = \Drupal::service('renderer')
        else {

          // this will also work if user has no permission to view the drawing
          // @todo: return some better markup or even empty output
          // @todo: add some background opaque image to empty drawings markup
          $img_path = '/' . drupal_get_path('module', 'visualn_embed') . '/images/paint-brush-solid.svg';
          if (!empty($entity) && !$entity
            ->access('view')) {
            $text = $this
              ->t('You don\'t have permission to view the drawing');
          else {
            $text = $this
              ->t('Drawing not found');
          $empty_drawing_markup = [
            '#markup' => '<div class="text">' . $text . '</div><div class="bkg"><img src="' . $img_path . '" /></div>',
            '#attached' => [
              'library' => [
          $entity_output = \Drupal::service('renderer')

          // Add 'drawing not found' wrapper class to apply background image
          // so that width and height properties would be respected (if set).
          // Position drawing not found markup at the wrapper div center.
          $classes = $node
          $classes = strlen($classes) > 0 ? explode(' ', $classes) : [];
          $classes[] = 'visualn-drawing-empty-drawing-wrapper';
            ->setAttribute('class', implode(' ', $classes));

          // @todo: Depending on module settings, return a 'Drawing not found' markup
          //   or empty output. Also the the markup could be themable in the first case
          //   so that themers could adjust it according to the site design.

        // The align and dimenstions (and any other) properties should apply to valid drawings
        // and empty drawing case as well.
        $style_properties = [];
        $style_attr_values = [
          // @todo: what if user wants to set units other than pixels
          'width' => !empty($width) ? "{$width}px" : '',
          'height' => !empty($height) ? "{$height}px" : '',
        foreach ($style_attr_values as $attr => $value) {
          if (!empty($value)) {
            $style_properties[] = implode(':', [

        // @todo: also check if style attribute is already set and concatinate in that case
        //   instead of overriding
        //   and also check if width and heigh properties are there - replace them in that case
        if ($style_properties) {
          $style = implode('; ', $style_properties) . ';';
            ->setAttribute('style', $style);
          ->setNodeContent($node, $entity_output);
    return $result;

   * Rename a DOMNode tag.
   * @note: This is a copy-paste of Drupal\embed\DomHelperTrait::changeNodeName()
   *   to avoid Embed contib module dependecy
   * @param \DOMNode $node
   *   A DOMElement object.
   * @param string $name
   *   The new tag name.
  protected function changeNodeName(\DOMNode &$node, $name = 'div') {
    if ($node->nodeName != $name) {

      /** @var \DOMElement $replacement_node */
      $replacement_node = $node->ownerDocument

      // Copy all children of the original node to the new node.
      if ($node->childNodes->length) {
        foreach ($node->childNodes as $child) {
          $child = $replacement_node->ownerDocument
            ->importNode($child, TRUE);

      // Copy all attributes of the original node to the new node.
      if ($node->attributes->length) {
        foreach ($node->attributes as $attribute) {
            ->setAttribute($attribute->nodeName, $attribute->nodeValue);
        ->replaceChild($replacement_node, $node);
      $node = $replacement_node;

   * Set the contents of a DOMNode.
   * @param \DOMNode $node
   *   A DOMNode object.
   * @param string $content
   *   The text or HTML that will replace the contents of $node.
  protected function setNodeContent(\DOMNode $node, $content) {

    // Remove all children of the DOMNode.
    while ($node
      ->hasChildNodes()) {
    if (strlen($content)) {

      // Load the contents into a new DOMDocument and retrieve the elements.
      $replacement_nodes = Html::load($content)

      // Finally, import and append the contents to the original node.
      foreach ($replacement_nodes->childNodes as $replacement_node) {
        $replacement_node = $node->ownerDocument
          ->importNode($replacement_node, TRUE);



Namesort descending Modifiers Type Description Overrides
EmbedDrawingFilter::changeNodeName protected function Rename a DOMNode tag.
EmbedDrawingFilter::IFRAME_HANDLER_KEY constant
EmbedDrawingFilter::process public function
EmbedDrawingFilter::setNodeContent protected function Set the contents of a DOMNode.