diff --git a/CRM/DataprocessorSearch/CaseSearch.php b/CRM/DataprocessorSearch/CaseSearch.php new file mode 100644 index 0000000000000000000000000000000000000000..7bd42b42a469bb9be3a4d4e69720e39763a40154 --- /dev/null +++ b/CRM/DataprocessorSearch/CaseSearch.php @@ -0,0 +1,187 @@ +<?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_CaseSearch 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('text', 'title', E::ts('Title'), true); + + $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', 'contact_id_field', E::ts('Contact ID field'), $fields, true, array( + 'style' => 'min-width:250px', + 'class' => 'crm-select2 huge', + 'placeholder' => E::ts('- select -'), + )); + $form->add('select', 'case_id_field', E::ts('Case ID field'), $fields, true, array( + 'style' => 'min-width:250px', + 'class' => 'crm-select2 huge', + 'placeholder' => E::ts('- select -'), + )); + $form->add('select', 'hide_id_fields', E::ts('Show Contact and Case ID field'), array(0=>'Contact and Case ID are Visible', 1=> 'Contact and Case ID are hidden')); + + $form->add('wysiwyg', 'help_text', E::ts('Help text for this search'), array('rows' => 6, 'cols' => 80)); + + // 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']['contact_id_field'])) { + $defaults['contact_id_field'] = $output['configuration']['contact_id_field']; + } + if (isset($output['configuration']['case_id_field'])) { + $defaults['case_id_field'] = $output['configuration']['case_id_field']; + } + if (isset($output['configuration']['navigation_id'])) { + $defaults['navigation_parent_path'] = $navigation->getNavigationParentPathById($output['configuration']['navigation_id']); + } + if (isset($output['configuration']['title'])) { + $defaults['title'] = $output['configuration']['title']; + } + if (isset($output['configuration']['hide_id_fields'])) { + $defaults['hide_id_fields'] = $output['configuration']['hide_id_fields']; + } + if (isset($output['configuration']['help_text'])) { + $defaults['help_text'] = $output['configuration']['help_text']; + } + } + } + if (!isset($defaults['permission'])) { + $defaults['permission'] = 'access CiviCRM'; + } + if (empty($defaults['title'])) { + $defaults['title'] = civicrm_api3('DataProcessor', 'getvalue', array('id' => $output['data_processor_id'], 'return' => 'title')); + } + $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/CaseSearch.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['title'] = $submittedValues['title']; + $configuration['contact_id_field'] = $submittedValues['contact_id_field']; + $configuration['case_id_field'] = $submittedValues['case_id_field']; + $configuration['navigation_parent_path'] = $submittedValues['navigation_parent_path']; + $configuration['hide_id_fields'] = $submittedValues['hide_id_fields']; + $configuration['help_text'] = $submittedValues['help_text']; + return $configuration; + } + + /** + * 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_case_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 isset($output['configuration']['title']) ? $output['configuration']['title'] : $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_CaseSearch'; + } + + /** + * 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'] + )); + } + +} \ No newline at end of file diff --git a/CRM/DataprocessorSearch/Controller/CaseSearch.php b/CRM/DataprocessorSearch/Controller/CaseSearch.php new file mode 100644 index 0000000000000000000000000000000000000000..9198fc75da1313ca19bb8e54ac777404234496b0 --- /dev/null +++ b/CRM/DataprocessorSearch/Controller/CaseSearch.php @@ -0,0 +1,80 @@ +<?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_CaseSearch 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->set('component_mode', CRM_Contact_BAO_Query::MODE_CASE); + $this->_stateMachine = new CRM_DataprocessorSearch_StateMachine_CaseSearch($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_CaseySearch) { + $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/CaseSearch.php b/CRM/DataprocessorSearch/Form/CaseSearch.php new file mode 100644 index 0000000000000000000000000000000000000000..03ad93c77d3add873850cf64f368bbfb7e7ff427 --- /dev/null +++ b/CRM/DataprocessorSearch/Form/CaseSearch.php @@ -0,0 +1,109 @@ +<?php +/** + * @author Jaap Jansma <jaap.jansma@civicoop.org> + * @license AGPL-3.0 + */ + +use CRM_Dataprocessor_ExtensionUtil as E; + +class CRM_DataprocessorSearch_Form_CaseSearch extends CRM_DataprocessorSearch_Form_AbstractSearch { + + /** + * Returns the url for view of the record action + * + * @param $row + * + * @return false|string + */ + protected function link($row) { + $record = $row['record']; + $idFieldName = $this->getIdFieldName(); + $contactIdFieldName = $this->getContactIdFieldName(); + $caseId = $record[$idFieldName]->formattedValue; + $contactId = $record[$contactIdFieldName]->formattedValue; + return CRM_Utils_System::url('civicrm/contact/view/case', 'reset=1&action=view&id='.$caseId.'&cid='.$contactId.'&context=search'); + } + + /** + * Returns the link text for view of the record action + * + * @param $row + * + * @return false|string + */ + protected function linkText($row) { + return E::ts('Manage case'); + } + + /** + * Return the data processor name + * + * @return String + */ + protected function getDataProcessorName() { + $dataProcessorName = str_replace('civicrm/dataprocessor_case_search/', '', CRM_Utils_System::getUrlPath()); + return $dataProcessorName; + } + + /** + * Returns the name of the output for this search + * + * @return string + */ + protected function getOutputName() { + return 'case_search'; + } + + /** + * Checks whether the output has a valid configuration + * + * @return bool + */ + protected function isConfigurationValid() { + if (!isset($this->dataProcessorOutput['configuration']['case_id_field'])) { + return false; + } + if (!isset($this->dataProcessorOutput['configuration']['contact_id_field'])) { + return false; + } + return true; + } + + /** + * Returns the name of the ID field in the dataset. + * + * @return string + */ + protected function getIdFieldName() { + return $this->dataProcessorOutput['configuration']['case_id_field']; + } + + /** + * Returns the name of the ID field in the dataset. + * + * @return string + */ + protected function getContactIdFieldName() { + return $this->dataProcessorOutput['configuration']['contact_id_field']; + } + + /** + * @return string + */ + protected function getEntityTable() { + return 'civicrm_case'; + } + + /** + * Builds the list of tasks or actions that a searcher can perform on a result set. + * + * @return array + */ + public function buildTaskList() { + if (!$this->_taskList) { + $this->_taskList = CRM_Case_Task::permissionedTaskTitles(CRM_Core_Permission::getPermission()); + } + return $this->_taskList; + } + +} \ No newline at end of file diff --git a/CRM/DataprocessorSearch/StateMachine/CaseSearch.php b/CRM/DataprocessorSearch/StateMachine/CaseSearch.php new file mode 100644 index 0000000000000000000000000000000000000000..237f0b50475c9da2329f56dd00e314d890839147 --- /dev/null +++ b/CRM/DataprocessorSearch/StateMachine/CaseSearch.php @@ -0,0 +1,89 @@ +<?php +/** + * @author Jaap Jansma <jaap.jansma@civicoop.org> + * @license AGPL-3.0 + */ + +class CRM_DataprocessorSearch_StateMachine_CaseSearch 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['Search'] = array( + 'className' => 'CRM_DataprocessorSearch_Form_CaseSearch', + ); + list($task, $result) = $this->taskName($controller, 'Search'); + $this->_task = $task; + + if (is_array($task)) { + foreach ($task as $t) { + $this->_pages[$t] = NULL; + } + } + else { + $this->_pages[$task] = 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. + * + * @param string $formName + * + * @return array + * the name of the form that will handle the task + */ + public function taskName($controller, $formName = 'Search') { + // total hack, check POST vars and then session to determine stuff + $value = CRM_Utils_Array::value('task', $_POST); + if (!isset($value)) { + $value = $this->_controller->get('task'); + } + $this->_controller->set('task', $value); + return CRM_Case_Task::getTask($value); + } + + /** + * Return the form name of the task. + * + * @return string + */ + public function getTaskFormName() { + return CRM_Utils_String::getClassName($this->_task); + } + + /** + * Should the controller reset the session. + * In some cases, specifically search we want to remember + * state across various actions and want to go back to the + * beginning from the final state, but retain the same session + * values + * + * @return bool + */ + public function shouldReset() { + return FALSE; + } + +} diff --git a/Civi/DataProcessor/Factory.php b/Civi/DataProcessor/Factory.php index 758a572844b9e57867a3168eb58bf2c500aa45d2..5936cefe131c47f09a7256c21b6973dfb1e4b47d 100644 --- a/Civi/DataProcessor/Factory.php +++ b/Civi/DataProcessor/Factory.php @@ -122,6 +122,7 @@ class Factory { $this->addOutput('api', 'Civi\DataProcessor\Output\Api', E::ts('API')); $this->addOutput('contact_search', 'CRM_DataprocessorSearch_ContactSearch', 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('export_csv', 'CRM_DataprocessorOutputExport_CSV', E::ts('CSV Export')); $this->addFilter('simple_sql_filter', 'Civi\DataProcessor\FilterHandler\SimpleSqlFilter', E::ts('Field filter')); $this->addjoinType('simple_join', 'Civi\DataProcessor\DataFlow\MultipleDataFlows\SimpleJoin', E::ts('Select fields to join on')); diff --git a/Civi/DataProcessor/Source/Cases/CaseSource.php b/Civi/DataProcessor/Source/Cases/CaseSource.php index d718ff18506f376d844837d39f19720944c3d8bf..76a4c43f29db4f54cdf03102ce91ffad7b46cfef 100644 --- a/Civi/DataProcessor/Source/Cases/CaseSource.php +++ b/Civi/DataProcessor/Source/Cases/CaseSource.php @@ -6,12 +6,41 @@ namespace Civi\DataProcessor\Source\Cases; +use Civi\DataProcessor\DataFlow\CombinedDataFlow\CombinedSqlDataFlow; +use Civi\DataProcessor\DataFlow\MultipleDataFlows\DataFlowDescription; +use Civi\DataProcessor\DataFlow\MultipleDataFlows\SimpleJoin; +use Civi\DataProcessor\DataFlow\CombinedDataFlow\SubqueryDataFlow; +use Civi\DataProcessor\DataFlow\SqlTableDataFlow; +use Civi\DataProcessor\DataSpecification\DataSpecification; use Civi\DataProcessor\Source\AbstractCivicrmEntitySource; +use Civi\DataProcessor\DataSpecification\Utils as DataSpecificationUtils; use CRM_Dataprocessor_ExtensionUtil as E; class CaseSource extends AbstractCivicrmEntitySource { + /** + * @var SqlTableDataFlow + */ + protected $caseDataFlow; + + /** + * @var SqlTableDataFlow + */ + protected $caseContactDataFlow; + + public function __construct() { + parent::__construct(); + + // Create the case data flow and data flow description + $this->caseDataFlow = new SqlTableDataFlow($this->getTable(), $this->getSourceName().'_case', $this->getSourceTitle()); + DataSpecificationUtils::addDAOFieldsToDataSpecification('CRM_Case_DAO_Case', $this->caseDataFlow->getDataSpecification()); + + // Create the case contact data flow and data flow description + $this->caseContactDataFlow = new SqlTableDataFlow('civicrm_case_contact', $this->getSourceName().'_case_contact'); + DataSpecificationUtils::addDAOFieldsToDataSpecification('CRM_Case_DAO_CaseContact', $this->caseContactDataFlow->getDataSpecification(), array('id'), '', 'case_contact_', E::ts('Client :: ')); + } + /** * Returns the entity name * @@ -29,4 +58,104 @@ class CaseSource extends AbstractCivicrmEntitySource { protected function getTable() { return 'civicrm_case'; } + + /** + * Returns the default configuration for this data source + * + * @return array + */ + public function getDefaultConfiguration() { + return array( + 'filter' => array( + 'is_deleted' => array ( + 'op' => '=', + 'value' => '0', + ), + ) + ); + } + + /** + * Initialize this data source. + * + * @throws \Exception + */ + public function initialize() { + if (!$this->primaryDataFlow) { + $this->primaryDataFlow = $this->getEntityDataFlow(); + } + $this->addFilters($this->configuration); + if (count($this->customGroupDataFlowDescriptions) || count($this->additionalDataFlowDescriptions)) { + $this->dataFlow = new CombinedSqlDataFlow('', $this->primaryDataFlow->getPrimaryTable(), $this->caseDataFlow->getTableAlias()); + $this->dataFlow->addSourceDataFlow(new DataFlowDescription($this->primaryDataFlow)); + foreach ($this->additionalDataFlowDescriptions as $additionalDataFlowDescription) { + $this->dataFlow->addSourceDataFlow($additionalDataFlowDescription); + } + foreach ($this->customGroupDataFlowDescriptions as $customGroupDataFlowDescription) { + $this->dataFlow->addSourceDataFlow($customGroupDataFlowDescription); + } + } + else { + $this->dataFlow = $this->primaryDataFlow; + } + } + + /** + * @return \Civi\DataProcessor\DataFlow\SqlDataFlow + * @throws \Exception + */ + protected function getEntityDataFlow() { + $caseDataDescription = new DataFlowDescription($this->caseDataFlow); + + $join = new SimpleJoin($this->caseDataFlow->getTableAlias(), 'id', $this->caseContactDataFlow->getTableAlias(), 'case_id'); + $join->setDataProcessor($this->dataProcessor); + $caseContactDataDescription = new DataFlowDescription($this->caseContactDataFlow, $join); + + // Create the subquery data flow + $entityDataFlow = new SubqueryDataFlow($this->getSourceName(), $this->getTable(), $this->getSourceName()); + $entityDataFlow->addSourceDataFlow($caseDataDescription); + $entityDataFlow->addSourceDataFlow($caseContactDataDescription); + + return $entityDataFlow; + } + + /** + * 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 instanceof SubqueryDataFlow && $this->primaryDataFlow->getPrimaryTable() === $this->getTable()) { + return $this->primaryDataFlow; + } elseif (empty($this->primaryDataFlow)) { + $this->primaryDataFlow = $this->getEntityDataFlow(); + return $this->primaryDataFlow; + } + foreach($this->additionalDataFlowDescriptions as $additionalDataFlowDescription) { + if ($additionalDataFlowDescription->getDataFlow()->getTable() == $this->getTable()) { + return $additionalDataFlowDescription->getDataFlow(); + } + } + $entityDataFlow = $this->getEntityDataFlow(); + $join = new SimpleJoin($this->getSourceName(), 'id', $this->getSourceName(), 'entity_id', 'LEFT'); + $join->setDataProcessor($this->dataProcessor); + $additionalDataFlowDescription = new DataFlowDescription($entityDataFlow,$join); + $this->additionalDataFlowDescriptions[] = $additionalDataFlowDescription; + return $additionalDataFlowDescription->getDataFlow(); + } + + /** + * Load the fields from this entity. + * + * @param DataSpecification $dataSpecification + * @throws \Civi\DataProcessor\DataSpecification\FieldExistsException + */ + protected function loadFields(DataSpecification $dataSpecification, $fieldsToSkip=array()) { + $daoClass = \CRM_Core_DAO_AllCoreTables::getFullName($this->getEntity()); + $aliasPrefix = $this->getSourceName().'_'; + + DataSpecificationUtils::addDAOFieldsToDataSpecification($daoClass, $dataSpecification, $fieldsToSkip, '', $aliasPrefix); + DataSpecificationUtils::addDAOFieldsToDataSpecification('CRM_Case_DAO_CaseContact', $dataSpecification, array('id', 'case_id'), 'case_contact_', $aliasPrefix, E::ts('Client :: ')); + } } \ No newline at end of file diff --git a/templates/CRM/DataprocessorSearch/Form/CaseSearch.tpl b/templates/CRM/DataprocessorSearch/Form/CaseSearch.tpl new file mode 100644 index 0000000000000000000000000000000000000000..004d808ee0824ff8682c71446896d5897a7f3670 --- /dev/null +++ b/templates/CRM/DataprocessorSearch/Form/CaseSearch.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/CaseSearch.tpl b/templates/CRM/DataprocessorSearch/Form/OutputConfiguration/CaseSearch.tpl new file mode 100644 index 0000000000000000000000000000000000000000..d54bdd9c3a52955ffd3227601e862f2b9541a085 --- /dev/null +++ b/templates/CRM/DataprocessorSearch/Form/OutputConfiguration/CaseSearch.tpl @@ -0,0 +1,37 @@ +{crmScope extensionKey='dataprocessor'} + <div class="crm-section"> + <div class="label">{$form.title.label}</div> + <div class="content">{$form.title.html}</div> + <div class="clear"></div> + </div> + <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.contact_id_field.label}</div> + <div class="content">{$form.contact_id_field.html}</div> + <div class="clear"></div> + </div> + <div class="crm-section"> + <div class="label">{$form.case_id_field.label}</div> + <div class="content">{$form.case_id_field.html}</div> + <div class="clear"></div> + </div> + <div class="crm-section"> + <div class="label">{$form.hide_id_fields.label}</div> + <div class="content">{$form.hide_id_fields.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} \ No newline at end of file