From 0713541fdfc0e05ff5a4117375a664fd32c49757 Mon Sep 17 00:00:00 2001 From: Jaap Jansma <jaap.jansma@civicoop.org> Date: Tue, 29 Oct 2019 17:03:10 +0100 Subject: [PATCH] Output a data processor as a contribution search. --- CHANGELOG.md | 1 + .../Form/Output/AbstractUIOutputForm.php | 22 +-- .../ContributionSearch.php | 185 ++++++++++++++++++ .../Controller/ContributionSearch.php | 78 ++++++++ .../Form/ContributionSearch.php | 110 +++++++++++ .../StateMachine/ContributionSearch.php | 92 +++++++++ Civi/DataProcessor/Factory.php | 1 + .../Form/ContributionSearch.tpl | 1 + .../ContributionSearch.tpl | 32 +++ 9 files changed, 507 insertions(+), 15 deletions(-) create mode 100644 CRM/DataprocessorSearch/ContributionSearch.php create mode 100644 CRM/DataprocessorSearch/Controller/ContributionSearch.php create mode 100644 CRM/DataprocessorSearch/Form/ContributionSearch.php create mode 100644 CRM/DataprocessorSearch/StateMachine/ContributionSearch.php create mode 100644 templates/CRM/DataprocessorSearch/Form/ContributionSearch.tpl create mode 100644 templates/CRM/DataprocessorSearch/Form/OutputConfiguration/ContributionSearch.tpl diff --git a/CHANGELOG.md b/CHANGELOG.md index d684c62d..667d2b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Allow to limit ContactFilter to only show contacts from specific groups. * Output a data processor as a dashboard. * Output a data processor as a tab on the contact summary screen. +* Output a data processor as a contribution search. * Added field outputs for simple calculations (substract and total). * Added escaped output to search screens. * Replaced the value separator in the raw field with a comma. diff --git a/CRM/Dataprocessor/Form/Output/AbstractUIOutputForm.php b/CRM/Dataprocessor/Form/Output/AbstractUIOutputForm.php index 9871ae66..768af4d7 100644 --- a/CRM/Dataprocessor/Form/Output/AbstractUIOutputForm.php +++ b/CRM/Dataprocessor/Form/Output/AbstractUIOutputForm.php @@ -55,26 +55,13 @@ abstract class CRM_Dataprocessor_Form_Output_AbstractUIOutputForm extends CRM_Co $this->assign('has_exposed_filters', $this->hasExposedFilters()); } - /** - * Check whether the user has access to the output. - * - * @return bool - */ - protected function checkPermission() { - if (isset($this->dataProcessorOutput['permission']) && $this->dataProcessorOutput['permission']) { - if (!CRM_Core_Permission::check(array($this->dataProcessorOutput['permission']))) { - return false; - } - } - return 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 = " @@ -97,7 +84,12 @@ abstract class CRM_Dataprocessor_Form_Output_AbstractUIOutputForm extends CRM_Co $this->dataProcessorOutput = civicrm_api3('DataProcessorOutput', 'getsingle', array('id' => $dao->output_id)); $this->assign('output', $this->dataProcessorOutput); - if (!$this->checkPermission()) { + $outputClass = $factory->getOutputByName($this->dataProcessorOutput['type']); + if (!$outputClass instanceof \Civi\DataProcessor\Output\UIOutputInterface) { + throw new \Exception('Invalid output'); + } + + if (!$outputClass->checkUIPermission($this->dataProcessorOutput, $this->dataProcessor)) { CRM_Utils_System::permissionDenied(); CRM_Utils_System::civiExit(); } elseif (!$this->isConfigurationValid()) { diff --git a/CRM/DataprocessorSearch/ContributionSearch.php b/CRM/DataprocessorSearch/ContributionSearch.php new file mode 100644 index 00000000..e12ab5f5 --- /dev/null +++ b/CRM/DataprocessorSearch/ContributionSearch.php @@ -0,0 +1,185 @@ +<?php +/** + * @author Jaap Jansma <jaap.jansma@civicoop.org> + * @license AGPL-3.0 + */ + +use Civi\DataProcessor\Output\UIOutputInterface; + +use CRM_Dataprocessor_ExtensionUtil as E; + +class CRM_DataprocessorSearch_ContributionSearch implements UIOutputInterface { + + /** + * Returns true when this filter has additional configuration + * + * @return bool + */ + public function hasConfiguration() { + 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 $filter + */ + public function buildConfigurationForm(\CRM_Core_Form $form, $output=array()) { + + $navigation = CRM_Dataprocessor_Utils_Navigation::singleton(); + $dataProcessor = civicrm_api3('DataProcessor', 'getsingle', array('id' => $output['data_processor_id'])); + $dataProcessorClass = \CRM_Dataprocessor_BAO_DataProcessor::dataProcessorToClass($dataProcessor); + $fields = array(); + foreach($dataProcessorClass->getDataFlow()->getOutputFieldHandlers() as $outputFieldHandler) { + $field = $outputFieldHandler->getOutputFieldSpecification(); + $fields[$field->alias] = $field->title; + } + + $form->add('select','permission', E::ts('Permission'), \CRM_Core_Permission::basicPermissions(), true, array( + 'style' => 'min-width:250px', + 'class' => 'crm-select2 huge', + 'placeholder' => E::ts('- select -'), + )); + $form->add('select', 'contribution_id_field', E::ts('Contribution ID field'), $fields, true, array( + 'style' => 'min-width:250px', + 'class' => 'crm-select2 huge', + 'placeholder' => E::ts('- select -'), + )); + $form->add('select', 'hide_id_field', E::ts('Show Contribution ID field'), array(0=>'Contribution ID is Visible', 1=> 'Contribution ID is hidden')); + + $form->add('wysiwyg', 'help_text', E::ts('Help text for this search'), array('rows' => 6, 'cols' => 80)); + $form->add('checkbox', 'expanded_search', E::ts('Expand criteria form initially')); + + // navigation field + $navigationOptions = $navigation->getNavigationOptions(); + if (isset($output['configuration']['navigation_id'])) { + $navigationPath = $navigation->getNavigationPathById($output['configuration']['navigation_id']); + unset($navigationOptions[$navigationPath]); + } + $form->add('select', 'navigation_parent_path', ts('Parent Menu'), array('' => ts('- select -')) + $navigationOptions, true); + + $defaults = array(); + if ($output) { + if (isset($output['permission'])) { + $defaults['permission'] = $output['permission']; + } + if (isset($output['configuration']) && is_array($output['configuration'])) { + if (isset($output['configuration']['contribution_id_field'])) { + $defaults['contribution_id_field'] = $output['configuration']['contribution_id_field']; + } + if (isset($output['configuration']['navigation_id'])) { + $defaults['navigation_parent_path'] = $navigation->getNavigationParentPathById($output['configuration']['navigation_id']); + } + if (isset($output['configuration']['hide_id_field'])) { + $defaults['hide_id_field'] = $output['configuration']['hide_id_field']; + } + if (isset($output['configuration']['help_text'])) { + $defaults['help_text'] = $output['configuration']['help_text']; + } + if (isset($output['configuration']['expanded_search'])) { + $defaults['expanded_search'] = $output['configuration']['expanded_search']; + } + } + } + if (!isset($defaults['permission'])) { + $defaults['permission'] = 'access CiviCRM'; + } + $form->setDefaults($defaults); + } + + /** + * When this filter type has configuration specify the template file name + * for the configuration form. + * + * @return false|string + */ + public function getConfigurationTemplateFileName() { + return "CRM/DataprocessorSearch/Form/OutputConfiguration/ContributionSearch.tpl"; + } + + + /** + * Process the submitted values and create a configuration array + * + * @param $submittedValues + * @param array $output + * @return array + */ + public function processConfiguration($submittedValues, &$output) { + $output['permission'] = $submittedValues['permission']; + $configuration['contribution_id_field'] = $submittedValues['contribution_id_field']; + $configuration['navigation_parent_path'] = $submittedValues['navigation_parent_path']; + $configuration['hide_id_field'] = $submittedValues['hide_id_field']; + $configuration['help_text'] = $submittedValues['help_text']; + $configuration['expanded_search'] = isset($submittedValues['expanded_search']) ? $submittedValues['expanded_search'] : false; + return $configuration; + } + + /** + * This function is called prior to removing an output + * + * @param array $output + * @return void + */ + public function deleteOutput($output) { + // Do nothing + } + + /** + * Returns the url for the page/form this output will show to the user + * + * @param array $output + * @param array $dataProcessor + * @return string + */ + public function getUrlToUi($output, $dataProcessor) { + return "civicrm/dataprocessor_contribution_search/{$dataProcessor['name']}"; + } + + /** + * Returns the url for the page/form this output will show to the user + * + * @param array $output + * @param array $dataProcessor + * @return string + */ + public function getTitleForUiLink($output, $dataProcessor) { + return $dataProcessor['title']; + } + + /** + * Returns the url for the page/form this output will show to the user + * + * @param array $output + * @param array $dataProcessor + * @return string|false + */ + public function getIconForUiLink($output, $dataProcessor) { + return false; + } + + /** + * Returns the callback for the UI. + * + * @return string + */ + public function getCallbackForUi() { + return 'CRM_DataprocessorSearch_Controller_ContributionSearch'; + } + + /** + * Checks whether the current user has access to this output + * + * @param array $output + * @param array $dataProcessor + * @return bool + */ + public function checkUIPermission($output, $dataProcessor) { + return CRM_Core_Permission::check(array( + $output['permission'] + )); + } + +} diff --git a/CRM/DataprocessorSearch/Controller/ContributionSearch.php b/CRM/DataprocessorSearch/Controller/ContributionSearch.php new file mode 100644 index 00000000..ca1427e0 --- /dev/null +++ b/CRM/DataprocessorSearch/Controller/ContributionSearch.php @@ -0,0 +1,78 @@ +<?php +/** + * @author Jaap Jansma <jaap.jansma@civicoop.org> + * @license AGPL-3.0 + */ + +/** + * This class is used by the Search functionality. + * + * - the search controller is used for building/processing multiform + * searches. + * + * Typically the first form will display the search criteria and it's results + * + * The second form is used to process search results with the associated actions. + */ +class CRM_DataprocessorSearch_Controller_ContributionSearch extends CRM_Core_Controller { + + /** + * Class constructor. + * + * @param string $title + * @param bool $modal + * @param int|mixed|null $action + */ + public function __construct($title = NULL, $modal = TRUE, $action = CRM_Core_Action::NONE) { + parent::__construct($title, $modal); + + $this->_stateMachine = new CRM_DataprocessorSearch_StateMachine_ContributionSearch($this, $action); + + // create and instantiate the pages + $this->addPages($this->_stateMachine, $action); + + // add all the actions + $this->addActions(); + } + + /** + * Process the request, overrides the default QFC run method + * This routine actually checks if the QFC is modal and if it + * is the first invalid page, if so it call the requested action + * if not, it calls the display action on the first invalid page + * avoids the issue of users hitting the back button and getting + * a broken page + * + * This run is basically a composition of the original run and the + * jump action + * + * @return mixed + */ + public function run() { + + $actionName = $this->getActionName(); + list($pageName, $action) = $actionName; + // Hack to replace to userContext for redirecting after a Task has been completed. + // We want the redirect + if (!$this->_pages[$pageName] instanceof CRM_DataprocessorSearch_Form_ContributionSearch) { + $session = CRM_Core_Session::singleton(); + $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this); + $urlPath = CRM_Utils_System::getUrlPath(); + $urlParams = 'force=1'; + if ($qfKey) { + $urlParams .= "&qfKey=$qfKey"; + } + $this->setDestination(CRM_Utils_System::url($urlPath, $urlParams)); + } + + return parent::run(); + } + + /** + * @return mixed + */ + public function selectorName() { + return $this->get('selectorName'); + } + +} diff --git a/CRM/DataprocessorSearch/Form/ContributionSearch.php b/CRM/DataprocessorSearch/Form/ContributionSearch.php new file mode 100644 index 00000000..ee93085c --- /dev/null +++ b/CRM/DataprocessorSearch/Form/ContributionSearch.php @@ -0,0 +1,110 @@ +<?php +/** + * @author Jaap Jansma <jaap.jansma@civicoop.org> + * @license AGPL-3.0 + */ + +use CRM_Dataprocessor_ExtensionUtil as E; + +class CRM_DataprocessorSearch_Form_ContributionSearch extends CRM_DataprocessorSearch_Form_AbstractSearch { + + /** + * Returns the name of the default Entity + * + * @return string + */ + public function getDefaultEntity() { + return 'Contribution'; + } + + /** + * Returns the url for view of the record action + * + * @param $row + * + * @return false|string + */ + protected function link($row) { + return CRM_Utils_System::url('civicrm/contact/view/contribution', 'reset=1&id='.$row['id'].'&cid='.$row['id'].'&action=view'); + } + + /** + * Returns the link text for view of the record action + * + * @param $row + * + * @return false|string + */ + protected function linkText($row) { + return E::ts('View contribution'); + } + + /** + * Checks whether the output has a valid configuration + * + * @return bool + */ + protected function isConfigurationValid() { + if (!isset($this->dataProcessorOutput['configuration']['contribution_id_field'])) { + return false; + } + return true; + } + + /** + * Return the data processor ID + * + * @return String + */ + protected function getDataProcessorName() { + $dataProcessorName = str_replace('civicrm/dataprocessor_contribution_search/', '', CRM_Utils_System::getUrlPath()); + return $dataProcessorName; + } + + /** + * Returns the name of the output for this search + * + * @return string + */ + protected function getOutputName() { + return 'contribution_search'; + } + + /** + * Returns the name of the ID field in the dataset. + * + * @return string + */ + protected function getIdFieldName() { + return $this->dataProcessorOutput['configuration']['contribution_id_field']; + } + + /** + * @return string + */ + protected function getEntityTable() { + return 'civicrm_contribution'; + } + + /** + * Returns whether we want to use the prevnext cache. + * @return bool + */ + protected function usePrevNextCache() { + return true; + } + + /** + * Builds the list of tasks or actions that a searcher can perform on a result set. + * + * @return array + */ + public function buildTaskList() { + if (!$this->_taskList) { + $taskParams['softCreditFiltering'] = FALSE; + $this->_taskList = CRM_Contribute_Task::permissionedTaskTitles(CRM_Core_Permission::getPermission(), $taskParams); + } + return $this->_taskList; + } + +} diff --git a/CRM/DataprocessorSearch/StateMachine/ContributionSearch.php b/CRM/DataprocessorSearch/StateMachine/ContributionSearch.php new file mode 100644 index 00000000..b8defeb6 --- /dev/null +++ b/CRM/DataprocessorSearch/StateMachine/ContributionSearch.php @@ -0,0 +1,92 @@ +<?php +/** + * @author Jaap Jansma <jaap.jansma@civicoop.org> + * @license AGPL-3.0 + */ + +class CRM_DataprocessorSearch_StateMachine_ContributionSearch extends CRM_Core_StateMachine { + + /** + * The task that the wizard is currently processing + * + * @var string + */ + protected $_task; + + /** + * Class constructor. + * + * @param object $controller + * @param \const|int $action + */ + public function __construct($controller, $action = CRM_Core_Action::NONE) { + parent::__construct($controller, $action); + + $this->_pages = array(); + $this->_pages['Basic'] = array( + 'className' => 'CRM_DataprocessorSearch_Form_ContributionSearch', + ); + list($task, $result) = $this->taskName($controller); + $this->_task = $task; + if (is_array($task)) { + foreach ($task as $t) { + $this->_pages[$t] = NULL; + } + } + else { + $this->_pages[$task] = NULL; + } + + if ($result) { + $this->_pages['CRM_Contribute_Form_Task_Result'] = NULL; + } + + $this->addSequentialPages($this->_pages, $action); + } + + /** + * Determine the form name based on the action. This allows us + * to avoid using conditional state machine, much more efficient + * and simpler + * + * @param CRM_Core_Controller $controller + * The controller object. + * + * @return array + * the name of the form that will handle the task + */ + public function taskName($controller) { + // total hack, check POST vars and then session to determine stuff + $value = CRM_Utils_Array::value('task', $_POST); + if (!isset($value)) { + $value = $controller->get('task'); + } + $this->_controller->set('task', $value); + + return CRM_Contribute_Task::getTask($value); + } + + /** + * Return the form name of the task. + * + * @return string + */ + public function getTaskFormName() { + if (is_array($this->_task)) { + // return first page + return CRM_Utils_String::getClassName($this->_task[0]); + } + else { + return CRM_Utils_String::getClassName($this->_task); + } + } + + /** + * Since this is a state machine for search and we want to come back to the same state + * we dont want to issue a reset of the state session when we are done processing a task + */ + public function shouldReset() { + return FALSE; + } + +} diff --git a/Civi/DataProcessor/Factory.php b/Civi/DataProcessor/Factory.php index 3292f2b4..cef2312c 100644 --- a/Civi/DataProcessor/Factory.php +++ b/Civi/DataProcessor/Factory.php @@ -125,6 +125,7 @@ class Factory { $this->addOutput('contact_search', 'CRM_Contact_DataProcessorContactSearch', E::ts('Contact Search')); $this->addOutput('activity_search', 'CRM_DataprocessorSearch_ActivitySearch', E::ts('Activity Search')); $this->addOutput('case_search', 'CRM_DataprocessorSearch_CaseSearch', E::ts('Case Search')); + $this->addOutput('contribution_search', 'CRM_DataprocessorSearch_ContributionSearch', E::ts('Contribution Search')); $this->addOutput('participant_search', 'CRM_DataprocessorSearch_ParticipantSearch', E::ts('Participant Search')); $this->addOutput('export_csv', 'CRM_DataprocessorOutputExport_CSV', E::ts('CSV Export')); $this->addFilter('simple_sql_filter', 'Civi\DataProcessor\FilterHandler\SimpleSqlFilter', E::ts('Field filter')); diff --git a/templates/CRM/DataprocessorSearch/Form/ContributionSearch.tpl b/templates/CRM/DataprocessorSearch/Form/ContributionSearch.tpl new file mode 100644 index 00000000..004d808e --- /dev/null +++ b/templates/CRM/DataprocessorSearch/Form/ContributionSearch.tpl @@ -0,0 +1 @@ +{include file="CRM/DataprocessorSearch/Form/Search.tpl"} \ No newline at end of file diff --git a/templates/CRM/DataprocessorSearch/Form/OutputConfiguration/ContributionSearch.tpl b/templates/CRM/DataprocessorSearch/Form/OutputConfiguration/ContributionSearch.tpl new file mode 100644 index 00000000..cf52f6b9 --- /dev/null +++ b/templates/CRM/DataprocessorSearch/Form/OutputConfiguration/ContributionSearch.tpl @@ -0,0 +1,32 @@ +{crmScope extensionKey='dataprocessor'} + <div class="crm-section"> + <div class="label">{$form.navigation_parent_path.label}</div> + <div class="content">{$form.navigation_parent_path.html}</div> + <div class="clear"></div> + </div> + <div class="crm-section"> + <div class="label">{$form.permission.label}</div> + <div class="content">{$form.permission.html}</div> + <div class="clear"></div> + </div> + <div class="crm-section"> + <div class="label">{$form.contribution_id_field.label}</div> + <div class="content">{$form.contribution_id_field.html}</div> + <div class="clear"></div> + </div> + <div class="crm-section"> + <div class="label">{$form.hide_id_field.label}</div> + <div class="content">{$form.hide_id_field.html}</div> + <div class="clear"></div> + </div> + <div class="crm-section"> + <div class="label">{$form.expanded_search.label}</div> + <div class="content">{$form.expanded_search.html}</div> + <div class="clear"></div> + </div> + <div class="crm-section"> + <div class="label">{$form.help_text.label}</div> + <div class="content">{$form.help_text.html}</div> + <div class="clear"></div> + </div> +{/crmScope} -- GitLab