From 5486d24ed555e38653b420d148ab981a310ed231 Mon Sep 17 00:00:00 2001 From: Jaap Jansma <jaap.jansma@civicoop.org> Date: Tue, 28 Jan 2020 20:52:11 +0100 Subject: [PATCH] Made CSV Export download available for anonymous users. --- CHANGELOG.md | 4 + CRM/Dataprocessor/Form/DataProcessor.php | 2 + .../Form/ManageDataProcessors.php | 6 +- CRM/DataprocessorOutputExport/CSV.php | 59 +++++++- CRM/DataprocessorOutputExport/Page/Export.php | 136 ++++++++++++++++++ .../DirectDownloadExportOutputInterface.php | 33 +++++ .../Output/UrlOutputInterface.php | 43 ++++++ .../Form/Configuration/CSV.tpl | 13 +- xml/Menu/dataprocessor.xml | 9 +- 9 files changed, 299 insertions(+), 6 deletions(-) create mode 100644 CRM/DataprocessorOutputExport/Page/Export.php create mode 100644 Civi/DataProcessor/Output/DirectDownloadExportOutputInterface.php create mode 100644 Civi/DataProcessor/Output/UrlOutputInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ba16bba..8f68df0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# Version 1.2.0 (not yet released) + +* Made CSV Export download available for anonymous users. + # Version 1.1.0 * Respect selected permissions for outputs diff --git a/CRM/Dataprocessor/Form/DataProcessor.php b/CRM/Dataprocessor/Form/DataProcessor.php index edd4f7ad..32504fac 100644 --- a/CRM/Dataprocessor/Form/DataProcessor.php +++ b/CRM/Dataprocessor/Form/DataProcessor.php @@ -125,6 +125,8 @@ class CRM_Dataprocessor_Form_DataProcessor extends CRM_Core_Form { $outputClass = $factory->getOutputByName($output['type']); if ($outputClass instanceof \Civi\DataProcessor\Output\UIOutputInterface) { $outputs[$idx]['navigation_url'] = CRM_Utils_System::url($outputClass->getUrlToUi($output, $this->dataProcessor), array('reset' => '1')); + } elseif ($outputClass instanceof \Civi\DataProcessor\Output\UrlOutputInterface && $outputClass->checkPermission($output, $this->dataProcessor)) { + $outputs[$idx]['navigation_url'] = $outputClass->getUrl($output, $this->dataProcessor); } if (isset($types[$output['type']])) { diff --git a/CRM/Dataprocessor/Form/ManageDataProcessors.php b/CRM/Dataprocessor/Form/ManageDataProcessors.php index 085cff92..67dfca7a 100644 --- a/CRM/Dataprocessor/Form/ManageDataProcessors.php +++ b/CRM/Dataprocessor/Form/ManageDataProcessors.php @@ -41,7 +41,11 @@ class CRM_Dataprocessor_Form_ManageDataProcessors extends CRM_Core_Form { if ($outputClass instanceof \Civi\DataProcessor\Output\UIOutputInterface) { $dataProcessors[$idx]['navigation'][$outputIndex]['url'] = CRM_Utils_System::url($outputClass->getUrlToUi($output, $dataProcessor), array('reset' => '1')); $dataProcessors[$idx]['navigation'][$outputIndex]['title'] = $outputClass->getTitleForUiLink($output, $dataProcessor); + } elseif ($outputClass instanceof \Civi\DataProcessor\Output\UrlOutputInterface && $outputClass->checkPermission($output, $dataProcessor)) { + $dataProcessors[$idx]['navigation'][$outputIndex]['url'] = $outputClass->getUrl($output, $dataProcessor); + $dataProcessors[$idx]['navigation'][$outputIndex]['title'] = $outputClass->getTitleForLink($output, $dataProcessor); } + } } @@ -78,4 +82,4 @@ class CRM_Dataprocessor_Form_ManageDataProcessors extends CRM_Core_Form { } -} \ No newline at end of file +} diff --git a/CRM/DataprocessorOutputExport/CSV.php b/CRM/DataprocessorOutputExport/CSV.php index 62c5352c..f3ceed55 100644 --- a/CRM/DataprocessorOutputExport/CSV.php +++ b/CRM/DataprocessorOutputExport/CSV.php @@ -5,10 +5,11 @@ */ use Civi\DataProcessor\Output\ExportOutputInterface; +use Civi\DataProcessor\Output\DirectDownloadExportOutputInterface; use CRM_Dataprocessor_ExtensionUtil as E; -class CRM_DataprocessorOutputExport_CSV implements ExportOutputInterface { +class CRM_DataprocessorOutputExport_CSV implements ExportOutputInterface, DirectDownloadExportOutputInterface { const MAX_DIRECT_SIZE = 500; @@ -34,6 +35,7 @@ class CRM_DataprocessorOutputExport_CSV implements ExportOutputInterface { $form->add('text', 'delimiter', E::ts('Delimiter'), array(), true); $form->add('text', 'enclosure', E::ts('Enclosure'), array(), true); $form->add('text', 'escape_char', E::ts('Escape char'), array(), true); + $form->add('checkbox', 'anonymous', E::ts('Available for anonymous users'), array(), false); $configuration = false; if ($output && isset($output['configuration'])) { @@ -54,6 +56,9 @@ class CRM_DataprocessorOutputExport_CSV implements ExportOutputInterface { } else { $defaults['escape_char'] = '\\'; } + if ($configuration && isset($configuration['anonymous'])) { + $defaults['anonymous'] = $configuration['anonymous']; + } $form->setDefaults($defaults); } @@ -80,6 +85,7 @@ class CRM_DataprocessorOutputExport_CSV implements ExportOutputInterface { $configuration['delimiter'] = $submittedValues['delimiter']; $configuration['enclosure'] = $submittedValues['enclosure']; $configuration['escape_char'] = $submittedValues['escape_char']; + $configuration['anonymous'] = $submittedValues['anonymous']; return $configuration; } @@ -139,16 +145,17 @@ class CRM_DataprocessorOutputExport_CSV implements ExportOutputInterface { * @param array $selectedIds * Array with the selectedIds. * @return string + * @throws \Exception */ public function downloadExport(\Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass, $dataProcessor, $outputBAO, $formValues, $sortFieldName = null, $sortDirection = 'ASC', $idField=null, $selectedIds=array()) { - if ($dataProcessorClass->getDataFlow()->recordCount() > self::MAX_DIRECT_SIZE) { + if (!$dataProcessorClass->getDataFlow()->recordCount() > self::MAX_DIRECT_SIZE) { $this->startBatchJob($dataProcessorClass, $dataProcessor, $outputBAO, $formValues, $sortFieldName, $sortDirection, $idField, $selectedIds); } else { $this->doDirectDownload($dataProcessorClass, $dataProcessor, $outputBAO, $sortFieldName, $sortDirection, $idField, $selectedIds); } } - protected function doDirectDownload(\Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass, $dataProcessor, $outputBAO, $sortFieldName = null, $sortDirection = 'ASC', $idField, $selectedIds=array()) { + public function doDirectDownload(\Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass, $dataProcessor, $outputBAO, $sortFieldName = null, $sortDirection = 'ASC', $idField, $selectedIds=array()) { $filename = date('Ymdhis').'_'.$dataProcessor['id'].'_'.$outputBAO['id'].'_'.CRM_Core_Session::getLoggedInContactID().'_'.$dataProcessor['name'].'.csv'; $download_name = date('Ymdhis').'_'.$dataProcessor['name'].'.csv'; @@ -306,5 +313,51 @@ class CRM_DataprocessorOutputExport_CSV implements ExportOutputInterface { CRM_Core_Session::setStatus(E::ts('<a href="%1">Download CSV file</a>', array(1=>$downloadLink)), E::ts('Exported data'), 'success'); } + /** + * 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($output, $dataProcessor) { + return CRM_Utils_System::url('civicrm/dataprocessor/output/export', array( + 'name' => $dataProcessor['name'], + 'type' => $output['type'] + )); + } + + /** + * 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($output, $dataProcessor) { + return $dataProcessor['title']; + } + + /** + * Checks whether the current user has access to this output + * + * @param array $output + * @param array $dataProcessor + * @return bool + */ + public function checkPermission($output, $dataProcessor) { + $anonymous = false; + if (isset($output['configuration']) && isset($output['configuration']['anonymous'])) { + $anonymous = $output['configuration']['anonymous'] ? true : false; + } + $userId = \CRM_Core_Session::getLoggedInContactID(); + if ($userId) { + return true; + } elseif ($anonymous) { + return true; + } + return false; + } + } diff --git a/CRM/DataprocessorOutputExport/Page/Export.php b/CRM/DataprocessorOutputExport/Page/Export.php new file mode 100644 index 00000000..be1c5dd2 --- /dev/null +++ b/CRM/DataprocessorOutputExport/Page/Export.php @@ -0,0 +1,136 @@ +<?php +/** + * @author Jaap Jansma <jaap.jansma@civicoop.org> + * @license AGPL-3.0 + */ + +class CRM_DataprocessorOutputExport_Page_Export extends CRM_Core_Page { + + /** + * @var array + */ + protected $dataProcessor; + + /** + * @var \Civi\DataProcessor\ProcessorType\AbstractProcessorType; + */ + protected $dataProcessorClass; + + /** + * @var int + */ + protected $dataProcessorId; + + /** + * @var \CRM_Dataprocessor_BAO_Output + */ + protected $dataProcessorOutput; + + /** + * Run page. + */ + public function run() { + $this->loadDataProcessor(); + $sortFields = $this->addColumnHeaders(); + $this->sort = new CRM_Utils_Sort($sortFields); + + $this->runExport(); + } + + protected function runExport() { + $factory = dataprocessor_get_factory(); + CRM_Dataprocessor_Form_Output_AbstractUIOutputForm::applyFilters($this->dataProcessorClass, array()); + + // Set the sort + $sortDirection = 'ASC'; + $sortFieldName = null; + if (!empty($this->sort->_vars[$this->sort->getCurrentSortID()])) { + $sortField = $this->sort->_vars[$this->sort->getCurrentSortID()]; + if ($this->sort->getCurrentSortDirection() == CRM_Utils_Sort::DESCENDING) { + $sortDirection = 'DESC'; + } + $sortFieldName = $sortField['name']; + } + + $outputClass = $factory->getOutputByName($this->dataProcessorOutput['type']); + if ($outputClass instanceof \Civi\DataProcessor\Output\DirectDownloadExportOutputInterface) { + $outputClass->doDirectDownload($this->dataProcessorClass, $this->dataProcessor, $this->dataProcessorOutput, array(), $sortFieldName, $sortDirection); + } + throw new \Exception('Unable to export'); + } + + protected function getDataProcessorName() { + return CRM_Utils_Request::retrieve('name', 'String', CRM_Core_DAO::$_nullObject, TRUE); + } + + protected function getOutputName() { + return CRM_Utils_Request::retrieve('type', 'String', CRM_Core_DAO::$_nullObject, TRUE); + } + + /** + * Retrieve the data processor and the output configuration + * + * @throws \Exception + */ + protected function loadDataProcessor() { + $factory = dataprocessor_get_factory(); + if (!$this->dataProcessorId) { + $dataProcessorName = $this->getDataProcessorName(); + $sql = " + SELECT civicrm_data_processor.id as data_processor_id, civicrm_data_processor_output.id AS output_id + FROM civicrm_data_processor + INNER JOIN civicrm_data_processor_output ON civicrm_data_processor.id = civicrm_data_processor_output.data_processor_id + WHERE is_active = 1 AND civicrm_data_processor.name = %1 AND civicrm_data_processor_output.type = %2 + "; + $params[1] = [$dataProcessorName, 'String']; + $params[2] = [$this->getOutputName(), 'String']; + $dao = CRM_Dataprocessor_BAO_DataProcessor::executeQuery($sql, $params, TRUE, 'CRM_Dataprocessor_BAO_DataProcessor'); + if (!$dao->fetch()) { + throw new \Exception('Could not find Data Processor "' . $dataProcessorName.'"'); + } + + $this->dataProcessor = civicrm_api3('DataProcessor', 'getsingle', array('id' => $dao->data_processor_id)); + $this->dataProcessorClass = \CRM_Dataprocessor_BAO_DataProcessor::dataProcessorToClass($this->dataProcessor, true); + $this->dataProcessorId = $dao->data_processor_id; + + $this->dataProcessorOutput = civicrm_api3('DataProcessorOutput', 'getsingle', array('id' => $dao->output_id)); + + $outputClass = $factory->getOutputByName($this->dataProcessorOutput['type']); + if (!$outputClass instanceof \Civi\DataProcessor\Output\DirectDownloadExportOutputInterface) { + throw new \Exception('Invalid output'); + } + if (!$outputClass->checkPermission($this->dataProcessorOutput, $this->dataProcessor)) { + CRM_Utils_System::permissionDenied(); + CRM_Utils_System::civiExit(); + } + } + } + + /** + * Add the headers for the columns + * + * @return array + * Array with all possible sort fields. + * + * @throws \Civi\DataProcessor\DataFlow\InvalidFlowException + */ + protected function addColumnHeaders() { + $sortFields = array(); + $columnHeaders = array(); + $sortColumnNr = 1; + foreach($this->dataProcessorClass->getDataFlow()->getOutputFieldHandlers() as $outputFieldHandler) { + $field = $outputFieldHandler->getOutputFieldSpecification(); + $columnHeaders[$field->alias] = $field->title; + if ($outputFieldHandler instanceof \Civi\DataProcessor\FieldOutputHandler\OutputHandlerSortable) { + $sortFields[$sortColumnNr] = array( + 'name' => $field->title, + 'sort' => $field->alias, + 'direction' => CRM_Utils_Sort::DONTCARE, + ); + $sortColumnNr++; + } + } + return $sortFields; + } + +} diff --git a/Civi/DataProcessor/Output/DirectDownloadExportOutputInterface.php b/Civi/DataProcessor/Output/DirectDownloadExportOutputInterface.php new file mode 100644 index 00000000..0a5313b4 --- /dev/null +++ b/Civi/DataProcessor/Output/DirectDownloadExportOutputInterface.php @@ -0,0 +1,33 @@ +<?php +/** + * @author Jaap Jansma <jaap.jansma@civicoop.org> + * @license AGPL-3.0 + */ + +namespace Civi\DataProcessor\Output; + +/** + * This interface indicates that the output type is available as a direct download. + * + * @package Civi\DataProcessor\Output + */ +interface DirectDownloadExportOutputInterface extends UrlOutputInterface { + + /** + * 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. + * @return string + */ + public function doDirectDownload(\Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass, $dataProcessor, $outputBAO, $sortFieldName = null, $sortDirection = 'ASC', $idField, $selectedIds=array()); + +} diff --git a/Civi/DataProcessor/Output/UrlOutputInterface.php b/Civi/DataProcessor/Output/UrlOutputInterface.php new file mode 100644 index 00000000..fd14a754 --- /dev/null +++ b/Civi/DataProcessor/Output/UrlOutputInterface.php @@ -0,0 +1,43 @@ +<?php +/** + * @author Jaap Jansma <jaap.jansma@civicoop.org> + * @license AGPL-3.0 + */ + +namespace Civi\DataProcessor\Output; + +/** + * This interface indicates that the output type is available through a url. + * + * @package Civi\DataProcessor\Output + */ +interface UrlOutputInterface { + + /** + * 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($output, $dataProcessor); + + /** + * 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($output, $dataProcessor); + + /** + * Checks whether the current user has access to this output + * + * @param array $output + * @param array $dataProcessor + * @return bool + */ + public function checkPermission($output, $dataProcessor); + +} diff --git a/templates/CRM/DataprocessorOutputExport/Form/Configuration/CSV.tpl b/templates/CRM/DataprocessorOutputExport/Form/Configuration/CSV.tpl index d6e41a9c..e23712f3 100644 --- a/templates/CRM/DataprocessorOutputExport/Form/Configuration/CSV.tpl +++ b/templates/CRM/DataprocessorOutputExport/Form/Configuration/CSV.tpl @@ -15,5 +15,16 @@ <div class="content">{$form.escape_char.html}</div> <div class="clear"></div> </div> + <div class="crm-section"> + <div class="label">{$form.anonymous.label}</div> + <div class="content">{$form.anonymous.html} + <p class="description"> + {ts}Tick this box when you want to make the CSV available for non-logged in users. <br> + This could be necessary when another system is importing this csv file on a regular basis. E.g. a website with + a public agenda of the upcoming events. + <br><strong>Caution:</strong> when you check this box the data becomes available without logging so this might lead to a data breach.{/ts}</p> + </div> + <div class="clear"></div> + </div> -{/crmScope} \ No newline at end of file +{/crmScope} diff --git a/xml/Menu/dataprocessor.xml b/xml/Menu/dataprocessor.xml index 7ef77365..c237e1ac 100644 --- a/xml/Menu/dataprocessor.xml +++ b/xml/Menu/dataprocessor.xml @@ -83,8 +83,15 @@ </item> <item> <path>civicrm/dataprocessor/form/output/download</path> - <title>Browse Uploaded files</title> + <title>Download</title> <access_arguments>access uploaded files</access_arguments> <page_callback>CRM_DataprocessorOutputExport_Page_Download</page_callback> </item> + <item> + <path>civicrm/dataprocessor/output/export</path> + <title>Export</title> + <page_callback>CRM_DataprocessorOutputExport_Page_Export</page_callback> + <access_callback>1</access_callback> + <is_public>true</is_public> + </item> </menu> -- GitLab