diff --git a/CRM/FormProcessor/BAO/FormProcessorInstance.php b/CRM/FormProcessor/BAO/FormProcessorInstance.php index 1e9ddb2cfb009c1ecd1fb5205572379d0c046b65..22a0358cafca46dcfedd1c8be6717833b52f2f82 100644 --- a/CRM/FormProcessor/BAO/FormProcessorInstance.php +++ b/CRM/FormProcessor/BAO/FormProcessorInstance.php @@ -31,6 +31,24 @@ $row['inputs'] = array_values(CRM_FormProcessor_BAO_FormProcessorInput::getValues(array('form_processor_instance_id' => $formProcessorInstance->id))); $row['actions'] = array_values(CRM_FormProcessor_BAO_FormProcessorAction::getValues(array('form_processor_instance_id' => $formProcessorInstance->id))); + $handler = \Civi::service('form_processor_output_handler_factory')->getHandlerByName($row['output_handler']); + if ($handler) { + $configuration = $handler->getDefaultConfiguration(); + if (isset($row['output_handler_configuration']) && is_string($row['output_handler_configuration'])) { + $row['output_handler_configuration'] = json_decode($row['output_handler_configuration'], true); + } + if (empty($row['output_handler_configuration']) || !is_array($row['output_handler_configuration'])) { + $row['output_handler_configuration'] = array(); + } + foreach($row['output_handler_configuration'] as $name => $value) { + $configuration->set($name, $value); + } + $handler->setConfiguration($configuration); + + $row['output_handler'] = $handler; + } + + $result[$row['id']] = $row; } return $result; @@ -70,6 +88,9 @@ $formProcessorInstance->name = CRM_FormProcessor_BAO_FormProcessorInstance::buildNameFromLabel($formProcessorInstance->title); } } + if (is_array($formProcessorInstance->output_handler_configuration)) { + $formProcessorInstance->output_handler_configuration = json_encode($formProcessorInstance->output_handler_configuration); + } $formProcessorInstance->save(); self::storeValues($formProcessorInstance, $result); diff --git a/CRM/FormProcessor/DAO/FormProcessorInstance.php b/CRM/FormProcessor/DAO/FormProcessorInstance.php index 1df1a4fc1f0cee022c5faf60c6a1e0d7100571c5..469d44019270ee292903ea3a3f0765c2e78ce013 100644 --- a/CRM/FormProcessor/DAO/FormProcessorInstance.php +++ b/CRM/FormProcessor/DAO/FormProcessorInstance.php @@ -47,6 +47,7 @@ class CRM_FormProcessor_DAO_FormProcessorInstance extends CRM_Core_DAO { 'title' => E::ts('Title'), 'type' => CRM_Utils_Type::T_STRING, 'maxlength' => 128, + 'required' => true ), 'is_active' => array( 'name' => 'is_active', @@ -58,6 +59,18 @@ class CRM_FormProcessor_DAO_FormProcessorInstance extends CRM_Core_DAO { 'title' => E::ts('Description'), 'type' => CRM_Utils_Type::T_STRING, ), + 'output_handler' => array( + 'name' => 'output_handler', + 'title' => E::ts('Output handler'), + 'type' => CRM_Utils_Type::T_STRING, + 'maxlength' => 88, + 'required' => true, + ) , + 'output_handler_configuration' => array( + 'name' => 'output_handler_configuration', + 'title' => E::ts('Output handler Configuration'), + 'type' => CRM_Utils_Type::T_TEXT, + ), 'created_date' => array( 'name' => 'created_date', 'type' => CRM_Utils_Type::T_DATE @@ -95,6 +108,8 @@ class CRM_FormProcessor_DAO_FormProcessorInstance extends CRM_Core_DAO { 'label' => 'label', 'is_active' => 'is_active', 'description' => 'description', + 'output_handler' => 'outpunt_handler', + 'output_handler_configuration' => 'output_handler_configuration', 'created_date' => 'created_date', 'created_user_id' => 'created_user_id', 'modified_date' => 'modified_date', diff --git a/Civi/FormProcessor/API/Provider.php b/Civi/FormProcessor/API/Provider.php index fe50e25dd1f731a003a49273e50b7fabaf23da58..e2e364f2091c35444ec911e264ec4593c22df7b8 100644 --- a/Civi/FormProcessor/API/Provider.php +++ b/Civi/FormProcessor/API/Provider.php @@ -10,6 +10,7 @@ use Civi\API\Event\RespondEvent; use Civi\API\Events; use Civi\API\Provider\ProviderInterface as API_ProviderInterface; + use Civi\FormProcessor\DataBag; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -103,7 +104,7 @@ */ public function invoke($apiRequest) { $actionProvider = form_processor_get_action_provider(); - $parameterBag = $actionProvider->createParameterBag(); + $dataBag = new DataBag(); $params = $apiRequest['params']; @@ -117,6 +118,9 @@ // Validate the parameters. foreach($formProcessor['inputs'] as $input) { + $objInput = new \CRM_FormProcessor_BAO_FormProcessorInput(); + $objInput->copyValues($input); + if ($input['is_required'] && !isset($params[$input['name']])) { throw new \API_Exception('Parameter '.$input['name'].' is required'); } @@ -129,24 +133,25 @@ throw new \API_Exception($validator['validator']->getInvalidMessage().' (Parameter '.$input['name'].')'); } } - $parameterBag->setParameter('input.'.$input['name'], $params[$input['name']]); + + $dataBag->setInputData($objInput, $params[$objInput->name]); } // Execute the actions $actionParams = array(); - $output = array(); foreach($formProcessor['actions'] as $action) { + $objAction = new \CRM_FormProcessor_BAO_FormProcessorAction(); + $objAction->copyValues($action); + // Create a parameter bag for the action + $parameterBag = $dataBag->convertToActionProviderParameterBag($actionProvider); $mappedParameterBag = $actionProvider->createdMappedParameterBag($parameterBag, $action['mapping']); $outputBag = $action['type']->execute($mappedParameterBag); - foreach($outputBag as $field => $value) { - $outputParameterName = 'action.'.$action['id'].'.'.$field; - $parameterBag->setParameter($outputParameterName, $value); - $output[$action['title']][$field] = $value; - } + // Add the output of the action to the data bag of this action. + $dataBag->setActionDataFromActionProviderParameterBag($objAction, $outputBag); } - - return $output; + + return $formProcessor['output_handler']->handle($dataBag); } /** diff --git a/Civi/FormProcessor/Config/Specification.php b/Civi/FormProcessor/Config/Specification.php index 914af05c61fd12b630dfedabc7e59a16b5172e80..989b33bc7532574c363e7d0d77f6ca8a9f8b95d5 100644 --- a/Civi/FormProcessor/Config/Specification.php +++ b/Civi/FormProcessor/Config/Specification.php @@ -2,7 +2,9 @@ namespace Civi\FormProcessor\Config; -class Specification { +use Civi\FormProcessor\Config\SpecificationInterface; + +class Specification implements SpecificationInterface { /** * @var mixed @@ -45,7 +47,7 @@ class Specification { * @param $name * @param $dataType */ - public function __construct($name, $dataType = 'String', $title='', $required = false, $defaultValue = null, $fkEntity = null, $options = array(), $multiple = false, $description) { + public function __construct($name, $dataType = 'String', $title='', $required = false, $defaultValue = null, $fkEntity = null, $options = array(), $multiple = false, $description='') { $this->setName($name); $this->setDataType($dataType); $this->setTitle($title); @@ -57,6 +59,33 @@ class Specification { $this->setDescription($description); } + /** + * Returns the type of specifcation + * + * @return string + */ + public function getType() { + return 'specification'; + } + + /** + * Validates the given value + * + * @param mixed $value + * @return bool + */ + public function validateValue($value) { + if (isset($value)) { + // Check the type + if (!\CRM_Utils_Type::validate($value, $this->getDataType(), false)) { + return false; + } + } elseif ($this->isRequired()) { + return false; + } + return true; + } + /** * @return mixed */ @@ -243,6 +272,7 @@ class Specification { $key = strtolower(preg_replace('/(?=[A-Z])/', '_$0', $key)); $ret[$key] = $val; } + $ret['type'] = $this->getType(); return $ret; } diff --git a/Civi/FormProcessor/Config/SpecificationBag.php b/Civi/FormProcessor/Config/SpecificationBag.php index 4f1e057e2a1dfdbb4ea55ddafebe1b0c02e9b4a5..c97b8b716b1eae02a4ca5df495bfbf375304ff6c 100644 --- a/Civi/FormProcessor/Config/SpecificationBag.php +++ b/Civi/FormProcessor/Config/SpecificationBag.php @@ -2,16 +2,30 @@ namespace Civi\FormProcessor\Config; +use Civi\FormProcessor\Config\SpecificationInterface; +use Civi\FormProcessor\Config\ConfigurationBag; + class SpecificationBag implements \IteratorAggregate { protected $parameterSpecifications = array(); + /** + * @param array <SpecificationInterface> + */ public function __construct($specifcations = array()) { foreach($specifcations as $spec) { $this->parameterSpecifications[$spec->getName()] = $spec; } } + public function getDefaultConfiguration() { + $default = new ConfigurationBag(); + foreach($this->parameterSpecifications as $spec) { + $default->set($spec->getName(), $spec->getDefaultValue()); + } + return $default; + } + /** * Validates the configuration. * @@ -23,12 +37,7 @@ class SpecificationBag implements \IteratorAggregate { foreach($specification as $spec) { // First check whether the value is present and should be present. $value = $configuration->get($spec->getName()); - if (isset($value)) { - // Check the type - if (!\CRM_Utils_Type::validate($value, $spec->getDataType(), false)) { - return false; - } - } elseif ($spec->isRequired()) { + if (!$spec->validateValue($value)) { return false; } } @@ -36,21 +45,21 @@ class SpecificationBag implements \IteratorAggregate { } /** - * @param Specification $specification + * @param SpecificationInterface $specification * The specification object. * @return SpecificationBag */ - public function addSpecification(Specification $specification) { + public function addSpecification(SpecificationInterface $specification) { $this->parameterSpecifications[$specification->getName()] = $specification; return $this; } /** - * @param Specification $specification + * @param SpecificationInterface $specification * The specification object. * @return SpecificationBag */ - public function removeSpecification(Specification $specification) { + public function removeSpecification(SpecificationInterface $specification) { foreach($this->parameterSpecifications as $key => $spec) { if ($spec == $specification) { unset($this->parameterSpecifications[$key]); @@ -76,7 +85,7 @@ class SpecificationBag implements \IteratorAggregate { /** * @param string $name * The name of the parameter. - * @return Specification|null + * @return SpecificationInterface|null */ public function getSpecificationByName($name) { foreach($this->parameterSpecifications as $key => $spec) { @@ -98,9 +107,11 @@ class SpecificationBag implements \IteratorAggregate { */ public function toArray() { $return = array(); + $return['parameter_specifications'] = array(); foreach($this->parameterSpecifications as $spec) { - $return[] = $spec->toArray(); + $return['parameter_specifications'][] = $spec->toArray(); } + $return['default_configuration'] = $this->getDefaultConfiguration()->toArray(); return $return; } diff --git a/Civi/FormProcessor/Config/SpecificationCollection.php b/Civi/FormProcessor/Config/SpecificationCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..ef9bc67be6095634e1c0e767faf7fccb53e4ea28 --- /dev/null +++ b/Civi/FormProcessor/Config/SpecificationCollection.php @@ -0,0 +1,223 @@ +<?php + +namespace Civi\FormProcessor\Config; + +use Civi\FormProcessor\Config\ConfigurationBag; +use Civi\FormProcessor\Config\SpecificationBag; +use Civi\FormProcessor\Config\SpecificationInterface; + +class SpecificationCollection implements SpecificationInterface { + + /** + * @var SpecificationBag + */ + protected $specificationBag; + + /** + * @var false|int + */ + protected $min = false; + + /** + * @var false|int + */ + protected $max = false; + + /** + * @var string + */ + protected $name; + /** + * @var string + */ + protected $title; + /** + * @var string + */ + protected $description; + + /** + * @param $name + * @param $dataType + */ + public function __construct($name, $title, SpecificationBag $specificationBag, $min=false, $max=false, $description='') { + $this->setName($name); + $this->setTitle($title); + $this->setMin($min); + $this->setMax($max); + $this->specificationBag = $specificationBag; + $this->setDescription($description); + } + + /** + * Returns the type of specifcation + * + * @return string + */ + public function getType() { + return 'collection'; + } + + /** + * Validates the given value + * + * @param mixed $value + * @return bool + */ + public function validateValue($collection) { + if (!is_array($collection)) { + return false; + } + $count = count($collection); + if ($this->min !== false && $count < $this->min) { + return false; + } + if ($this->max !== false && $count > $this->max) { + return false; + } + foreach($collection as $value) { + if ($value instanceof ConfigurationBag) { + $configuration = $value; + } elseif (is_array($value)) { + $configuration = new ConfigurationBag(); + foreach($value as $k => $v) { + $configuration->set($k, $v); + } + } else { + return false; // invalid value + } + if (!SpecificationBag::validate($configuration, $this->specificationBag)) { + return false; + } + } + } + + /** + * Returns the default value + * + * @return mixed + */ + public function getDefaultValue() { + $defaultValues = array(); + if ($this->min > 0) { + $defaultValue = new ConfigurationBag; + foreach($this->specificationBag as $spec) { + $defaultValue->setParameter($spec->getName(), $spec->getDefaultValue()); + } + for($i=0; $i < $this->min; $i++) { + $defaultValues[] = $defaultValue; + } + } + return $defaultValues; + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @param string $name + * + * @return $this + */ + public function setName($name) { + $this->name = $name; + return $this; + } + + /** + * @return string + */ + public function getTitle() { + return $this->title; + } + + /** + * @param string $title + * + * @return $this + */ + public function setTitle($title) { + $this->title = $title; + return $this; + } + + /** + * @return string + */ + public function getDescription() { + return $this->description; + } + + /** + * @param string $description + * + * @return $this + */ + public function setDescription($description) { + $this->description = $description; + return $this; + } + + /** + * Returns the minimum count for the collection. False if not set. + * + * @return false|int + */ + public function getMin() { + return $this->min; + } + + /** + * Sets the minimum count for the collection. False if not set. + * + * @param false|int + * @return SpecificationCollection + */ + public function setMin($min) { + $this->min = $min; + return $this; + } + + /** + * Returns the maximum count for the collection. False if not set. + * + * @return false|int + */ + public function getMax() { + return $this->max; + } + + /** + * Sets the maximum count for the collection. False if not set. + * + * @param false|int + * @return SpecificationCollection + */ + public function setMax($max) { + $this->max = $max; + return $this; + } + + /** + * Converts the object to an array. + * + * @return array + */ + public function toArray() { + return array( + 'specification_bag' => $this->specificationBag->toArray(), + 'type' => $this->getType(), + 'name' => $this->getName(), + 'title' => $this->getTitle(), + 'description' => $this->getDescription(), + 'min' => $this->getMin(), + 'max' => $this->getMax(), + 'default_value' => $this->getDefaultValue(), + ); + } + +} diff --git a/Civi/FormProcessor/Config/SpecificationFields.php b/Civi/FormProcessor/Config/SpecificationFields.php new file mode 100644 index 0000000000000000000000000000000000000000..69fe056dd7add59f1dc303b481023c723ecdff44 --- /dev/null +++ b/Civi/FormProcessor/Config/SpecificationFields.php @@ -0,0 +1,170 @@ +<?php + +namespace Civi\FormProcessor\Config; + +use Civi\FormProcessor\Config\ConfigurationBag; +use Civi\FormProcessor\Config\SpecificationBag; +use Civi\FormProcessor\Config\SpecificationInterface; + +class SpecificationFields implements SpecificationInterface { + + /** + * @var string + */ + protected $name; + /** + * @var string + */ + protected $title; + /** + * @var string + */ + protected $description; + + /** + * @var bool + */ + protected $required = FALSE; + + /** + * The key is the name of the field and the value the label + * + * @var array + */ + protected $fields = array(); + + /** + * @param string $name + * @param string $title, + * @param bool + * @param array + * @param string + */ + public function __construct($name, $title, $required=false, $fields=array(), $description='') { + $this->setName($name); + $this->setTitle($title); + $this->fields = $fields; + $this->setDescription($description); + $this->setRequired($required); + } + + + public function getType() { + return 'fields'; + } + + /** + * @return bool + */ + public function isRequired() { + return $this->required; + } + + /** + * @param bool $required + * + * @return Field + */ + public function setRequired($required) { + $this->required = $required; + return $this; + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @param string $name + * + * @return $this + */ + public function setName($name) { + $this->name = $name; + return $this; + } + + /** + * @return string + */ + public function getTitle() { + return $this->title; + } + + /** + * @param string $title + * + * @return $this + */ + public function setTitle($title) { + $this->title = $title; + return $this; + } + + /** + * @return string + */ + public function getDescription() { + return $this->description; + } + + /** + * @param string $description + * + * @return $this + */ + public function setDescription($description) { + $this->description = $description; + return $this; + } + + public function addField($name, $label) { + $this->field[$name] = $label; + } + + /** + * @return array<Field> + */ + public function getFields() { + return $this->fields; + } + + /** + * Validates the given value + * + * @param mixed $value + * @return bool + */ + public function validateValue($value) { + return true; + } + + /** + * Returns the default value + * + * @return mixed + */ + public function getDefaultValue() { + return null; + } + + /** + * Converts the object to an array. + * + * @return array + */ + public function toArray() { + return array( + 'type' => $this->getType(), + 'name' => $this->getName(), + 'title' => $this->getTitle(), + 'description' => $this->getDescription(), + 'fields' => $this->getFields(), + 'required' => $this->isRequired(), + ); + } + +} \ No newline at end of file diff --git a/Civi/FormProcessor/Config/SpecificationInterface.php b/Civi/FormProcessor/Config/SpecificationInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..858a9fd32c0a971b2da5b5d81bbbf982169145bb --- /dev/null +++ b/Civi/FormProcessor/Config/SpecificationInterface.php @@ -0,0 +1,57 @@ +<?php + +namespace Civi\FormProcessor\Config; + +interface SpecificationInterface { + + /** + * Returns the type of specifcation + * + * @return string + */ + public function getType(); + + /** + * Returns the name of the specification + * + * @return string + */ + public function getName(); + + /** + * Returns the title of the specification + * + * @return string + */ + public function getTitle(); + + /** + * Returns the descripyion of the specification + * + * @return string + */ + public function getDescription(); + + /** + * Validates the given value + * + * @param mixed $value + * @return bool + */ + public function validateValue($value); + + /** + * Returns the default value + * + * @return mixed + */ + public function getDefaultValue(); + + /** + * Returns the specification object as an array + * + * @return array + */ + public function toArray(); + +} diff --git a/Civi/FormProcessor/DataBag.php b/Civi/FormProcessor/DataBag.php new file mode 100644 index 0000000000000000000000000000000000000000..6e34560c9b4fff2a38fe4b6e6cf8f0901e5f7bf7 --- /dev/null +++ b/Civi/FormProcessor/DataBag.php @@ -0,0 +1,134 @@ +<?php + +/** + * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org> + * @license http://www.gnu.org/licenses/agpl-3.0.html + */ + +namespace Civi\FormProcessor; + +class DataBag { + + private $inputs = array(); + + private $actions = array(); + + private $inputData = array(); + + private $actionData = array(); + /** + * Sets the input data for a given input. + * + * @param array $input + * @return DataBag + */ + public function setInputData(\CRM_FormProcessor_BAO_FormProcessorInput $input, $value) { + $this->inputs[$input->id] = $input; + $this->inputData[$input->id] = $value; + return $this; + } + + /** + * Retrieves the input data for a given input. + * + * @param $input + * @return string + */ + public function getInputData(\CRM_FormProcessor_BAO_FormProcessorInput $input) { + return $this->inputData[$input->id]; + } + + /** + * Returns an array with all the inputs. + * + * @return array + */ + public function getAllInputs() { + return $this->inputs; + } + + /** + * Sets the action data for a given action. + * + * @param $action + * @param string $field + * @return DataBag + */ + public function setActionData(\CRM_FormProcessor_BAO_FormProcessorAction $action, $field, $value) { + $this->actions[$action->id] = $action; + $this->actionData[$action->id][$field] = $value; + return $this; + } + + /** + * Retrieves all the action data for a given action. + * + * @param $action + * @return string + */ + public function getActionData(\CRM_FormProcessor_BAO_FormProcessorAction $action) { + return $this->actionData[$action->id]; + } + + /** + * Returns an array with all the actions. + * + * @return array + */ + public function getAllActions() { + return $this->actions; + } + + /** + * Sets the action data from an action provider parameter bag object. + * + * @param $action + * @param $parameterBag + * @return DataBag + */ + public function setActionDataFromActionProviderParameterBag(\CRM_FormProcessor_BAO_FormProcessorAction $action, $parameterBag) { + $this->actions[$action->id] = $action; + foreach($parameterBag as $field => $value) { + $this->actionData[$action->id][$field] = $value; + } + return $this; + } + + /** + * Returns the data by its alias. Returns null when the alias is not set + * + * Example aliases: + * input.email + * action.3.contact_id + * + * @param string + * @return mixed|null + */ + public function getDataByAlias($alias) { + $splitted_alias = explode(".", $alias); + if ($splitted_alias[0] == 'input') { + return $this->inputData[$splitted_alias[1]]; + } elseif ($splitted_alias[0] == 'action') { + return $this->actionData[$splitted_alias[1]][$splitted_alias[2]]; + } + return null; + } + + /** + * Convert this data bag to the action provider parameter bag. + */ + public function convertToActionProviderParameterBag($actionProvider) { + $parameterBag = $actionProvider->createParameterBag(); + foreach($this->inputs as $input) { + $parameterBag->setParameter('input.'.$input->name, $this->inputData[$input->id]); + } + foreach($this->actions as $action) { + foreach($this->actionData[$action->id] as $field => $value) { + $outputParameterName = 'action.'.$action->id.'.'.$field; + $parameterBag->setParameter($outputParameterName, $value); + } + } + return $parameterBag; + } + +} diff --git a/Civi/FormProcessor/OutputHandler/AbstractOutputHandler.php b/Civi/FormProcessor/OutputHandler/AbstractOutputHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..e8ae281b3a7111d16f3e54175cbea73b71a4940f --- /dev/null +++ b/Civi/FormProcessor/OutputHandler/AbstractOutputHandler.php @@ -0,0 +1,112 @@ +<?php + +/** + * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org> + * @license http://www.gnu.org/licenses/agpl-3.0.html + */ + + namespace Civi\FormProcessor\OutputHandler; + + use \Civi\FormProcessor\Config\ConfigurationBag; + use \Civi\FormProcessor\Config\Specification; + use \Civi\FormProcessor\Config\SpecificationBag; + use \Civi\FormProcessor\OutputHandler\OutputHandlerInterface; + use \Civi\FormProcessor\DataBag; + + abstract class AbstractOutputHandler implements OutputHandlerInterface { + + /** + * @var ConfigurationBag + */ + protected $configuration; + + /** + * @var ConfigurationBag + */ + protected $defaultConfiguration; + + /** + * Get the configuration specification + * + * @return SpecificationBag + */ + abstract public function getConfigurationSpecification(); + + /** + * Convert the action data to output data. + * + * @param DataBag $dataBag + * @return array + */ + abstract public function handle(DataBag $dataBag); + + /** + * Returns the label of the output handler. + * + * @return string + */ + abstract public function getLabel(); + + public function __construct() { + $this->configuration = null; + $this->defaultConfiguration = null; + } + + /** + * @return ConfigurationBag + */ + public function getConfiguration() { + if (!$this->configuration) { + $this->configuration = clone $this->getDefaultConfiguration(); + } + return $this->configuration; + } + + /** + * @return ConfigurationBag + */ + public function getDefaultConfiguration() { + if (!$this->defaultConfiguration) { + $this->defaultConfiguration = new ConfigurationBag(); + foreach($this->getConfigurationSpecification() as $spec) { + $this->defaultConfiguration->set($spec->getName(), $spec->getDefaultValue()); + } + } + return $this->defaultConfiguration; + } + + /** + * @param ConfigurationBag $configuration + */ + public function setConfiguration(ConfigurationBag $configuration) { + $this->configuration = $configuration; + return $this; + } + + /** + * Converts the handler to an array + * + * @return array + */ + public function toArray() { + return array( + 'name' => $this->getName(), + 'label' => $this->getLabel(), + 'configuration_spec' => $this->getConfigurationSpecification()->toArray(), + 'configuration' => $this->getConfiguration()->toArray(), + 'default_configuration' => $this->getDefaultConfiguration()->toArray(), + ); + } + + /** + * Returns the name of the output handler. + * + * @return string + */ + public function getName() { + $reflect = new \ReflectionClass($this); + $className = $reflect->getShortName(); + return $className; + } + + } diff --git a/Civi/FormProcessor/OutputHandler/Factory.php b/Civi/FormProcessor/OutputHandler/Factory.php new file mode 100644 index 0000000000000000000000000000000000000000..56208c29ded93870f3290978e611ee3e4c67c068 --- /dev/null +++ b/Civi/FormProcessor/OutputHandler/Factory.php @@ -0,0 +1,84 @@ +<?php + +/** + * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org> + * @license http://www.gnu.org/licenses/agpl-3.0.html + */ + + namespace Civi\FormProcessor\OutputHandler; + + use \Civi\FormProcessor\OutputHandler\OutputHandlerInterface; + use \Civi\FormProcessor\OutputHandler\OutputAllActionOutput; + use \Civi\FormProcessor\OutputHandler\FormatOutput; + + class Factory { + + /** + * @var array<OutputHandlerInterface> + */ + protected $handlers = array(); + + /** + * @var OutputHandlerInterface + */ + protected $defaultHandler; + + public function __construct() { + $this->defaultHandler = new OutputAllActionOutput(); + $this->addHandler($this->defaultHandler); + $this->addHandler(new FormatOutput()); + } + + /** + * Returns the default output handler + * + * @return OutputHandlerInterface + */ + public function getDefaultHandler() { + return $this->defaultHandler; + } + + /** + * Add a handler + * + * @param OutputHandlerInterface $handler + */ + public function addHandler(OutputHandlerInterface $handler) { + $this->handlers[$handler->getName()] = $handler; + return $this; + } + + /** + * Return the handler + * + * @param string $name + * @return OutputHandlerInterface|null + */ + public function getHandlerByName($name) { + if (isset($this->handlers[$name])) { + return $this->handlers[$name]; + } + return null; + } + + /** + * @return array<OutputHandlerInterface> + */ + public function getHandlers() { + return $this->handlers; + } + + /** + * Returns an array with the name and the array of the handler + * + * @return array<array> + */ + public function getHandlersAsArray() { + $return = array(); + foreach($this->handlers as $handler) { + $return[] = $handler->toArray(); + } + return $return; + } + + } diff --git a/Civi/FormProcessor/OutputHandler/FormatOutput.php b/Civi/FormProcessor/OutputHandler/FormatOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..2fc8f21c1e0fb478f21ed96b54b2376334a1a46c --- /dev/null +++ b/Civi/FormProcessor/OutputHandler/FormatOutput.php @@ -0,0 +1,77 @@ +<?php + +/** + * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org> + * @license http://www.gnu.org/licenses/agpl-3.0.html + */ + + namespace Civi\FormProcessor\OutputHandler; + + use \Civi\FormProcessor\Config\ConfigurationBag; + use \Civi\FormProcessor\Config\Specification; + use \Civi\FormProcessor\Config\SpecificationBag; + use \Civi\FormProcessor\Config\SpecificationCollection; + use \Civi\FormProcessor\Config\SpecificationFields; + use \Civi\FormProcessor\OutputHandler\AbstractOutputHandler; + use \Civi\FormProcessor\DataBag; + + use CRM_FormProcessor_ExtensionUtil as E; + + class FormatOutput extends AbstractOutputHandler { + + /** + * Get the configuration specification + * + * @return SpecificationBag + */ + public function getConfigurationSpecification() { + return new SpecificationBag(array( + new SpecificationCollection( + 'fields', + E::ts('Output fields'), + new SpecificationBag(array( + new SpecificationFields('field', E::ts('Field'), true), + new Specification('output_name', 'String', E::ts('Output name'), true), + )) + ) + )); + } + + /** + * Convert the action data to output data. + * + * @param DataBag $dataBag + * @return array + */ + public function handle(DataBag $dataBag) { + $output = array(); + $fields = $this->configuration->get('fields'); + foreach($fields as $field) { + $output[$field['output_name']] = $dataBag->getDataByAlias($field['field']); + } + return $output; + + foreach($dataBag->getAllInputs() as $input) { + $output['input'][$input->name] = $dataBag->getInputData($input); + } + foreach($dataBag->getAllActions() as $action) { + $actionData['action'] = $action->title; + $actionData['output'] = $dataBag->getActionData($action); + if (!is_array($actionData['output'])) { + $actionData['output'] = array(); + } + $output['action'][] = $actionData; + } + return $output; + } + + /** + * Returns the label of the output handler. + * + * @return string + */ + public function getLabel() { + return E::ts('Formal output'); + } + + } \ No newline at end of file diff --git a/Civi/FormProcessor/OutputHandler/OutputAllActionOutput.php b/Civi/FormProcessor/OutputHandler/OutputAllActionOutput.php new file mode 100644 index 0000000000000000000000000000000000000000..3899237d4f85aae928b3ce1603defad93f8da56b --- /dev/null +++ b/Civi/FormProcessor/OutputHandler/OutputAllActionOutput.php @@ -0,0 +1,60 @@ +<?php + +/** + * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org> + * @license http://www.gnu.org/licenses/agpl-3.0.html + */ + + namespace Civi\FormProcessor\OutputHandler; + + use \Civi\FormProcessor\Config\ConfigurationBag; + use \Civi\FormProcessor\Config\Specification; + use \Civi\FormProcessor\Config\SpecificationBag; + use \Civi\FormProcessor\OutputHandler\AbstractOutputHandler; + use \Civi\FormProcessor\DataBag; + + use CRM_FormProcessor_ExtensionUtil as E; + + class OutputAllActionOutput extends AbstractOutputHandler { + + /** + * Get the configuration specification + * + * @return SpecificationBag + */ + public function getConfigurationSpecification() { + return new SpecificationBag(); + } + + /** + * Convert the action data to output data. + * + * @param DataBag $dataBag + * @return array + */ + public function handle(DataBag $dataBag) { + $output = array(); + foreach($dataBag->getAllInputs() as $input) { + $output['input'][$input->name] = $dataBag->getInputData($input); + } + foreach($dataBag->getAllActions() as $action) { + $actionData['action'] = $action->title; + $actionData['output'] = $dataBag->getActionData($action); + if (!is_array($actionData['output'])) { + $actionData['output'] = array(); + } + $output['action'][] = $actionData; + } + return $output; + } + + /** + * Returns the label of the output handler. + * + * @return string + */ + public function getLabel() { + return E::ts('Output all the output from all the actions'); + } + + } \ No newline at end of file diff --git a/Civi/FormProcessor/OutputHandler/OutputHandlerInterface.php b/Civi/FormProcessor/OutputHandler/OutputHandlerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..02d4f7f0d4a972000c803584b2e92852b7141400 --- /dev/null +++ b/Civi/FormProcessor/OutputHandler/OutputHandlerInterface.php @@ -0,0 +1,68 @@ +<?php + +/** + * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org> + * @license http://www.gnu.org/licenses/agpl-3.0.html + */ + + namespace Civi\FormProcessor\OutputHandler; + + use \Civi\FormProcessor\Config\ConfigurationBag; + use \Civi\FormProcessor\Config\Specification; + use \Civi\FormProcessor\Config\SpecificationBag; + use \Civi\FormProcessor\DataBag; + + interface OutputHandlerInterface { + + /** + * Get the configuration specification + * + * @return SpecificationBag + */ + public function getConfigurationSpecification(); + + /** + * @return ConfigurationBag + */ + public function getConfiguration(); + + /** + * @return ConfigurationBag + */ + public function getDefaultConfiguration(); + + /** + * @param ConfigurationBag $configuration + */ + public function setConfiguration(ConfigurationBag $configuration); + + /** + * Convert the action data to output data. + * + * @param DataBag $dataBag + * @return array + */ + public function handle(DataBag $dataBag); + + /** + * Converts the handler to an array + * + * @return array + */ + public function toArray(); + + /** + * Returns the name of the output handler. + * + * @return string + */ + public function getName(); + + /** + * Returns the label of the output handler. + * + * @return string + */ + public function getLabel(); + + } diff --git a/Civi/FormProcessor/Type/AbstractType.php b/Civi/FormProcessor/Type/AbstractType.php index 2b771f2c726b63036f5c89c347974516fb7adc39..4f060056b0a4c8af522985e659ba31c3b8c978ff 100644 --- a/Civi/FormProcessor/Type/AbstractType.php +++ b/Civi/FormProcessor/Type/AbstractType.php @@ -128,6 +128,7 @@ 'label' => $this->label, 'configuration_spec' => $this->getConfigurationSpecification()->toArray(), 'configuration' => $this->getConfiguration()->toArray(), + 'default_configuration' => $this->getDefaultConfiguration()->toArray(), ); } diff --git a/ang/form_processor.ang.php b/ang/form_processor.ang.php index 520547d66c3c4130bca23087397e6219036819e3..e6dafb637323c0b70a758e75dc36b45e05de5215 100644 --- a/ang/form_processor.ang.php +++ b/ang/form_processor.ang.php @@ -35,6 +35,8 @@ return array ( array ( 'inputTypes' => \Civi::service('form_processor_type_factory')->getTypeLabels(), 'validators' => \Civi::service('form_processor_validation_factory')->getValidators(), + 'outputHandlers' => \Civi::service('form_processor_output_handler_factory')->getHandlersAsArray(), + 'defaultOutputHandler' => \Civi::service('form_processor_output_handler_factory')->getDefaultHandler()->toArray(), 'actionTypes' => $action_provider->getActions(), ), ); diff --git a/ang/form_processor/FormProcessorEditCtrl.html b/ang/form_processor/FormProcessorEditCtrl.html index 0d81dbfba4886471f24b37f9617a2d8cf1c6cc6f..60f24a91c90408ff301bda3cfe841f67d0e2c463 100644 --- a/ang/form_processor/FormProcessorEditCtrl.html +++ b/ang/form_processor/FormProcessorEditCtrl.html @@ -18,6 +18,8 @@ Required vars: formProcessor <div ng-include="'~/form_processor/FormProcessorEditCtrl/InputTable.html'"></div> <div ng-include="'~/form_processor/FormProcessorEditCtrl/ActionTable.html'"></div> + + <div ng-include="'~/form_processor/FormProcessorEditCtrl/OutputHandler.html'"></div> <div class="crm-submit-buttons"> <button crm-icon="fa-check" ng-click="save(false);" ng-disabled="editFormProcessorForm.$invalid || !isNameValid"> diff --git a/ang/form_processor/FormProcessorEditCtrl.js b/ang/form_processor/FormProcessorEditCtrl.js index bb6aaa03b3ab6d686c059944ffbc7731437ba7a8..b19c0b33e61e2a0bf4128920bdeb20fda05b07d6 100644 --- a/ang/form_processor/FormProcessorEditCtrl.js +++ b/ang/form_processor/FormProcessorEditCtrl.js @@ -12,7 +12,9 @@ title: '', is_active: '1', inputs: [], - actions: [] + actions: [], + output_handler: false, + output_handler_configuration: [], }; return reqs; } @@ -60,6 +62,11 @@ } $scope.deletedActions = []; + $scope.outputHandlers = CRM.form_processor.outputHandlers; + if (!$scope.formProcessor.output_handler) { + $scope.formProcessor.output_handler = CRM.form_processor.defaultOutputHandler; + } + $scope.$watch('formProcessor.name', function(newFormProcessorName, oldFrmProcessorName) { // Watch for changes in the name field crmApi('FormProcessorInstance', 'validatename', {'name': newFormProcessorName,'id': $scope.formProcessor.id}) @@ -89,98 +96,100 @@ return (!name) || name.match(/^[a-z0-9_]+$/) ? true : false; }; - $scope.save = function(goBack) { + $scope.save = function(goBack) { + var output_handler = angular.copy($scope.formProcessor.output_handler); + $scope.formProcessor.output_handler = output_handler.name; + $scope.formProcessor.output_handler_configuration = output_handler.configuration; var result = crmApi('FormProcessorInstance', 'create', $scope.formProcessor, true); result.then(function(data) { - if (data.is_error === 0 || data.is_error == '0') { - $scope.formProcessor.id = data.id; - - var apiCalls = []; - - angular.forEach($scope.deletedInputs, function(input, key) { - if (input.id) { - apiCalls.push(crmApi('FormProcessorInput', 'delete', {'id': input.id}, true)); - } - }); - - angular.forEach($scope.formProcessor.inputs, function(input, key) { - if (input.id < 1) { - delete input.id; - } - var type = angular.copy(input.type); - input.form_processor_instance_id = $scope.formProcessor.id; - input.configuration = input.type.configuration; - input.type = input.type.name; - apiCalls.push(crmApi('FormProcessorInput', 'create', input, true).then(function (input_result){ - input.id = input_result['id']; - input.type = type; - angular.forEach(input.validators, function(validator, validator_index){ - validator.form_processor_input_id = input.id; - var validator_type = angular.copy(validator.validator); - validator.validator = validator.validator.name; - crmApi('FormProcessorInputValidation', 'create', validator).then(function (validator_result) { - validator.validator = validator_type; - }); - }); - })); - }); - - angular.forEach($scope.deletedActions, function(action, key) { - if (action.id) { - apiCalls.push(crmApi('FormProcessorAction', 'delete', {'id': action.id}, true)); - } - }); - - var newActions = []; - var actionWeight = 0; - angular.forEach($scope.formProcessor.actions, function(action, key) { - var old_id = action.id; - if (action.id < 0) { - delete action.id; - } - var action_type = angular.copy(action.type); - action.form_processor_instance_id = $scope.formProcessor.id; - action.type = action.type.name; - action.weight = actionWeight; - apiCalls.push(crmApi('FormProcessorAction', 'create', action, true).then(function(action_result) { - action.id = action_result.id; - action.type = action_type; - if (old_id < 0) { - newActions[old_id] = action.id; - } - })); - actionWeight++; - }); - - // Update the mapping - $q.all(apiCalls).then(function(result){ - // We have saved the last action. - // Lets fix the mapping of the actions so that the mapping with an id < 0 - // will be resolved to their new id - angular.forEach($scope.formProcessor.actions, function(action, key) { - var actionChanged = false; - angular.forEach(action.mapping, function (field, parameter) { - var splitted_field = field.split('.'); - if (splitted_field[0] == 'action' && splitted_field[1] < 0) { - action.mapping[parameter] = 'action.'+newActions[splitted_field[1]]+'.'+splitted_field[2]; - actionChanged = true; - } - }); - if (actionChanged) { - var action_type = angular.copy(action.type); - action.type = action.type.name; - crmApi('FormProcessorAction', 'create', action, true).then(function(action_result) { - action.type = action_type; - }); - } - }); - }); - - $scope.editFormProcessorForm.$setPristine(); - - if (goBack) { - window.location.href = '#/formprocessors'; - } + $scope.formProcessor.id = data.id; + $scope.formProcessor.output_handler = output_handler; + + var apiCalls = []; + + angular.forEach($scope.deletedInputs, function(input, key) { + if (input.id) { + apiCalls.push(crmApi('FormProcessorInput', 'delete', {'id': input.id}, true)); + } + }); + + angular.forEach($scope.formProcessor.inputs, function(input, key) { + if (input.id < 1) { + delete input.id; + } + var type = angular.copy(input.type); + input.form_processor_instance_id = $scope.formProcessor.id; + input.configuration = input.type.configuration; + input.type = input.type.name; + apiCalls.push(crmApi('FormProcessorInput', 'create', input, true).then(function (input_result){ + input.id = input_result['id']; + input.type = type; + angular.forEach(input.validators, function(validator, validator_index){ + validator.form_processor_input_id = input.id; + var validator_type = angular.copy(validator.validator); + validator.validator = validator.validator.name; + crmApi('FormProcessorInputValidation', 'create', validator).then(function (validator_result) { + validator.validator = validator_type; + }); + }); + })); + }); + + angular.forEach($scope.deletedActions, function(action, key) { + if (action.id) { + apiCalls.push(crmApi('FormProcessorAction', 'delete', {'id': action.id}, true)); + } + }); + + var newActions = []; + var actionWeight = 0; + angular.forEach($scope.formProcessor.actions, function(action, key) { + var old_id = action.id; + if (action.id < 0) { + delete action.id; + } + var action_type = angular.copy(action.type); + action.form_processor_instance_id = $scope.formProcessor.id; + action.type = action.type.name; + action.weight = actionWeight; + apiCalls.push(crmApi('FormProcessorAction', 'create', action, true).then(function(action_result) { + action.id = action_result.id; + action.type = action_type; + if (old_id < 0) { + newActions[old_id] = action.id; + } + })); + actionWeight++; + }); + + // Update the mapping + $q.all(apiCalls).then(function(result){ + // We have saved the last action. + // Lets fix the mapping of the actions so that the mapping with an id < 0 + // will be resolved to their new id + angular.forEach($scope.formProcessor.actions, function(action, key) { + var actionChanged = false; + angular.forEach(action.mapping, function (field, parameter) { + var splitted_field = field.split('.'); + if (splitted_field[0] == 'action' && splitted_field[1] < 0) { + action.mapping[parameter] = 'action.'+newActions[splitted_field[1]]+'.'+splitted_field[2]; + actionChanged = true; + } + }); + if (actionChanged) { + var action_type = angular.copy(action.type); + action.type = action.type.name; + crmApi('FormProcessorAction', 'create', action, true).then(function(action_result) { + action.type = action_type; + }); + } + }); + }); + + $scope.editFormProcessorForm.$setPristine(); + + if (goBack) { + window.location.href = '#/formprocessors'; } }); }; @@ -288,6 +297,28 @@ } }); }; + + $scope.editOutputHandler = function editOutputHandler() { + var options = CRM.utils.adjustDialogDefaults({ + autoOpen: false, + width: '40%', + height: 'auto', + title: ts('Edit output handler') + }); + + $scope.updateFields($scope.formProcessor); + var model = { + formProcessor: $scope.formProcessor, + outputHandler: $scope.formProcessor.output_handler, + outputHandlers: $scope.outputHandlers + }; + dialogService.open('OutputHandlerDialog', '~/form_processor/OutputHandlerDialogCtrl.html', model, options) + .then(function(data) { + $scope.editFormProcessorForm.$setDirty(); + console.log(data); + $scope.formProcessor.output_handler = data.outputHandler; + }); + }; $scope.updateFields = function updateFields(formProcessor, selectedAction) { formProcessor.fields = []; diff --git a/ang/form_processor/FormProcessorEditCtrl/OutputHandler.html b/ang/form_processor/FormProcessorEditCtrl/OutputHandler.html new file mode 100644 index 0000000000000000000000000000000000000000..e65c90a6a27362c78f135d5ce9792b650eaf5929 --- /dev/null +++ b/ang/form_processor/FormProcessorEditCtrl/OutputHandler.html @@ -0,0 +1,6 @@ +<div class="crm-block" ng-form="OutputHandlerForm" crm-ui-id-scope> + <h3>{{ts('Output Handler')}}: {{formProcessor.output_handler.label}}</h3> + <div><a crm-icon="fa-edit" class="crm-hover-button" ng-click="editOutputHandler()" title="{{ts('Change and configure output handler')}}"> + {{ts('Change and configure output handler')}} + </a></div> +</div> \ No newline at end of file diff --git a/ang/form_processor/InputDialogCtrl.html b/ang/form_processor/InputDialogCtrl.html index 6ca5aef8bc80948d37758e34b94661c812fe6b13..51a16e1ee7df9e7fdb7b1e60a5e37e53caba3b69 100644 --- a/ang/form_processor/InputDialogCtrl.html +++ b/ang/form_processor/InputDialogCtrl.html @@ -54,7 +54,7 @@ ng-options="newValidator.label for newValidator in validators"> <option value="">{{ts('- Select validation -')}}</option> </select> - <button crm-icon="fa-check" ng-click="addValidator(newValidator)">{{ts('Add validation rule')}}</button> + <button crm-icon="fa-plus" ng-click="addValidator(newValidator)">{{ts('Add validation rule')}}</button> </div> </div> diff --git a/ang/form_processor/OutputHandlerDialogCtrl.html b/ang/form_processor/OutputHandlerDialogCtrl.html new file mode 100644 index 0000000000000000000000000000000000000000..4c04bfba6204f9442dd57ca26e2abf1aabd6bec2 --- /dev/null +++ b/ang/form_processor/OutputHandlerDialogCtrl.html @@ -0,0 +1,28 @@ +<div ng-controller="OutputHandlerDialogCtrl" class="crm-block crm-form-block"> + <div class="crm-block crm-form-block" ng-form="OutputHandlerForm" crm-ui-id-scope> + <div class="crm-section"> + <h2>{{ts('Output handler')}}</h2> + <select + crm-ui-id="OutputHandlerForm.outputHandler" + name="type" + ui-jq="select2" + ui-options="{dropdownAutoWidth : true, allowClear: true}" + ng-model="outputHandler" + ng-options="handler.label for handler in outputHandlers"> + <option value="">{{ts('- Select output handler -')}}</option> + </select> + </div> + <div class="crm-section"> + <crm-form-processor-specification-bag + specification-bag="outputHandler.configuration_spec" + configuration="outputHandler.configuration" + fields="formProcessor.fields" + > + </crm-form-processor-specification-bag> + </div> + </div> + + <button crm-icon="fa-check" ng-click="saveClick()" ng-disabled="OutputHandlerForm.$invalid">{{ts('Save and close')}}</button> + <button crm-icon="fa-times" ng-click="cancelClick()">{{ts('Cancel')}}</button> + +</div> \ No newline at end of file diff --git a/ang/form_processor/OutputHandlerDialogCtrl.js b/ang/form_processor/OutputHandlerDialogCtrl.js new file mode 100644 index 0000000000000000000000000000000000000000..89b0568aecc1480b9a4c6bbb8a315c1ef9caee6c --- /dev/null +++ b/ang/form_processor/OutputHandlerDialogCtrl.js @@ -0,0 +1,29 @@ +(function(angular, $, _) { + + angular.module('form_processor').controller('OutputHandlerDialogCtrl', function InputDialogCtrl($scope, dialogService) { + $scope.ts = CRM.ts(null); + + $scope.outputHandler = angular.copy($scope.model.outputHandler); + $scope.outputHandlers = $scope.model.outputHandlers; + $scope.formProcessor = $scope.model.formProcessor; + + angular.forEach($scope.outputHandlers, function (outputHandler, key) { + outputHandler.configuration = outputHandler.default_configuration; + if (outputHandler.name == $scope.outputHandler.name) { + outputHandler.configuration = angular.copy($scope.outputHandler.configuration); + $scope.outputHandler = outputHandler; + } + }); + + $scope.saveClick = function() { + $scope.model.outputHandler = $scope.outputHandler; + dialogService.close('OutputHandlerDialog', $scope.model); + }; + + $scope.cancelClick = function() { + dialogService.cancel('OutputHandlerDialog'); + }; + + }); + +})(angular, CRM.$, CRM._); \ No newline at end of file diff --git a/ang/form_processor/config/crmFormProcessorSpecification.html b/ang/form_processor/config/crmFormProcessorSpecification.html new file mode 100644 index 0000000000000000000000000000000000000000..c6d813e3761347cc2eda569fb585fa08329d8172 --- /dev/null +++ b/ang/form_processor/config/crmFormProcessorSpecification.html @@ -0,0 +1,50 @@ +<div ng-if="specification.type == 'specification'"> + <input + crm-ui-field="{name: 'specification.name', title: specification.title, required: specification.required}" + type="text" + name="{{specification.name}}" + ng-model="configuration[specification.name]" + class="big crm-form-text" + ng-required="specification.required" + /> +</div> +<div ng-if="specification.type == 'fields'"> + <select + crm-ui-id="{name: 'specification.name', title: specification.title, required: specification.required}" + name="{{specification.name}}" + ui-jq="select2" + ui-options="{allowClear: true}" + ng-model="configuration[specification.name]" + ng-required="specification.required" + ng-options="field.name as field.label for field in fields"> + <option value=""> - {{specification.title}} - </option> + </select> +</div> +<div ng-if="specification.type == 'collection'"> + <table> + <thead> + <th ng-repeat="collection_spec in specification.specification_bag.parameter_specifications"> + {{collection_spec.title}} + </th> + <th></th> + </thead> + <tbody> + <tr ng-repeat="collection_config in configuration[specification.name]" ng-class-even="'crm-entity even-row even'" ng-class-odd="'crm-entity odd-row odd'"> + <td ng-repeat="collection_spec in specification.specification_bag.parameter_specifications"> + <crm-form-processor-specification + specification="collection_spec" + configuration="collection_config" + fields="fields" + > + </crm-form-processor-specification> + </td> + <td> + <a crm-icon="fa-trash" class="crm-hover-button" ng-click="removeItem(collection_config)" title="{{ts('Remove')}}">{{ts('Remove')}}</a> + </td> + </tr> + </tbody> + <button crm-icon="fa-plus" ng-click="addItemToCollection(specification.specification_bag)">{{ts('Add item')}}</button> +</table> +</div> + +<div class="description" ng-if="specification.description" ng-bind-html="specification.description"></div> \ No newline at end of file diff --git a/ang/form_processor/config/crmFormProcessorSpecification.js b/ang/form_processor/config/crmFormProcessorSpecification.js new file mode 100644 index 0000000000000000000000000000000000000000..09aeeaab9e26a00e1f3200d98daefc9c3e49da9d --- /dev/null +++ b/ang/form_processor/config/crmFormProcessorSpecification.js @@ -0,0 +1,56 @@ +(function(angular, $, _) { + + angular.module('form_processor').directive('crmFormProcessorSpecification', function($compile) { + return { + restrict: 'E', + transclude: true, + scope: { + specification: '=specification', + fields: '=fields', + configuration: '=configuration', + }, + templateUrl: '~/form_processor/config/crmFormProcessorSpecification.html', + // Code below is from: https://stackoverflow.com/a/19172067/3853493 + // And is taken from that site because we need recursion of this directive. + compile: function(tElement, tAttr, transclude) { + //We are removing the contents/innerHTML from the element we are going to be applying the + //directive to and saving it to adding it below to the $compile call as the template + var contents = tElement.contents().remove(); + var compiledContents; + return function(scope, iElement, iAttr) { + scope.ts = CRM.ts(null); + + scope.addItemToCollection = function addItemToCollection(specificationBag) { + var item_config = angular.copy(specificationBag.default_configuration); + scope.configuration[scope.specification.name].push(item_config); + }; + + scope.removeItem = function removeItem(item) { + var index = scope.configuration[scope.specification.name].indexOf(item); + if (index >= 0) { + scope.configuration[scope.specification.name].splice(index, 1); + } + }; + + + if(!compiledContents) { + //Get the link function with the contents frome top level template with + //the transclude + compiledContents = $compile(contents, transclude); + } + //Call the link function to link the given scope and + //a Clone Attach Function, http://docs.angularjs.org/api/ng.$compile : + // "Calling the linking function returns the element of the template. + // It is either the original element passed in, + // or the clone of the element if the cloneAttachFn is provided." + compiledContents(scope, function(clone, scope) { + //Appending the cloned template to the instance element, "iElement", + //on which the directive is to used. + iElement.append(clone); + }); + }; + } + }; + }); + +})(angular, CRM.$, CRM._); \ No newline at end of file diff --git a/ang/form_processor/config/crmFormProcessorSpecificationBag.html b/ang/form_processor/config/crmFormProcessorSpecificationBag.html new file mode 100644 index 0000000000000000000000000000000000000000..a2fe918d4ef48732bc07de8bee8e5446acc3c32a --- /dev/null +++ b/ang/form_processor/config/crmFormProcessorSpecificationBag.html @@ -0,0 +1,9 @@ +<ng-repeat ng-repeat="spec in specificationBag.parameter_specifications"> + <div class="crm-section"> + <crm-form-processor-specification + specification="spec" + configuration="configuration" + fields="fields" + ></crm-form-processor-specification> + </div> +</ng-repeat> \ No newline at end of file diff --git a/ang/form_processor/config/crmFormProcessorSpecificationBag.js b/ang/form_processor/config/crmFormProcessorSpecificationBag.js new file mode 100644 index 0000000000000000000000000000000000000000..d8ee7e89057a89bd8da899eaab649a91a7ccc100 --- /dev/null +++ b/ang/form_processor/config/crmFormProcessorSpecificationBag.js @@ -0,0 +1,17 @@ +(function(angular, $, _) { + + angular.module('form_processor').directive('crmFormProcessorSpecificationBag', function() { + return { + restrict: 'E', + scope: { + specificationBag: '=specificationBag', + fields: '=fields', + configuration: '=configuration', + }, + templateUrl: '~/form_processor/config/crmFormProcessorSpecificationBag.html', + link: function($scope, element, attrs){ + } + }; + }); + +})(angular, CRM.$, CRM._); \ No newline at end of file diff --git a/ang/form_processor/crmFormProcessorOutputHandler/FormatActionOutput.html b/ang/form_processor/crmFormProcessorOutputHandler/FormatActionOutput.html new file mode 100644 index 0000000000000000000000000000000000000000..4975abc4025341f505705d009568bfa14d014c20 --- /dev/null +++ b/ang/form_processor/crmFormProcessorOutputHandler/FormatActionOutput.html @@ -0,0 +1,40 @@ +<div class="crm-block crm-form-block" ng-form="FormatActionOutputForm" ng-controller="FormatActionOutputCtrl"> + <h4>{{ts('Output Specification')}}</h4> + <select + crm-ui-id="FormatActionOutputForm.field" + name="type" + ui-jq="select2" + ui-options="{dropdownAutoWidth : true, allowClear: true}" + ng-model="field" + ng-options="field.label for field in fields" + > + <option value="">{{ts('- Select field to add to the output -')}}</option> + </select> + <button crm-icon="fa-check" ng-click="addField(field)">{{ts('Add field to output')}}</button> + + <table> + <thead> + <tr> + <th>{{ts('Field')}}</th> + <th>{{ts('Output name')}}</th> + <th></th> + </tr> + </thead> + <tbody> + <tr ng-repeat="configuration_field in configuration " ng-class-even="'crm-entity even-row even'" ng-class-odd="'crm-entity odd-row odd'"> + <td>{{configuration_field.field.label}}</td> + <td> + <input + type="text" + ng-model="configuration_field.output_name" + class="big crm-form-text" + required + autofocus + /> + </td> + <td> + </td> + </tr> + </tbody> + +</div> \ No newline at end of file diff --git a/ang/form_processor/crmFormProcessorOutputHandler/FormatActionOutputCtrl.js b/ang/form_processor/crmFormProcessorOutputHandler/FormatActionOutputCtrl.js new file mode 100644 index 0000000000000000000000000000000000000000..6775e79e0176c8e2b15bca417d941c8578c37b77 --- /dev/null +++ b/ang/form_processor/crmFormProcessorOutputHandler/FormatActionOutputCtrl.js @@ -0,0 +1,22 @@ +(function(angular, $, _) { + + angular.module('form_processor').controller('FormatActionOutputCtrl', function FormatActionOutputCtrl($scope) { + $scope.ts = CRM.ts(null); + + $scope.configuration = outputHandler.configuration; + + //$scope.watchCollection('configuration') + + $scope.addField = function(field) { + var splitted_field = field.name.split('.'); + var output_name = splitted_field[splitted_field.length - 1]; + $scope.configuration.push({ + 'field': field, + 'output_name': output_name + }); + }; + + + }); + +})(angular, CRM.$, CRM._); diff --git a/ang/form_processor/crmFormProcessorOutputHandler/OutputAllActionOutput.html b/ang/form_processor/crmFormProcessorOutputHandler/OutputAllActionOutput.html new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/api/v3/FormProcessorInstance/Create.php b/api/v3/FormProcessorInstance/Create.php index 2167e42b4b0879675b381c5d932a196a2bbf3825..d315b94d4de49a4b932778af15d49de8e7f5cd97 100644 --- a/api/v3/FormProcessorInstance/Create.php +++ b/api/v3/FormProcessorInstance/Create.php @@ -37,6 +37,16 @@ function _civicrm_api3_form_processor_instance_create_spec(&$spec) { 'type' => CRM_Utils_Type::T_TEXT, 'api.required' => true ); + $spec['output_handler'] = array( + 'title' => E::ts('Output handler'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => true + ); + $spec['output_handler_configuration'] = array( + 'title' => E::ts('Output handler configuration'), + 'type' => CRM_Utils_Type::T_TEXT, + 'api.required' => false + ); } /** diff --git a/api/v3/FormProcessorInstance/Get.php b/api/v3/FormProcessorInstance/Get.php index 2de109f2d2a09ef7dd8e70b05a574d1f9ff770f8..085b7b88bb2bc129fd97580e65a7f4d8857a4021 100644 --- a/api/v3/FormProcessorInstance/Get.php +++ b/api/v3/FormProcessorInstance/Get.php @@ -11,15 +11,21 @@ function civicrm_api3_form_processor_instance_get($params) { $returnValues = CRM_FormProcessor_BAO_FormProcessorInstance::getValues($params); foreach($returnValues as $index => $formProcessor) { + // Convert inputs to arrays foreach($formProcessor['inputs'] as $key => $input) { $returnValues[$index]['inputs'][$key]['type'] = $input['type']->toArray(); foreach($input['validators'] as $validator_key => $validator) { $returnValues[$index]['inputs'][$key]['validators'][$validator_key]['validator'] = $validator['validator']->toArray(); } } + + // Convert action object to arrays foreach($formProcessor['actions'] as $key => $action) { $returnValues[$index]['actions'][$key]['type'] = $action['type']->toArray(); } + + // Convert output handler object to array + $returnValues[$index]['output_handler'] = $returnValues[$index]['output_handler']->toArray(); } return civicrm_api3_create_success($returnValues, $params, 'FormProcessorInstance', 'Get'); } diff --git a/form_processor.php b/form_processor.php index 13845c385fe2f41fca5afa2bcb286a8e2acfce34..ac5b817ed81aa36a8c200b53a138c7cc8190335a 100644 --- a/form_processor.php +++ b/form_processor.php @@ -14,6 +14,9 @@ use \Symfony\Component\DependencyInjection\Definition; function form_processor_civicrm_container(ContainerBuilder $container) { // Register the TypeFactory $container->setDefinition('form_processor_type_factory', new Definition('Civi\FormProcessor\Type\Factory')); + // Register the OutputHandlerFactory + $container->setDefinition('form_processor_output_handler_factory', new Definition('Civi\FormProcessor\OutputHandler\Factory')); + // Register the validationFactory $validationFactoryDefinition = new Definition('Civi\FormProcessor\Validation\Factory'); $validationFactoryDefinition->setFactory(array('Civi\FormProcessor\Validation\Factory', 'singleton')); $container->setDefinition('form_processor_validation_factory', $validationFactoryDefinition); diff --git a/sql/create_civicrm_form_processor.sql b/sql/create_civicrm_form_processor.sql index 8f0b9be16165f6bd6d1cb6b9183cb5c99401fc83..14e7a430cf9e40750cad752f138b9e2e76752e54 100644 --- a/sql/create_civicrm_form_processor.sql +++ b/sql/create_civicrm_form_processor.sql @@ -4,6 +4,8 @@ CREATE TABLE IF NOT EXISTS `civicrm_form_processor_instance` ( `title` VARCHAR(128) NULL, `is_active` TINYINT NULL DEFAULT 1, `description` TEXT NULL, + `output_handler` VARCHAR(88) NOT NULL, + `output_handler_configuration` TEXT NULL, `created_date` DATE NULL, `created_user_id` INT NULL, `modified_date` DATE NULL,