<?php
/**
 * @author Jaap Jansma <jaap.jansma@civicoop.org>
 * @license AGPL-3.0
 */

use Civi\DataProcessor\DataFlow\CombinedDataFlow\CombinedSqlDataFlow;
use Civi\DataProcessor\DataFlow\CombinedDataFlow\SubqueryDataFlow;
use Civi\DataProcessor\DataFlow\InMemoryDataFlow;
use Civi\DataProcessor\DataFlow\InvalidFlowException;
use Civi\DataProcessor\DataFlow\SqlDataFlow;
use Civi\DataProcessor\DataFlow\SqlTableDataFlow;
use Civi\DataProcessor\Exception\OutputNotFoundException;
use Civi\DataProcessor\FieldOutputHandler\AbstractSimpleFieldOutputHandler;
use Civi\DataProcessor\Output\ExportOutputInterface;
use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
use Civi\DataProcessor\Storage\TemporaryDatabaseTable;
use CRM_Dataprocessor_ExtensionUtil as E;

abstract class CRM_DataprocessorOutputExport_AbstractOutputExport implements ExportOutputInterface {

  /**
   * @var CRM_Queue_TaskContext
   */
  protected $queueCtx;

  /**
   * @var string
   */
  protected $batchName;

  /**
   * Returns the directory name for storing temporary files.
   *
   * @param array $configuration
   * @return String
   */
  abstract public function getDirectory(array $configuration=[]): string;

  /**
   * Returns the file extension.
   *
   * @param array $configuration
   * @return String
   */
  abstract public function getExtension(array $configuration=[]): string;

  /**
   * Returns the mime type of the export file.
   *
   * @param array $configuration
   * @return string
   */
  abstract public function mimeType(array $configuration=[]): string;

  /**
   * Run the export of the data processor.
   *
   * @param string $filename
   * @param AbstractProcessorType $dataProcessor
   * @param array $configuration
   * @param string|null $idField
   * @param array $selectedIds
   * @param array $formValues
   */
  abstract protected function exportRecords(string $filename, AbstractProcessorType $dataProcessor, array $configuration, string $idField=null, array $selectedIds=[], array $formValues=[]);

  /**
   * Returns the class name of the form with the export options.
   *
   * @return string
   */
  public function getExportFormClassName(): string {
    return 'CRM_DataprocessorOutputExport_Form_OutputExport';
  }


  /**
   * Returns the number of records to run a direct download.
   * Otherwise a progressbar is shown to the user.
   *
   * @return int
   */
  protected function getMaxDirectDownload(): int {
    return 50;
  }

  /**
   * When a progressbar is shown to the user set the number
   * of records per job.
   *
   * @return int
   */
  protected function getJobSize(): int {
    return 50;
  }

  /**
   * Returns true when this filter has additional configuration
   *
   * @return bool
   */
  public function hasConfiguration(): bool {
    return true;
  }

  /**
   * When this filter type has additional configuration you can add
   * the fields on the form with this function.
   *
   * @param \CRM_Core_Form $form
   * @param array $output
   */
  public function buildConfigurationForm(CRM_Core_Form $form, $output=array()) {
    try {
      $form->add('checkbox', 'anonymous', E::ts('Is public'));
    } catch (CRM_Core_Exception $e) {
    }
    $defaults = [];
    $configuration = false;
    if ($output && isset($output['configuration'])) {
      $configuration = $output['configuration'];
    }
    if ($configuration && isset($configuration['anonymous'])) {
      $defaults['anonymous'] = $configuration['anonymous'];
    }
    $form->setDefaults($defaults);
  }

  /**
   * When this filter type has configuration specify the template file name
   * for the configuration form.
   *
   * @return false|string
   */
  public function getConfigurationTemplateFileName():? string {
    return "CRM/DataprocessorOutputExport/Form/Configuration/GenericOutputExport.tpl";
  }

  /**
   * Process the submitted values and create a configuration array
   *
   * @param $submittedValues
   * @param array $output
   * @return array
   */
  public function processConfiguration($submittedValues, &$output): array {
    $configuration = array();
    $configuration['anonymous'] = $submittedValues['anonymous'] ?? FALSE;
    return $configuration;
  }

  /**
   * @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass
   * @param string|null $idField
   * @param array $selectedIds
   */
  protected static function addSelectedIdsFilterToDataProcessor(AbstractProcessorType $dataProcessorClass, string $idField=null, array $selectedIds=array()) {
    if (count($selectedIds) && !empty($idField)) {
      try {
        $outputFieldHandlers = $dataProcessorClass->getDataFlow()->getOutputFieldHandlers();
        foreach ($outputFieldHandlers as $outputFieldHandler) {
          if ($outputFieldHandler->getOutputFieldSpecification()->alias == $idField) {
            if ($outputFieldHandler instanceof AbstractSimpleFieldOutputHandler) {
              $dataSource = $outputFieldHandler->getDataSource();
              $dataFlow = $dataSource->getDataFlow();
              $idFilterField = $dataSource->getAvailableFilterFields()
                ->getFieldSpecificationByName($outputFieldHandler->getInputFieldSpecification()->name);
              if ($idFilterField && $dataFlow instanceof SqlDataFlow) {
                $tableAlias = $dataFlow->getName();
                if ($dataFlow instanceof SqlTableDataFlow) {
                  $tableAlias = $dataFlow->getTableAlias();
                } elseif ($dataFlow instanceof SubqueryDataFlow) {
                  $tableAlias = $dataFlow->getName();
                }
                elseif ($dataFlow instanceof CombinedSqlDataFlow) {
                  $tableAlias = $dataFlow->getPrimaryTableAlias();
                }
                $whereClause = new SqlDataFlow\SimpleWhereClause($tableAlias, $idFilterField->getName(), 'IN', $selectedIds, $idFilterField->type, FALSE);
                $dataProcessorClass->getDataFlow()->addWhereClause($whereClause);
              }
              elseif ($idFilterField && $dataFlow instanceof InMemoryDataFlow) {
                $filterClass = new InMemoryDataFlow\SimpleFilter($idFilterField->getName(), 'IN', $selectedIds);
                $dataProcessorClass->getDataFlow()->addFilter($filterClass);
              }
            }
            break;
          }
        }
      } catch (InvalidFlowException $e) {
      }
    }
  }

  /**
   * @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass
   * @param string $queueName
   *
   * @return \Civi\DataProcessor\Storage\TemporaryDatabaseTable
   */
  protected static function setTemporaryStorage(AbstractProcessorType $dataProcessorClass, string $queueName): TemporaryDatabaseTable {
    $storage = new TemporaryDatabaseTable($queueName);
    $dataProcessorClass->setStorage($storage);
    return $storage;
  }

  /**
   * Download export
   *
   * @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass
   * @param array $dataProcessor
   * @param array $outputBAO
   * @param array $formValues
   * @param string $sortFieldName
   * @param string $sortDirection
   * @param string $idField
   *  Set $idField to the name of the field containing the ID of the array $selectedIds
   * @param array $selectedIds
   *   Array with the selectedIds.
   * @param bool $redirectBack
   *   After a direct download redirect back to previous page.
   */
  public function downloadExport(AbstractProcessorType $dataProcessorClass, $dataProcessor, $outputBAO, $formValues, $sortFieldName = null, $sortDirection = 'ASC', $idField=null, $selectedIds=array(), bool $redirectBack = false) {
    try {
      static::addSelectedIdsFilterToDataProcessor($dataProcessorClass, $idField, $selectedIds);
      $count = CRM_Dataprocessor_Form_Output_AbstractUIOutputForm::getCount($dataProcessorClass, $formValues);
      if ($count > $this->getMaxDirectDownload()) {
        $this->startBatchJob($dataProcessorClass, $dataProcessor, $outputBAO, $formValues, $sortFieldName, $sortDirection, $idField, $selectedIds);
      } else {
        $this->doDirectDownload($dataProcessorClass, $dataProcessor, $outputBAO, $sortFieldName, $sortDirection, $idField, $selectedIds, $formValues, $redirectBack);
      }
    } catch (InvalidFlowException $e) {
    }
  }

  /**
   * Get the download name of the export file.
   *
   * @param $dataProcessor
   * @param $outputBAO
   *
   * @return string
   */
  public function getDownloadName($dataProcessor, $outputBAO): string {
    $configuration = [];
    if (isset($outputBAO['configuration']) && is_array($outputBAO['configuration'])) {
      $configuration = $outputBAO['configuration'];
    }

    return date('Ymdhis') . '_' . $dataProcessor['name'] . '.' . $this->getExtension($configuration);
  }

  public function doDirectDownload(AbstractProcessorType $dataProcessorClass, $dataProcessor, $outputBAO, $sortFieldName = null, $sortDirection = 'ASC', $idField=null, $selectedIds=array(), $formValues=array(), bool $redirectBack = false) {
    $filename = date('Ymdhis').'_'.$dataProcessor['id'].'_'.$outputBAO['id'].'_'.CRM_Core_Session::getLoggedInContactID().'_'.$dataProcessor['name'];

    $download_name = $this->getDownloadName($dataProcessor, $outputBAO);

    $basePath = CRM_Core_Config::singleton()->templateCompileDir . $this->getDirectory();
    CRM_Utils_File::createDir($basePath);
    CRM_Utils_File::restrictAccess($basePath.'/');

    $path = CRM_Core_Config::singleton()->templateCompileDir . $this->getDirectory().'/'. $filename;
    if ($sortFieldName) {
      try {
        $dataProcessorClass->getDataFlow()->resetSort();
        $dataProcessorClass->getDataFlow()->addSort($sortFieldName, $sortDirection);
      } catch (InvalidFlowException $e) {
      }
    }

    $dataProcessorClass->getDataFlow()->resetInitializeState();
    $this->createHeader($path, $dataProcessorClass, $outputBAO['configuration'], $dataProcessor, $idField, $selectedIds, $formValues);
    $dataProcessorClass->getDataFlow()->resetInitializeState();
    $this->exportRecords($path, $dataProcessorClass, $outputBAO['configuration'], $idField, $selectedIds, $formValues);
    $dataProcessorClass->getDataFlow()->resetInitializeState();
    $this->createFooter($path, $dataProcessorClass, $outputBAO['configuration'], $dataProcessor, $idField, $selectedIds, $formValues);

    $mimeType = $this->mimeType();

    if (!$path) {
      CRM_Core_Error::statusBounce('Could not retrieve the file');
    }

    if (!$redirectBack) {
      $buffer = file_get_contents($path . '.' . $this->getExtension($outputBAO['configuration']));
      if (!$buffer) {
        CRM_Core_Error::statusBounce('The file is either empty or you do not have permission to retrieve the file');
      }

      CRM_Utils_System::setHttpHeader('Expires', gmdate('D, d M Y H:i:s \G\M\T', time()));
      CRM_Utils_System::setHttpHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
      CRM_Utils_System::setHttpHeader('Access-Control-Allow-Origin', '*');
      CRM_Utils_System::download(
        $download_name,
        $mimeType,
        $buffer,
        NULL,
        TRUE,
        'download'
      );
    }

    $session = CRM_Core_Session::singleton();
    $downloadLink = CRM_Utils_System::url('civicrm/dataprocessor/form/output/download', 'filename='.$filename. '.' . $this->getExtension($outputBAO['configuration']).'&dataprocessor_id='.$dataProcessor['id'].'&export_id='.$outputBAO['id'], TRUE);
    $download_name = $this->getDownloadName($dataProcessor, $outputBAO);
    //set a status message for the user
    CRM_Core_Session::setStatus(E::ts('Download <a class="dataprocessor-export-download-link" href="%1">%2</a>', array(1=>$downloadLink, 2 => $download_name)), E::ts('Exported data'), 'success');
    $url = str_replace("&amp;", "&", $session->readUserContext());
    CRM_Utils_System::redirect($url);
  }

  protected function startBatchJob(AbstractProcessorType $dataProcessorClass, $dataProcessor, $outputBAO, $formValues, $sortFieldName = null, $sortDirection = 'ASC', $idField=null, $selectedIds=array()) {
    $session = CRM_Core_Session::singleton();

    $name = date('Ymdhis').'_'.$dataProcessor['id'].'_'.$outputBAO['id'].'_'.CRM_Core_Session::getLoggedInContactID().'_'.md5($dataProcessor['name']);

    $queue = CRM_Queue_Service::singleton()->create(array(
      'type' => 'Sql',
      'name' => $name,
      'reset' => TRUE, //do flush queue upon creation
    ));

    self::setTemporaryStorage($dataProcessorClass, $name);

    $basePath = CRM_Core_Config::singleton()->templateCompileDir . $this->getDirectory();
    CRM_Utils_File::createDir($basePath);
    CRM_Utils_File::restrictAccess($basePath.'/');
    $filename = $basePath.'/'. $name;

    $task = new CRM_Queue_Task(
      array(
        'CRM_DataprocessorOutputExport_AbstractOutputExport',
        'exportBatchHeader'
      ), //call back method
      array($filename,$formValues, $dataProcessor['id'], $outputBAO['id'], $sortFieldName, $sortDirection, $idField, $selectedIds, $formValues), //parameters,
      E::ts('Create header')
    );
    //now add this task to the queue
    $queue->createItem($task);

    $count = CRM_Dataprocessor_Form_Output_AbstractUIOutputForm::getCount($dataProcessorClass, $formValues);
    $recordsPerJob = $this->getJobSize();
    for($i=0; $i < $count; $i = $i + $recordsPerJob) {
      $title = E::ts('Exporting records %1/%2', array(
        1 => ($i+$recordsPerJob) <= $count ? $i+$recordsPerJob : $count,
        2 => $count,
      ));

      //create a task without parameters
      $task = new CRM_Queue_Task(
        array(
          'CRM_DataprocessorOutputExport_AbstractOutputExport',
          'exportBatch'
        ), //call back method
        array($filename,$formValues, $dataProcessor['id'], $outputBAO['id'], $i, $recordsPerJob, $sortFieldName, $sortDirection, $idField, $selectedIds, $formValues), //parameters,
        $title
      );
      //now add this task to the queue
      $queue->createItem($task);
    }

    $task = new CRM_Queue_Task(
      array(
        'CRM_DataprocessorOutputExport_AbstractOutputExport',
        'exportBatchFooter'
      ), //call back method
      array($filename,$formValues, $dataProcessor['id'], $outputBAO['id'], $sortFieldName, $sortDirection, $idField, $selectedIds, $formValues), //parameters,
      E::ts('Create footer')
    );
    //now add this task to the queue
    $queue->createItem($task);

    $url = str_replace("&amp;", "&", $session->readUserContext());

    $runner = new CRM_Queue_Runner(array(
      'title' => E::ts('Exporting data'), //title fo the queue
      'queue' => $queue, //the queue object
      'errorMode'=> CRM_Queue_Runner::ERROR_CONTINUE, //abort upon error and keep task in queue
      'onEnd' => array('CRM_DataprocessorOutputExport_AbstractOutputExport', 'onEnd'), //method which is called as soon as the queue is finished
      'onEndUrl' => $url,
    ));

    $runner->runAllViaWeb(); // does not return
  }

  public static function exportBatch(CRM_Queue_TaskContext $ctx, $filename, $params, $dataProcessorId, $outputId, $offset, $limit, $sortFieldName = null, $sortDirection = 'ASC', $idField=null, $selectedIds=array(), $formValues=array()): bool {
    $factory = dataprocessor_get_factory();
    try {
      $dataProcessor = civicrm_api3('DataProcessor', 'getsingle', ['id' => $dataProcessorId]);
      $output = civicrm_api3('DataProcessorOutput', 'getsingle', array('id' => $outputId));
      $dataProcessorClass = CRM_Dataprocessor_BAO_DataProcessor::dataProcessorToClass($dataProcessor);
    } catch (Exception|CiviCRM_API3_Exception $e) {
      return false;
    }
    CRM_Dataprocessor_Form_Output_AbstractUIOutputForm::applyFilters($dataProcessorClass, $params);
    try {
      if ($sortFieldName) {
        $dataProcessorClass->getDataFlow()->resetSort();
        $dataProcessorClass->getDataFlow()
          ->addSort($sortFieldName, $sortDirection);
        if ($idField && $idField != $sortFieldName) {
          $dataProcessorClass->getDataFlow()->addSort($idField, 'ASC');
        }
      }
      elseif ($idField) {
        $dataProcessorClass->getDataFlow()->addSort($idField, 'ASC');
      }
      self::addSelectedIdsFilterToDataProcessor($dataProcessorClass, $idField, $selectedIds);
      self::setTemporaryStorage($dataProcessorClass, $ctx->queue->getName());

      $dataProcessorClass->getDataFlow()->setOffset($offset);
      $dataProcessorClass->getDataFlow()->setLimit($limit);
    } catch (InvalidFlowException $e) {
      // Do nothing
    }
    try {
      $outputClass = $factory->getOutputByName($output['type']);
    } catch (OutputNotFoundException $e) {
      return FALSE;
    }
    if (!$outputClass instanceof CRM_DataprocessorOutputExport_AbstractOutputExport) {
      return FALSE;
    }
    $outputClass->queueCtx = $ctx;
    $outputClass->exportRecords($filename, $dataProcessorClass, $output['configuration'], $idField, $selectedIds, $formValues);
    return TRUE;
  }

  /**
   * @param \CRM_Queue_TaskContext $ctx
   * @param array $params
   * @param AbstractProcessorType $dataProcessorClass
   * @param array $output
   * @param string|null $sortFieldName
   * @param string $sortDirection
   * @param string|null $idField
   * @param array $selectedIds
   *
   * @return \CRM_DataprocessorOutputExport_AbstractOutputExport|false
   */
  protected static function getOutputClass(CRM_Queue_TaskContext $ctx, array $params, AbstractProcessorType $dataProcessorClass, array $output, string $sortFieldName = null, string $sortDirection = 'ASC', string $idField=null, array $selectedIds=array()):? CRM_DataprocessorOutputExport_AbstractOutputExport {
    $factory = dataprocessor_get_factory();
    CRM_Dataprocessor_Form_Output_AbstractUIOutputForm::applyFilters($dataProcessorClass, $params);
    if ($sortFieldName) {
      try {
        $dataProcessorClass->getDataFlow()->addSort($sortFieldName, $sortDirection);
      } catch (InvalidFlowException $e) {
      }
    }
    self::addSelectedIdsFilterToDataProcessor($dataProcessorClass, $idField, $selectedIds);
    try {
      $outputClass = $factory->getOutputByName($output['type']);
    } catch (OutputNotFoundException $e) {
      return false;
    }
    if (!$outputClass instanceof CRM_DataprocessorOutputExport_AbstractOutputExport) {
      return false;
    }

    $outputClass->queueCtx = $ctx;
    return $outputClass;
  }

  public static function exportBatchHeader(CRM_Queue_TaskContext $ctx, $filename, $params, $dataProcessorId, $outputId, $sortFieldName = null, $sortDirection = 'ASC', $idField=null, $selectedIds=array(), $formValues=array()): bool {
    try {
      $dataProcessor = civicrm_api3('DataProcessor', 'getsingle', ['id' => $dataProcessorId]);
      $output = civicrm_api3('DataProcessorOutput', 'getsingle', array('id' => $outputId));
      $dataProcessorClass = CRM_Dataprocessor_BAO_DataProcessor::dataProcessorToClass($dataProcessor);
    } catch (CiviCRM_API3_Exception|Exception $e) {
      return false;
    }

    self::setTemporaryStorage($dataProcessorClass, $ctx->queue->getName());

    $outputClass = static::getOutputClass($ctx, $params, $dataProcessorClass, $output, $sortFieldName, $sortDirection, $idField, $selectedIds);
    if ($outputClass) {
      $outputClass->createHeader($filename, $dataProcessorClass, $output['configuration'], $dataProcessor, $idField, $selectedIds, $formValues);
      return TRUE;
    }
    return FALSE;
  }

  public static function exportBatchFooter(CRM_Queue_TaskContext $ctx, $filename, $params, $dataProcessorId, $outputId, $sortFieldName = null, $sortDirection = 'ASC', $idField=null, $selectedIds=array(), $formValues=array()): bool {

    try {
      $dataProcessor = civicrm_api3('DataProcessor', 'getsingle', ['id' => $dataProcessorId]);
      $output = civicrm_api3('DataProcessorOutput', 'getsingle', array('id' => $outputId));
      $dataProcessorClass = CRM_Dataprocessor_BAO_DataProcessor::dataProcessorToClass($dataProcessor);
    } catch (CiviCRM_API3_Exception|Exception $e) {
      return false;
    }
    $storage = self::setTemporaryStorage($dataProcessorClass, $ctx->queue->getName());
    $storage->dropTable();
    $dataProcessorClass->setStorage(null);

    $outputClass = static::getOutputClass($ctx, $params, $dataProcessorClass, $output, $sortFieldName, $sortDirection, $idField, $selectedIds);
    if ($outputClass) {
      $outputClass->createFooter($filename, $dataProcessorClass, $output['configuration'], $dataProcessor, $idField, $selectedIds, $formValues);
      return TRUE;
    }

    return FALSE;
  }

  /**
   * Run this function when the progressbar is finished.
   *
   * @param \CRM_Queue_TaskContext $ctx
   */
  public static function onEnd(CRM_Queue_TaskContext $ctx) {
    $factory = dataprocessor_get_factory();
    $queue_name = $ctx->queue->getName();
    [$_1, $dataProcessorId, $outputId, $_2, $_3] = explode("_", $queue_name);
    try {
      $dataProcessor = civicrm_api3('DataProcessor', 'getsingle', ['id' => $dataProcessorId]);
      $output = civicrm_api3('DataProcessorOutput', 'getsingle', array('id' => $outputId));
      $configuration = [];
      if (isset($output['configuration']) && is_array($output['configuration'])) {
        $configuration = $output['configuration'];
      }
    } catch (CiviCRM_API3_Exception $e) {
      return;
    }
    try {
      $outputClass = $factory->getOutputByName($output['type']);
    } catch (OutputNotFoundException $e) {
      return;
    }
    if (!$outputClass instanceof CRM_DataprocessorOutputExport_AbstractOutputExport) {
      return;
    }

    $filename = $queue_name.'.'.$outputClass->getExtension($configuration);
    $downloadLink = CRM_Utils_System::url('civicrm/dataprocessor/form/output/download', 'filename='.$filename.'&dataprocessor_id='.$dataProcessorId.'&export_id='.$outputId, TRUE);
    $download_name = $outputClass->getDownloadName($dataProcessor, $output);
    //set a status message for the user
    CRM_Core_Session::setStatus(E::ts('Download <a class="dataprocessor-export-download-link" href="%1">%2</a>', array(1=>$downloadLink, 2 => $download_name)), E::ts('Exported data'), 'success');
  }

  /**
   * Checks whether the current user has access to this output
   *
   * @param array $output
   * @param array $dataProcessor
   * @return bool
   */
  public function checkPermission(array $output, array $dataProcessor): bool {
    $anonymous = false;
    if (isset($output['configuration']['anonymous'])) {
      $anonymous = $output['configuration']['anonymous'];
    }
    $userId = CRM_Core_Session::getLoggedInContactID();
    if ($userId) {
      return true;
    } elseif ($anonymous) {
      return true;
    }
    return false;
  }

  /**
   * Returns the url for the page/form this output will show to the user
   *
   * @param array $output
   * @param array $dataProcessor
   * @return string
   */
  public function getUrl(array $output, array $dataProcessor): string {
    $frontendUrl = false;
    if (isset($output['configuration']['anonymous'])) {
      $frontendUrl = (bool) $output['configuration']['anonymous'];
    }

    return CRM_Utils_System::url('civicrm/dataprocessor/output/export', array(
      'dataprocessor' => $dataProcessor['name'],
      'type' => $output['type']
    ), TRUE, NULL, FALSE, $frontendUrl);
  }

  /**
   * Returns the url for the page/form this output will show to the user
   *
   * @param array $output
   * @param array $dataProcessor
   * @return string
   */
  public function getTitleForLink(array $output, array $dataProcessor): string {
    return $dataProcessor['title'];
  }

  /**
   * This function could be overridden in child classes to create a header.
   * For example the CSV export uses this function.
   *
   * @param $filename
   * @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass
   * @param $configuration
   * @param $dataProcessor
   * @param string|null $idField
   * @param array $selectedIds
   * @param array $formValues
   */
  protected function createHeader($filename, AbstractProcessorType $dataProcessorClass, $configuration, $dataProcessor, string $idField=null, array $selectedIds=[], array $formValues=[]) {
    // Do nothing by default.
  }

  /**
   * This function could be overridden in child classes to create a footer.
   * For example the PDF export uses this function.
   *
   * @param $filename
   * @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass
   * @param $configuration
   * @param $dataProcessor
   * @param $idField
   * @param array $selectedIds
   * @param array $formValues
   */
  protected function createFooter($filename, AbstractProcessorType $dataProcessorClass, $configuration, $dataProcessor, $idField=null, array $selectedIds=[], array $formValues=[]) {
    // Do nothing by default.
  }

  /**
   * Returns the name of the current batch.
   *
   * @return string
   */
  protected function getBatchName(): string {
    if (empty($this->batchName)) {
      if ($this->queueCtx) {
        $this->batchName = $this->queueCtx->queue->getName();
      }
      $this->batchName = date('Ymdis') . CRM_Core_Session::getLoggedInContactID();
    }
    return $this->batchName;
  }

  protected function removeFileExtensionFromFileName(string $filename, array $configuration= []): string
  {
    $extension = '.'.$this->getExtension($configuration);
    $needle_len = strlen($extension);
    if (0 === substr_compare($filename, $extension, - $needle_len)) {
      return substr($filename, 0, -$needle_len);
    }
    return $filename;
  }

}
