Skip to content
Snippets Groups Projects
UwSmartDate.php 6.49 KiB
Newer Older
<?php

namespace Drupal\uw_migrate\Plugin\migrate\process;

use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Drupal\smart_date_recur\Entity\SmartDateOverride;
use Drupal\smart_date_recur\Entity\SmartDateRule;

/**
 * Custom process plugin to convert date field values into Smart Date values.
 *
 * The plugin also creates SmartDateRule entities, based on recurring rules.
 *
 * @code
 * process:
 *   type:
 *     plugin: uw_smartdate
 *     source: value
 * @endcode
 *
 * @MigrateProcessPlugin(
 *   id = "uw_smartdate",
 *   handle_multiples = TRUE
 * )
 */
class UwSmartDate extends ProcessPluginBase {

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    $new_value = [];
    $prev_item = NULL;

    foreach ($value as $item) {
      $start_value = new DrupalDateTime($item['value'], $item['timezone']);
      $end_value = new DrupalDateTime($item['value2'], $item['timezone']);

      $new_item = [
        'value' => $start_value->getTimestamp(),
        'end_value' => $end_value->getTimestamp(),
        'duration' => (int) ($end_value->getTimestamp() - $start_value->getTimestamp()) / 60,
        'timezone' => $item['timezone'],
      ];

      if (empty($item['rrule'])) {
        $new_value[] = $new_item;
        continue;
      }
      // We don't need to process other field values if rrule is the same.
      // Other event instances will be created by Smart Date based on given
      // parameters. See: smart_date_recur_generate_rows().
      elseif ($prev_item && $prev_item['rrule'] === $item['rrule']) {
      $rrule_parts = $this->getRruleParts($item['rrule']);
      $rule_values = $this->getRuleValues($item['rrule']);
      $rule = SmartDateRule::create($rule_values);

      $new_item += [
        'repeat' => $rule->get('freq')->value,
        'repeat-advanced' => $rule->getParametersArray(),
      ];

      // Duplicate interval value as parent array key because this is where
      // smart_date expects it to be since:
      // https://www.drupal.org/project/smart_date/issues/3176366
      if (isset($new_item['repeat-advanced']['interval'])) {
        $new_item['interval'] = $new_item['repeat-advanced']['interval'];
      }

      if (!empty($rule_values['limit'])) {
        [$limit_type, $limit_value] = explode('=', $rule_values['limit']);
        $new_item['repeat-end'] = $limit_type;
        $new_item['repeat-end-count'] = $limit_type === 'COUNT' ? $limit_value : '';
        $new_item['repeat-end-date'] = $limit_type === 'UNTIL' ? $limit_value : '';
      }
      $new_value[] = $new_item;
      $prev_item = $item;
    }

    if (!empty($item['rrule'])) {
Ivan Doroshenko's avatar
Ivan Doroshenko committed
      // @todo It should be configurable or taken from destination values.
      $entity_type = 'node';
      $bundle = 'uw_ct_event';
      $field = 'field_uw_event_date';

Ivan Doroshenko's avatar
Ivan Doroshenko committed
      // Let SmartDate do some magic and create events based on recurring rules.
      smart_date_recur_generate_rows($new_value, $entity_type, $bundle, $field, 12);

      // Exclude dates provided by EXDATE property.
      if (!empty($rrule_parts['EXDATE'])) {
        $excluded_dates = array_map(function ($date) {
          $date = new DrupalDateTime($date);
          return $date->format('Y-m-d');
        }, explode(',', $rrule_parts['EXDATE']));

        foreach ($new_value as $nw_item) {
          $date = DrupalDateTime::createFromTimestamp($nw_item['value'], $nw_item['timezone'])->format('Y-m-d');

          $index = array_search($date, $excluded_dates);
          if ($index === FALSE) {
            continue;
          }

          $values = [
            'rrule'       => $nw_item['rrule'],
            'rrule_index' => $nw_item['rrule_index'],
          ];

          $override = SmartDateOverride::create($values);
          $override->save();
        }
      }

      // Append dates provided by RDATE property.
      if (!empty($rrule_parts['RDATE'])) {
        $extra_dates = explode(',', $rrule_parts['RDATE']);

        foreach ($extra_dates as $r_date) {
          $new_value[] = $this->calculateDateRange($r_date, $item['value'], $item['value2'], $item['timezone']);
        }
      }
    }

    return $new_value;
  }

  /**
   * Converts rrule string into array expected by SmartDateRule entity.
   *
Ivan Doroshenko's avatar
Ivan Doroshenko committed
   * @param string $rrule
   *   Recurring rule starting with "RRULE:".
   *   Array expected by SmartDateRule::create() method.
  protected function getRuleValues($rrule) {
    foreach ($this->getRruleParts($rrule) as $var_name => $var_value) {
      if ($var_name === 'FREQ') {
        $values['freq'] = $var_value;
      }
      elseif ($var_name === 'COUNT' || $var_name === 'UNTIL') {
        $values['limit'] = "{$var_name}=$var_value";
      }
      else {
        $values['parameters'][] = "{$var_name}=$var_value";
      }
    }
    // Convert parameters into string again.
    $values['parameters'] = implode(';', $values['parameters']);

    return $values;
  }

  /**
   * Splits rrule string into parameters associate array.
   *
Ivan Doroshenko's avatar
Ivan Doroshenko committed
   * @param string $rrule
   *   Recurring rule starting with "RRULE:".
   *
   * @return array
   *   Associative array of rule parameters.
   */
  protected function getRruleParts($rrule) {
    $params = [];
    $rrule = str_replace('RRULE:', '', $rrule);
    $rrule = str_replace(["\n", "\n\r"], ';', $rrule);
    $rrule = str_replace(':', '=', $rrule);
    if ($parts = explode(';', $rrule)) {
      foreach ($parts as $param) {
        [$var_name, $var_value] = explode('=', $param);
        $params[$var_name] = $var_value;
      }
    }
    return $params;
  }

  /**
   * Calculates date range for the given rule.
   */
  protected function calculateDateRange($rule_date, $event_start, $event_end, $timezone) {
    $start_parts = date_parse($event_start);
    $end_parts = date_parse($event_end);

    $start_value = new DrupalDateTime($rule_date, $timezone);
    $end_value = new DrupalDateTime($rule_date, $timezone);
    $start_value->setTime($start_parts['hour'], $start_parts['minute'], $start_parts['second']);
    $end_value->setTime($end_parts['hour'], $end_parts['minute'], $end_parts['second']);

    return [
      'value' => $start_value->getTimestamp(),
      'end_value' => $end_value->getTimestamp(),
      'duration' => (int) ($end_value->getTimestamp() - $start_value->getTimestamp()) / 60,
      'timezone' => $timezone,
    ];
  }