You are here

public function IpGeoLocPluginStyleLeaflet::render in IP Geolocation Views & Maps 8

Transform the View result in a list of marker locations and render on map.

@todo refactor

Overrides StylePluginBase::render

File

src/Plugin/views/style/IpGeoLocPluginStyleLeaflet.php, line 1334

Class

IpGeoLocPluginStyleLeaflet
Views Style plugin extension for Leaflet (if enabled).

Namespace

Drupal\ip_geoloc\Plugin\views\style

Code

public function render() {
  if (empty($this->options['map']) || !($map = $this
    ->pluginStyleLeafletMapGetInfo($this->options['map']))) {
    return $this
      ->t('No Leaflet map was selected or map configuration was not found.');
  }

  // @TODO check if this is the right property
  if (!empty($this->view->preview)) {
    return $this
      ->t('The preview function is incompatible with Leaflet maps so cannot be used. Please visit the page path or the block to view your map.');
  }
  $render_start = microtime(TRUE);
  $this->viewPluginStyle
    ->pluginStyleRenderFields($this);
  $open_balloons_on_click = !empty($this->options['open_balloons_on_click']);
  $open_balloons_on_hover = !empty($this->options['open_balloons_on_hover']);
  $enable_balloons = $open_balloons_on_click || $open_balloons_on_hover;
  $locations = $this->viewPluginStyle
    ->pluginStyleExtractLocations($this, $enable_balloons);
  $this
    ->fillOutLocationRegions($locations);
  $marker_color = $this->options['default_marker']['default_marker_color'];
  $visitor_marker_color = $this->options['visitor_marker_leaflet']['visitor_marker_color'];
  $center_option = !isset($this->options['center_option']) ? 0 : $this->options['center_option'];
  $sync_flags = 0;
  if (!empty($this->options['sync'][LEAFLET_SYNC_CONTENT_TO_MARKER])) {
    $sync_flags |= LEAFLET_SYNC_CONTENT_TO_MARKER;
  }
  if (!empty($this->options['sync'][LEAFLET_SYNC_MARKER_TO_CONTENT])) {
    $sync_flags |= LEAFLET_SYNC_MARKER_TO_CONTENT;
    if (!empty($this->options['sync'][LEAFLET_SYNC_MARKER_TO_CONTENT_WITH_POPUP])) {
      $sync_flags |= LEAFLET_SYNC_MARKER_TO_CONTENT_WITH_POPUP;
    }
    if (!empty($this->options['sync'][LEAFLET_SYNC_REVERT_LAST_MARKER_ON_MAP_OUT])) {
      $sync_flags |= LEAFLET_SYNC_REVERT_LAST_MARKER_ON_MAP_OUT;
    }
  }
  $has_full_screen = !empty($this->options['full_screen']);
  $has_mini_map = !empty($this->options['mini_map']['on']);
  $zoom_indicator = empty($this->options['zoom_indicator']) ? FALSE : TRUE;

  /*array('position' => 'topleft')*/
  $scale_control = FALSE;
  if (!empty($this->options['scale_metric']) || !empty($this->options['scale_imperial'])) {
    $scale_control = [
      'metric' => !empty($this->options['scale_metric']),
      'imperial' => !empty($this->options['scale_imperial']),
    ];
  }
  $goto_content_on_click = !empty($this->options['goto_content_on_click']);
  $reset_control = FALSE;
  if (!empty($this->options['map_reset'])) {
    $label = Xss::filterAdmin($this->options['map_reset_css_class']);
    $reset_control = [
      'label' => empty($label) ? ' ' : $label,
    ];
  }
  $cluster_control = FALSE;
  if ($this->moduleHandler
    ->moduleExists('leaflet_markercluster') && !empty($this->options['map_cluster_toggle'])) {
    $cluster_control = [
      'label' => 'C',
    ];
  }
  $cluster_radius = (int) $this->session
    ->getSessionValue('markercluster-radius');
  if ($cluster_radius < 2) {
    $cluster_radius = (int) $this->options['cluster_radius'];
  }
  $disable_clustering_at_zoom = $this->options['disable_clustering_at_zoom'];
  $hull_hug_factor = empty($this->options['cluster_differentiator']['cluster_outline']) ? -1 : 'auto';
  $cluster_tooltips = !empty($this->options['cluster_differentiator']['cluster_tooltips']);
  $cluster_touch_mode = empty($this->options['cluster_differentiator']['cluster_touch_mode']) ? 0 : 'auto';
  $cluster_aggregation_field = $this->options['cluster_aggregation']['aggregation_field'];
  $cluster_aggregation_function = $this->options['cluster_aggregation']['aggregation_function'];
  $cluster_aggregate_ranges = $this->options['cluster_aggregation']['ranges'];
  $cluster_aggregate_precision = $this->options['cluster_aggregation']['precision'];
  $allow_clusters_of_one = !empty($this->options['allow_clusters_of_one']);
  $tag_css_classes = $this->options['tags']['tag_css_class'];
  $module_path = drupal_get_path('module', 'ip_geoloc');
  $marker_path = file_create_url($this->ipGeolocGlobal
    ->markerDirectory());
  $max_zoom = (int) $this->options['map_options_leaflet']['maxzoom'];
  $zoom = max(1, (int) $this->options['map_options_leaflet']['zoom']);
  $zoom_on_click = (int) $this->options['map_options_leaflet']['zoom_on_click'];
  $scroll_wheel_zoom = (bool) $this->options['map_options_leaflet']['scrollwheelzoom'];
  $dragging = (bool) $this->options['map_options_leaflet']['dragging'];
  $visitor_location = $this->api
    ->getVisitorLocation();
  if (!isset($visitor_location['latitude'])) {
    $connection = \Drupal::database();
    $ip = \Drupal::request()
      ->getClientIp();

    //$query = $connection->select('ip_geoloc')->condition('ip_address', $ip);
    $result = $connection
      ->query('SELECT * FROM {ip_geoloc} WHERE ip_address = :ip_address', [
      ':ip_address' => $ip,
    ], []);
    foreach ($result as $item) {
      $visitor_location = $item;
    }

    // $visitor_location = $query->execute();
  }
  $use_specified_center = !empty($this->options['map_options_leaflet']['center_lat']) && !empty($this->options['map_options_leaflet']['center_lon']) && empty($visitor_location['is_updated']);
  if ($use_specified_center) {
    $map['center'] = [
      'lat' => $this->options['map_options_leaflet']['center_lat'],
      'lon' => $this->options['map_options_leaflet']['center_lon'],
    ];
  }
  elseif (!empty($locations) && ($center_option == IP_GEOLOC_MAP_CENTER_ON_FIRST_LOCATION || $visitor_marker_color == 'none' && count($locations) == 1)) {
    $map['center'] = $this
      ->getCenter(reset($locations));
  }
  elseif (($center_option == IP_GEOLOC_MAP_CENTER_OF_LOCATIONS || $center_option == IP_GEOLOC_MAP_CENTER_OF_LOCATIONS_WEIGHTED) && !empty($locations)) {
    list($center_lat, $center_lon) = $this->api
      ->centerOfLocations($locations, $center_option == IP_GEOLOC_MAP_CENTER_OF_LOCATIONS_WEIGHTED);
    $map['center'] = [
      'lat' => $center_lat,
      'lon' => $center_lon,
    ];
  }
  if (!$use_specified_center && (empty($locations) || $center_option == IP_GEOLOC_MAP_CENTER_ON_VISITOR) && isset($visitor_location['latitude'])) {
    $map['center'] = [
      'lat' => $visitor_location['latitude'],
      'lon' => $visitor_location['longitude'],
    ];
  }
  if (empty($locations)) {
    $ll = trim($this->options['empty_map_center']);
    if (empty($ll)) {

      // No map whatsoever.
      return;
    }
    if ($ll != $this
      ->t('visitor')) {

      // Empty map centered on coordinates provided.
      list($map['center']['lat'], $map['center']['lon']) = preg_split("/[\\s,]+/", $ll);
    }

    // else: empty map centered on visitor location, as set above.
  }
  else {
    uasort($locations, '_ip_geoloc_plugin_style_leaflet_compare');
  }
  $marker_dimensions = explode('x', $this->ipGeolocGlobal
    ->markerDimensions());
  $marker_width = (int) $marker_dimensions[0];
  $marker_height = (int) $marker_dimensions[1];
  $anchor_position = $this->config
    ->get('ip_geoloc_marker_anchor_pos') ? $this->config
    ->get('ip_geoloc_marker_anchor_pos') : 'bottom';
  switch ($anchor_position) {
    case 'top':
      $marker_anchor = 0;
      break;
    case 'middle':
      $marker_anchor = (int) (($marker_height + 1) / 2);
      break;
    default:
      $marker_anchor = $marker_height;
  }
  $features = [];
  foreach ($locations as $location) {
    $feature = [];
    if (isset($location->latitude) || isset($location->lat)) {
      $feature['type'] = 'point';
      $feature['lat'] = isset($location->latitude) ? $location->latitude : $location->lat;
      $feature['lon'] = isset($location->longitude) ? $location->longitude : $location->lon;
      if (!empty($location->random_displacement)) {
        $this->api
          ->addRandomDisplacement($feature, $location->random_displacement);
        $circle = [
          'type' => 'circle',
          'lat' => $feature['lat'],
          'lon' => $feature['lon'],
          'radius' => $location->random_displacement,
        ];
        $features[] = $circle;
      }
    }
    elseif (isset($location->component)) {

      // Possibly parsed by leaflet_process_geofield()
      // see _ip_geoloc_plugin_style_extract_lat_lng().
      $feature['type'] = $location->type;
      $feature['component'] = $location->component;
    }
    elseif (isset($location->points)) {
      $feature['type'] = $location->type;
      $feature['points'] = $location->points;
    }
    if (isset($location->id)) {

      // Allow marker events to identify the corresponding node.
      $feature['feature_id'] = $location->id;
    }

    // At this point $feature['type'] should be set.
    if (!empty($feature['type']) && $feature['type'] != 'point') {

      // Linestring, polygon ...
      $feature['flags'] = LEAFLET_MARKERCLUSTER_EXCLUDE_FROM_CLUSTER;
    }
    elseif (!isset($feature['lat'])) {

      // Points must have coords.
      continue;
    }
    if (!empty($sync_flags)) {
      $feature['flags'] = isset($feature['flags']) ? $feature['flags'] | $sync_flags : $sync_flags;
    }
    if ($enable_balloons && isset($location->balloon_text)) {
      $feature['popup'] = $location->balloon_text;
    }
    if (!empty($location->marker_special_char) || !empty($location->marker_special_char_class)) {
      $has_special_markers = TRUE;
      if (!empty($location->marker_special_char)) {
        $feature['specialChar'] = Xss::filterAdmin($location->marker_special_char);
      }
      if (!empty($location->marker_special_char_class)) {
        $feature['specialCharClass'] = Xss::filterAdmin($location->marker_special_char_class);
      }
    }
    elseif (!empty($this->options['default_marker']['default_marker_special_char']) || !empty($this->options['default_marker']['default_marker_special_char_class'])) {
      $has_special_markers = TRUE;
      $feature['specialChar'] = $this->options['default_marker']['default_marker_special_char'];
      $feature['specialCharClass'] = $this->options['default_marker']['default_marker_special_char_class'];
    }
    if (!empty($location->marker_tooltip)) {
      if ($this->moduleHandler
        ->moduleExists('leaflet_label')) {
        $feature['label'] = $location->marker_tooltip;
      }
      else {
        $has_special_markers = TRUE;
        $feature['tooltip'] = $location->marker_tooltip;
      }
    }
    if (!empty($location->regions)) {
      $has_special_markers = TRUE;

      // Make sure we start with 0 or regions will come across as Object
      // rather than array.
      $feature['regions'] = [
        0 => '',
      ] + $location->regions;
      if (isset($location->aggregation_value)) {
        $feature['aggregationValue'] = (double) $location->aggregation_value;
      }

      // Note: cannot use <br/>  or HTML in tooltip as separator. Use \n.
      $feature['tooltip'] = empty($feature['tooltip']) ? '' : $feature['tooltip'] . "\n";
      $second_last = count($feature['regions']) - 2;
      if ($second_last > 0) {
        $feature['tooltip'] .= $feature['regions'][$second_last] . ' - ';
      }
      $feature['tooltip'] .= end($feature['regions']);
    }
    if (!empty($location->marker_tag)) {
      $has_special_markers = TRUE;
      $feature['tag'] = $location->marker_tag;
    }
    if (!empty($tag_css_classes)) {
      $feature['cssClass'] = $tag_css_classes;
    }
    if (isset($location->marker_color) && $this
      ->isNoMarker($location->marker_color) || !isset($location->marker_color) && $this
      ->isNoMarker($marker_color)) {

      // "No marker" as opposed to "default" marker.
      $has_special_markers = TRUE;
      $feature['icon'] = FALSE;
    }
    elseif (!empty($location->marker_color) || !empty($marker_color)) {

      // Switch from default icon to specified color.
      $color = empty($location->marker_color) ? $marker_color : $location->marker_color;
      $feature['icon'] = [
        'iconUrl' => $marker_path . "/{$color}.png",
        'iconSize' => [
          'x' => $marker_width,
          'y' => $marker_height,
        ],
        'iconAnchor' => [
          'x' => (int) (($marker_width + 1) / 2),
          'y' => $marker_anchor,
        ],
        // Just above topline, center.
        'popupAnchor' => [
          'x' => 0,
          'y' => -$marker_height - 1,
        ],
      ];
    }
    $features[] = $feature;
  }
  if (isset($visitor_location['latitude'])) {
    if ($visitor_marker_color != 'none') {

      // See leaflet/README.txt for examples of Leaflet "features".
      $visitor_feature = [
        'type' => 'point',
        'lat' => $visitor_location['latitude'],
        'lon' => $visitor_location['longitude'],
        'specialChar' => Xss::filterAdmin($this->options['visitor_marker_leaflet']['visitor_marker_special_char']),
        'specialCharClass' => Xss::filterAdmin($this->options['visitor_marker_leaflet']['visitor_marker_special_char_class']),
        'popup' => !empty($visitor_location['popup']) ? $visitor_location['popup'] : $this
          ->t('Your approximate location'),
        'tooltip' => !empty($visitor_location['tooltip']) ? $visitor_location['tooltip'] : $this
          ->t('Your approximate location'),
        'zIndex' => 9999,
        // See leaflet_markercluster.drupal.js.
        'flags' => LEAFLET_MARKERCLUSTER_EXCLUDE_FROM_CLUSTER,
      ];
      if ($visitor_marker_color != '') {
        if (!empty($visitor_feature['specialChar']) || !empty($visitor_feature['specialCharClass'])) {
          $has_special_markers = TRUE;
        }
        $visitor_feature['icon'] = [
          'iconUrl' => $marker_path . "/{$visitor_marker_color}.png",
          'iconSize' => [
            'x' => $marker_width,
            'y' => $marker_height,
          ],
          'iconAnchor' => [
            'x' => (int) (($marker_width + 1) / 2),
            'y' => $marker_anchor,
          ],
          // Just above topline, center.
          'popupAnchor' => [
            'x' => 0,
            'y' => -$marker_height - 1,
          ],
        ];
      }
      $features[] = $visitor_feature;
    }
    if (!empty($this->options['visitor_marker_leaflet']['visitor_marker_accuracy_circle']) && !empty($visitor_location['accuracy'])) {
      $visitor_accuracy_circle = [
        'type' => 'circle',
        'lat' => $visitor_location['latitude'],
        'lon' => $visitor_location['longitude'],
        'radius' => (double) $visitor_location['accuracy'],
        'popup' => !empty($visitor_location['popup']) ? $visitor_location['popup'] : $this
          ->t("You are within @m meters of the centre of this circle.", [
          '@m' => $visitor_location['accuracy'],
        ]),
        // Requires Leaflet Label.
        'label' => !empty($visitor_location['tooltip']) ? $visitor_location['tooltip'] : $this
          ->t('You are within this circle'),
        'zIndex' => 9998,
        'flags' => LEAFLET_MARKERCLUSTER_EXCLUDE_FROM_CLUSTER,
      ];
      $features[] = $visitor_accuracy_circle;
    }
  }

  // If auto-box is chosen ($center_option==0), zoom only when there are
  // 0 or 1 markers [#1863374].
  if (!$use_specified_center && empty($center_option) && count($features) > 1) {
    unset($map['center']);
  }
  else {
    $map['settings']['zoom'] = $zoom;
    if (!empty($map['center'])) {

      // A leaflet.drupal.js quirk? Have to specify AND force a center...
      $map['center']['force'] = TRUE;
    }
  }
  $map['settings']['maxZoom'] = $max_zoom;
  $map['settings']['scrollWheelZoom'] = $scroll_wheel_zoom;
  $map['settings']['dragging'] = $dragging;
  $map['settings']['revertLastMarkerOnMapOut'] = (bool) ($sync_flags & LEAFLET_SYNC_REVERT_LAST_MARKER_ON_MAP_OUT);
  $map['settings']['maxClusterRadius'] = 0;
  if ($cluster_radius > 0) {
    if ($this->moduleHandler
      ->moduleExists('leaflet_markercluster')) {
      $map['settings']['maxClusterRadius'] = $cluster_radius;
      $map['settings']['disableClusteringAtZoom'] = $disable_clustering_at_zoom;
      $map['settings']['addRegionToolTips'] = $cluster_tooltips;
      $map['settings']['hullHugFactor'] = $hull_hug_factor;
      $map['settings']['touchMode'] = $cluster_touch_mode;
      $map['settings']['animateAddingMarkers'] = TRUE;
      if (!empty($cluster_aggregation_field)) {
        $map['settings']['clusterAggregationFunction'] = $cluster_aggregation_function;
        $map['settings']['clusterAggregateRanges'] = $cluster_aggregate_ranges;
        $map['settings']['clusterAggregatePrecision'] = $cluster_aggregate_precision;

        //@TODO check where is this library
        $output['#attached']['library'][] = 'leaflet/markercluster-aggregation';

        //drupal_add_css(leaflet_markercluster_get_library_path() . '/MarkerCluster.Aggregations.css');
      }
      if ($allow_clusters_of_one) {
        $map['settings']['allowClustersOfOne'] = TRUE;
        $map['settings']['spiderfyDistanceMultiplier'] = 4.0;
      }
    }
    else {
      $display = $this->view
        ->getDisplay();
      $display_name = $display
        ->getOption('title') . ' (' . $display->display['display_title'] . ')';
      $this->messenger
        ->addMessage($this
        ->t('Cannot cluster markers in View %display_name, as the module Leaflet MarkerCluster is not enabled.', [
        '%display_name' => $display_name,
      ]), 'warning');
    }
  }
  $zoom_ranges = array_filter($this->options['cluster_differentiator']['zoom_ranges']);
  if (!empty($zoom_ranges)) {

    // Make sure we start array with 0 and no missing elements. Otherwise this
    // array will arrive as an Object on the JS side.
    $region_levels = array_fill(0, $max_zoom + 1, 0);
    foreach ($zoom_ranges as $level => $zoom_range) {
      for ($zoom = 1; $zoom <= $max_zoom; $zoom++) {
        if ($this->ip_geoloc_global
          ->isInRange($zoom, $zoom_range)) {
          $region_levels[$zoom] = $level;
        }
      }
    }

    // Remove any gaps and zeroes.
    for ($z = 1; $z <= $max_zoom; $z++) {
      if (empty($region_levels[$z])) {
        $region_levels[$z] = $region_levels[$z - 1];
      }
    }
    $map['settings']['regionLevels'] = $region_levels;
  }

  // @TODO Check this code for displayid
  $map_id = 'ip-geoloc-map-of-view-' . $this->view->id . '-' . $this->view
    ->getDisplay()->display['display_id'] . '-' . md5(serialize($features));
  $output['#attached']['library'][] = 'leaflet/leaflet-drupal';

  // Don't load sync JS and CSS when option is not requested.
  if ($sync_flags !== 0) {
    $output['#attached']['library'][] = 'ip_geoloc/sync_content';
  }
  if ($has_full_screen) {

    // Load the 'leaflet-fullscreen' library, containing JS and CSS.
    // @TODO check libraries integration

    //@TODO check where is this library
    $output['#attached']['library'][] = 'ip_geoloc/leaflet-fullscreen';
    $map['settings']['fullscreenControl'] = [
      'position' => 'topright',
    ];
  }
  if ($has_mini_map) {

    // Load the 'leaflet-minimap' library, containing JS and CSS.
    // See https://github.com/Norkart/Leaflet-MiniMap for more settings.
    // @TODO add minimap library
    $output['#attached']['library'][] = 'ip_geoloc/leaflet-minimap';

    /*if (drupal_add_library('ip_geoloc', 'leaflet-minimap')) {*/
    $map['settings']['miniMap'] = [
      'autoToggleDisplay' => TRUE,
      'height' => $this->options['mini_map']['height'],
      'width' => $this->options['mini_map']['width'],
      'position' => 'bottomright',
      // 'bottomright'
      'toggleDisplay' => !empty($this->options['mini_map']['toggle']),
      'zoomAnimation' => FALSE,
      'zoomLevelOffset' => (int) $this->options['mini_map']['zoom_delta'],
      // Superimposed rectangle showing extent of main map on the inset.
      'aimingRectOptions' => [
        'color' => $this->options['mini_map']['scope_color'],
        'weight' => 3,
        'fillOpacity' => 0.1,
      ],
      // The "shadow" rectangle that shows the new map outline.
      'shadowRectOptions' => [
        'color' => '#888',
        'weight' => 1,
      ],
    ];

    /*}*/
  }
  $map['settings']['zoomIndicator'] = $zoom_indicator;
  $map['settings']['zoomOnClick'] = $zoom_on_click;
  $map['settings']['resetControl'] = $reset_control;
  $map['settings']['clusterControl'] = $cluster_control;
  $map['settings']['scaleControl'] = $scale_control;
  if ($has_mini_map || $zoom_indicator || $reset_control || $cluster_control || $scale_control) {
    $output['#attached']['library'][] = 'ip_geoloc/leaflet_controls';

    /*drupal_add_js($module_path . '/js/ip_geoloc_leaflet_controls.js', array('weight' => 1));
      drupal_add_css($module_path . '/css/ip_geoloc_leaflet_controls.css');*/
  }
  $map['settings']['openBalloonsOnHover'] = $open_balloons_on_hover;
  $map['settings']['gotoContentOnClick'] = $goto_content_on_click;
  if ($open_balloons_on_hover || $goto_content_on_click) {
    $output['#attached']['library'][] = 'ip_geoloc/content_on_click';
  }
  $settings = [
    'mapid' => $map_id,
    'map' => $map,
    'features' => $features,
  ];
  $output['#attached']['drupalSettings']['leaflet'] = array(
    $settings,
  );

  // @TODO check how to send variales to the libray
  // drupal_add_js(array('leaflet' => array($settings)), $this->options);
  $output['#attached']['library'][] = 'leaflet/leaflet';

  // @TODO not sure if this is migrated
  // libraries_load('leaflet');
  // Little hacky this, but can't see another way to load libraries for
  // Leaflet More Maps, Leaflet MarkerCluster, Leaflet Hash...
  // drupal_alter('leaflet_map_prebuild', $settings);
  if ($reset_control || $cluster_control || !empty($has_special_markers)) {

    // Load the CSS that comes with the font icon library which in return
    // tells the browser to fetch either the WOFF, TTF or SVG files that
    // define the font faces.
    $output['#attached']['library'][] = 'ip_geoloc/ip_geoloc_font_icon_libs';
    $variables['#attached']['library'][] = 'ip_geoloc/leaflet_markers';
  }
  if ($zoom_on_click) {
    $variables['#attached']['library'][] = 'ip_geoloc/leaflet_zoom_on_click';

    // drupal_add_js($module_path . '/js/ip_geoloc_leaflet_zoom_on_click.js', array('scope' => 'footer'));.
  }

  // drupal_alter('leaflet_build_map', $build); // @todo [#2567391].
  $marker_set = $this->ipGeolocGlobal
    ->markerDirectory();
  $marker_set = Unicode::substr($marker_set, strrpos($marker_set, '/') + 1);
  $height = trim($this->options['map_height']) ? trim($this->options['map_height']) : '300';
  $height = empty($height) ? '300px' : (is_numeric($height) ? $height . 'px' : trim($height));
  $style = Unicode::substr($height, 0, 6) == '<none>' ? '' : ' style="height:' . SafeMarkup::checkPlain($height) . '"';
  $output = $output + [
    '#theme' => 'ip_geoloc_leaflet',
    '#map_id' => $map_id,
    //'#view' => $this->view,
    '#marker_set' => $marker_set,
    '#style' => $style,
  ];
  $this->ipGeolocGlobal
    ->debug($this
    ->t('-- Leaflet map preparation time: %sec s', [
    '%sec' => number_format(microtime(TRUE) - $render_start, 2),
  ]));
  return \Drupal::service('renderer')
    ->render($output);

  //return $output;
}