Skip to content
Snippets Groups Projects
AbstractCivicrmEntitySource.php 14.4 KiB
Newer Older
jaapjansma's avatar
jaapjansma committed
<?php
/**
 * @author Jaap Jansma <jaap.jansma@civicoop.org>
 * @license AGPL-3.0
 */

namespace Civi\DataProcessor\Source;

use Civi\DataProcessor\DataFlow\MultipleDataFlows\JoinInterface;
jaapjansma's avatar
jaapjansma committed
use Civi\DataProcessor\DataFlow\SqlDataFlow\SimpleWhereClause;
use Civi\DataProcessor\DataFlow\SqlTableDataFlow;
use Civi\DataProcessor\DataFlow\CombinedDataFlow\CombinedSqlDataFlow;
use Civi\DataProcessor\DataFlow\MultipleDataFlows\DataFlowDescription;
use Civi\DataProcessor\DataFlow\MultipleDataFlows\SimpleJoin;
use Civi\DataProcessor\DataSpecification\AggregationField;
jaapjansma's avatar
jaapjansma committed
use Civi\DataProcessor\DataSpecification\CustomFieldSpecification;
use Civi\DataProcessor\DataSpecification\DataSpecification;
use Civi\DataProcessor\DataSpecification\FieldSpecification;
use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
jaapjansma's avatar
jaapjansma committed
abstract class AbstractCivicrmEntitySource extends AbstractSource {
jaapjansma's avatar
jaapjansma committed

  /**
   * @var \Civi\DataProcessor\DataFlow\SqlDataFlow
   */
  protected $dataFlow;

  /**
   * @var \Civi\DataProcessor\DataFlow\SqlDataFlow
   */
  protected $primaryDataFlow;

  /**
   * @var \Civi\DataProcessor\DataSpecification\DataSpecification
   */
  protected $availableFields;

  /**
   * @var \Civi\DataProcessor\DataSpecification\DataSpecification
   */
  protected $availableFilterFields;

  /**
   * @var array
   */
  protected $whereClauses = array();


  /**
   * @var array<\Civi\DataProcessor\DataFlow\MultipleDataFlows\DataFlowDescription>
   */
  protected $customGroupDataFlowDescriptions = array();

  /**
   * @var array<\Civi\DataProcessor\DataFlow\MultipleDataFlows\DataFlowDescription>
   */
  protected $additionalDataFlowDescriptions = array();

  /**
   * @var AbstractProcessorType
   */
  protected $dataProcessor;

  /**
   * @var array
   */
  protected $configuration;

jaapjansma's avatar
jaapjansma committed
  /**
   * Returns the entity name
   *
   * @return String
   */
  abstract protected function getEntity();

  /**
   * Returns the table name of this entity
   *
   * @return String
   */
  abstract protected function getTable();


  /**
   * Initialize this data source.
   *
   * @throws \Exception
   */
  public function initialize() {
    if (!$this->primaryDataFlow) {
jaapjansma's avatar
jaapjansma committed
      $this->primaryDataFlow = new SqlTableDataFlow($this->getTable(), $this->getSourceName(), $this->getSourceTitle());
jaapjansma's avatar
jaapjansma committed
    $this->addFilters($this->configuration);
    if (count($this->customGroupDataFlowDescriptions) || count($this->additionalDataFlowDescriptions)) {
      $this->dataFlow = new CombinedSqlDataFlow('', $this->primaryDataFlow->getTable(), $this->primaryDataFlow->getTableAlias());
      $this->dataFlow->addSourceDataFlow(new DataFlowDescription($this->primaryDataFlow));
      foreach ($this->additionalDataFlowDescriptions as $additionalDataFlowDescription) {
        $this->dataFlow->addSourceDataFlow($additionalDataFlowDescription);
jaapjansma's avatar
jaapjansma committed
      }
jaapjansma's avatar
jaapjansma committed
      foreach ($this->customGroupDataFlowDescriptions as $customGroupDataFlowDescription) {
        $this->dataFlow->addSourceDataFlow($customGroupDataFlowDescription);
      }
jaapjansma's avatar
jaapjansma committed
    else {
jaapjansma's avatar
jaapjansma committed
      $this->dataFlow = $this->primaryDataFlow;
jaapjansma's avatar
jaapjansma committed
    }
  protected function reset() {
jaapjansma's avatar
jaapjansma committed
    $this->primaryDataFlow = new SqlTableDataFlow($this->getTable(), $this->getSourceName(), $this->getSourceTitle());
    $this->dataFlow = null;
    $this->additionalDataFlowDescriptions = array();
jaapjansma's avatar
jaapjansma committed
  }

  /**
   * Load the fields from this entity.
   *
   * @param DataSpecification $dataSpecification
   * @throws \Civi\DataProcessor\DataSpecification\FieldExistsException
   */
  protected function loadFields(DataSpecification $dataSpecification, $fieldsToSkip=array()) {
    $dao = \CRM_Core_DAO_AllCoreTables::getFullName($this->getEntity());
    $bao = str_replace("DAO", "BAO", $dao);
    $fields = $dao::fields();
    foreach($fields as $field) {
      if (in_array($field['name'], $fieldsToSkip)) {
        continue;
      }
      $type = \CRM_Utils_Type::typeToString($field['type']);
jaapjansma's avatar
jaapjansma committed
      $options = $dao::buildOptions($field['name']);
jaapjansma's avatar
jaapjansma committed
      $alias = $this->getSourceName(). '_'.$field['name'];
      $fieldSpec = new FieldSpecification($field['name'], $type, $field['title'], $options, $alias);
      $dataSpecification->addFieldSpecification($fieldSpec->name, $fieldSpec);
    }
  }

  /**
   * Add custom fields to the available fields section
   *
   * @param DataSpecification $dataSpecification
   * @param bool $onlySearchAbleFields
   * @throws \Civi\DataProcessor\DataSpecification\FieldExistsException
   */
  protected function loadCustomGroupsAndFields(DataSpecification $dataSpecification, $onlySearchAbleFields) {
    $customGroupToReturnParam = array(
      'custom_field' => array(
        'id',
        'name',
        'label',
        'column_name',
        'data_type',
        'html_type',
        'default_value',
        'attributes',
        'is_required',
        'is_view',
        'is_searchable',
        'help_pre',
        'help_post',
        'options_per_line',
        'start_date_years',
        'end_date_years',
        'date_format',
        'time_format',
        'option_group_id',
        'in_selector',
      ),
      'custom_group' => array(
        'id',
        'name',
        'table_name',
        'title',
        'help_pre',
        'help_post',
        'collapse_display',
        'style',
        'is_multiple',
        'extends',
        'extends_entity_column_id',
        'extends_entity_column_value',
        'max_multiple',
      ),
    );
    $customGroups = \CRM_Core_BAO_CustomGroup::getTree($this->getEntity(), $customGroupToReturnParam,NULL,NULL,NULL,NULL,NULL,NULL,TRUE,FALSE,FALSE);
jaapjansma's avatar
jaapjansma committed
    foreach($customGroups as $cgId => $customGroup) {
      if ($cgId == 'info') {
        continue;
      }
      foreach($customGroup['fields'] as $field) {
        if (!$onlySearchAbleFields || $field['is_searchable']) {
          $alias = $this->getSourceName() . '_' . $customGroup['name'] . '_' . $field['name'];
          $customFieldSpec = new CustomFieldSpecification(
            $customGroup['name'], $customGroup['table_name'], $customGroup['title'],
            $field['id'], $field['column_name'], $field['name'], $field['data_type'], $field['label'],
            $alias
          );
          $dataSpecification->addFieldSpecification($customFieldSpec->name, $customFieldSpec);
        }
      }
    }
  }

  /**
   * Add the filters to the where clause of the data flow
   *
   * @param $configuration
   * @throws \Exception
   */
  protected function addFilters($configuration) {
    if (isset($configuration['filter']) && is_array($configuration['filter'])) {
      foreach($configuration['filter'] as $filter_alias => $filter_field) {
jaapjansma's avatar
jaapjansma committed
        $this->addFilter($filter_alias, $filter_field['op'], $filter_field['value']);
jaapjansma's avatar
jaapjansma committed
      }
    }
  }

  /**
   * Adds an inidvidual filter to the data source
   *
jaapjansma's avatar
jaapjansma committed
   * @param $filter_field_alias
   * @param $op
   * @param $values
jaapjansma's avatar
jaapjansma committed
   *
   * @throws \Exception
   */
jaapjansma's avatar
jaapjansma committed
  protected function addFilter($filter_field_alias, $op, $values) {
    if ($this->getAvailableFilterFields()->doesFieldExist($filter_field_alias)) {
      $spec = $this->getAvailableFilterFields()->getFieldSpecificationByName($filter_field_alias);
jaapjansma's avatar
jaapjansma committed
      if ($spec instanceof CustomFieldSpecification) {
        $customGroupDataFlow = $this->ensureCustomGroup($spec->customGroupTableName, $spec->customGroupName);
        $customGroupTableAlias = $customGroupDataFlow->getTableAlias();
        $customGroupDataFlow->addWhereClause(
jaapjansma's avatar
jaapjansma committed
          new SimpleWhereClause($customGroupTableAlias, $spec->customFieldColumnName, $op, $values)
jaapjansma's avatar
jaapjansma committed
        );
      } else {
        $entityDataFlow = $this->ensureEntity();
jaapjansma's avatar
jaapjansma committed
        $entityDataFlow->addWhereClause(new SimpleWhereClause($this->getSourceName(), $spec->name,$op, $values));
      }
    }
  }

  /**
   * Ensure that filter field is accesible in the query
   *
   * @param String $fieldName
jaapjansma's avatar
jaapjansma committed
   * @return \Civi\DataProcessor\DataFlow\AbstractDataFlow|null
   * @throws \Exception
   */
  public function ensureField($fieldName) {
    if ($this->getAvailableFilterFields()->doesFieldExist($fieldName)) {
      $spec = $this->getAvailableFilterFields()->getFieldSpecificationByName($fieldName);
      if ($spec instanceof CustomFieldSpecification) {
        return $this->ensureCustomGroup($spec->customGroupTableName, $spec->customGroupName);
jaapjansma's avatar
jaapjansma committed
      return $this->ensureEntity();
jaapjansma's avatar
jaapjansma committed
    }
  }

  /**
   * Ensure a custom group is added the to the data flow.
   *
   * @param $customGroupTableName
   * @param $customGroupName
   * @return \Civi\DataProcessor\DataFlow\AbstractDataFlow
   * @throws \Exception
jaapjansma's avatar
jaapjansma committed
   */
  protected function ensureCustomGroup($customGroupTableName, $customGroupName) {
    if (isset($this->customGroupDataFlowDescriptions[$customGroupName])) {
      return $this->customGroupDataFlowDescriptions[$customGroupName]->getDataFlow();
    } elseif ($this->primaryDataFlow && $this->primaryDataFlow->getTable() == $customGroupTableName) {
      return $this->primaryDataFlow;
    $customGroupTableAlias = $this->getSourceName().'_'.$customGroupName;
    $join = new SimpleJoin($this->getSourceName(), 'id', $customGroupTableAlias, 'entity_id', 'LEFT');
    $join->setDataProcessor($this->dataProcessor);
    $this->customGroupDataFlowDescriptions[$customGroupName] = new DataFlowDescription(
      new SqlTableDataFlow($customGroupTableName, $customGroupTableAlias, new DataSpecification()),
      $join
    );
    return $this->customGroupDataFlowDescriptions[$customGroupName]->getDataFlow();
  }

  /**
   * Ensure that the entity table is added the to the data flow.
   *
   * @return \Civi\DataProcessor\DataFlow\AbstractDataFlow
   * @throws \Exception
   */
  protected function ensureEntity() {
    if ($this->primaryDataFlow && $this->primaryDataFlow->getTable() === $this->getTable()) {
      return $this->primaryDataFlow;
    } elseif (empty($this->primaryDataFlow)) {
      $this->primaryDataFlow = new SqlTableDataFlow($this->getTable(), $this->getSourceName());
      return $this->primaryDataFlow;
    }
    foreach($this->additionalDataFlowDescriptions as $additionalDataFlowDescription) {
      if ($additionalDataFlowDescription->getDataFlow()->getTable() == $this->getTable()) {
        return $additionalDataFlowDescription->getDataFlow();
      }
    }
    $entityDataFlow = new SqlTableDataFlow($this->getTable(), $this->getSourceName());
    $join = new SimpleJoin($this->getSourceName(), 'id', $this->primaryDataFlow->getTableAlias(), 'entity_id', 'LEFT');
    $join->setDataProcessor($this->dataProcessor);
    $additionalDataFlowDescription = new DataFlowDescription($entityDataFlow,$join);
    $this->additionalDataFlowDescriptions[] = $additionalDataFlowDescription;
    return $additionalDataFlowDescription->getDataFlow();
  }

  /**
   * Sets the join specification to connect this source to other data sources.
   *
   * @param \Civi\DataProcessor\DataFlow\MultipleDataFlows\JoinInterface $join
   *
   * @return \Civi\DataProcessor\Source\SourceInterface
   */
  public function setJoin(JoinInterface $join) {
    foreach($this->customGroupDataFlowDescriptions as $idx => $customGroupDataFlowDescription) {
      if ($join->worksWithDataFlow($customGroupDataFlowDescription->getDataFlow())) {
        $this->primaryDataFlow = $customGroupDataFlowDescription->getDataFlow();
        unset($this->customGroupDataFlowDescriptions[$idx]);
        unset($this->dataFlow);
      }
    }
    return $this;
jaapjansma's avatar
jaapjansma committed
  }

  /**
   * @return \Civi\DataProcessor\DataSpecification\DataSpecification
   * @throws \Exception
   */
  public function getAvailableFields() {
    if (!$this->availableFields) {
      $this->availableFields = new DataSpecification();
      $this->loadFields($this->availableFields);
      $this->loadCustomGroupsAndFields($this->availableFields, false);
    }
    return $this->availableFields;
  }

  /**
   * @return \Civi\DataProcessor\DataSpecification\DataSpecification
   * @throws \Exception
   */
  public function getAvailableFilterFields() {
    if (!$this->availableFilterFields) {
      $this->availableFilterFields = new DataSpecification();
      $this->loadFields($this->availableFilterFields);
      $this->loadCustomGroupsAndFields($this->availableFilterFields, true);
    }
    return $this->availableFilterFields;
  }

  /**
   * @return \Civi\DataProcessor\DataSpecification\AggregationField[]
   * @throws \Exception
   */
  public function getAvailableAggregationFields() {
    $fields = $this->getAvailableFields();
    $aggregationFields = array();
    foreach($fields->getFields() as $field) {
      $aggregationFields[$field->alias] = new AggregationField($field, $this);
    }
    return $aggregationFields;
  }

jaapjansma's avatar
jaapjansma committed
  /**
   * Ensures a field is in the data source
   *
   * @param \Civi\DataProcessor\DataSpecification\FieldSpecification $fieldSpecification
   * @return SourceInterface
   * @throws \Exception
   */
  public function ensureFieldInSource(FieldSpecification $fieldSpecification) {
    if ($this->getAvailableFields()->doesFieldExist($fieldSpecification->name)) {
jaapjansma's avatar
jaapjansma committed
      if ($fieldSpecification instanceof CustomFieldSpecification) {
        $customGroupDataFlow = $this->ensureCustomGroup($fieldSpecification->customGroupTableName, $fieldSpecification->customGroupName);
        $customGroupDataFlow->getDataSpecification()->addFieldSpecification($fieldSpecification->alias, $fieldSpecification);
jaapjansma's avatar
jaapjansma committed
      } else {
        $entityDataFlow = $this->ensureEntity();
        $entityDataFlow->getDataSpecification()->addFieldSpecification($fieldSpecification->alias, $fieldSpecification);
  /**
   * Ensures an aggregation field in the data source
   *
   * @param \Civi\DataProcessor\DataSpecification\FieldSpecification $fieldSpecification
   * @return \Civi\DataProcessor\Source\SourceInterface
   * @throws \Exception
   */
  public function ensureAggregationFieldInSource(FieldSpecification $fieldSpecification) {
    if ($this->getAvailableFields()->doesFieldExist($fieldSpecification->name)) {
      if ($fieldSpecification instanceof CustomFieldSpecification) {
        $customGroupDataFlow = $this->ensureCustomGroup($fieldSpecification->customGroupTableName, $fieldSpecification->customGroupName);
        $customGroupDataFlow->addAggregateField($fieldSpecification);
      } else {
        $entityDataFlow = $this->ensureEntity();
        $entityDataFlow->addAggregateField($fieldSpecification);
jaapjansma's avatar
jaapjansma committed
  /**
   * Returns URL to configuration screen
   *
   * @return false|string
   */
  public function getConfigurationUrl() {
    return 'civicrm/dataprocessor/form/source/configuration';
  }

jaapjansma's avatar
jaapjansma committed
  /**
   * @return \Civi\DataProcessor\DataFlow\SqlDataFlow
   */
  public function getPrimaryDataFlow() {
    return $this->primaryDataFlow;
  }