Commit 2efbfde4 authored by jaapjansma's avatar jaapjansma

added condition functionality to the action provider.

parent 95248281
......@@ -3,6 +3,7 @@
namespace Civi\ActionProvider\Action;
use \Civi\ActionProvider\Provider;
use \Civi\ActionProvider\Condition\AbstractCondition;
use \Civi\ActionProvider\Parameter\ParameterBagInterface;
use \Civi\ActionProvider\Parameter\ParameterBag;
use \Civi\ActionProvider\Parameter\SpecificationBag;
......@@ -46,6 +47,11 @@ abstract class AbstractAction implements \JsonSerializable {
* @var Provider
*/
protected $provider;
/**
* @var AbstractCondition
*/
private $condition;
public function __construct() {
......@@ -117,17 +123,26 @@ abstract class AbstractAction implements \JsonSerializable {
* This method is basicly a wrapper around doAction.
*
* @param ParameterBagInterface $parameters;
* @param ParameterBagInterface $conditionParameters
* @param ParameterBagInterface $invalidConditionOutput
* @return ParameterBagInterface
* @throws \Exception
*/
public function execute(ParameterBagInterface $parameters) {
public function execute(ParameterBagInterface $parameters, ParameterBagInterface $conditionParameters, ParameterBagInterface $invalidConditionOutput) {
if (!$this->validateConfiguration()) {
throw new InvalidConfigurationException("Found invalid configuration for the action: ".$this->getTitle());
}
if (!$this->validateParameters($parameters)) {
throw new InvalidParameterException("Found invalid parameters for the action: ".$this->getTitle());
}
$output = $this->createParameterBag();
$this->doAction($parameters, $output);
if ($this->condition && !$this->condition->isConditionValid($conditionParameters)) {
// Condition is invalid
return $invalidConditionOutput;
}
// Condition is valid or no condition class is set
$output = $this->createParameterBag();
$this->doAction($parameters, $output);
return $output;
}
......@@ -164,6 +179,7 @@ abstract class AbstractAction implements \JsonSerializable {
/**
* @param ParameterBag $configuration
* @return AbstractAction
*/
public function setConfiguration(ParameterBag $configuration) {
$this->configuration = $configuration;
......@@ -184,14 +200,27 @@ abstract class AbstractAction implements \JsonSerializable {
public function getTags() {
return array();
}
/**
* Sets the condition object
*
* @param AbstractCondition|null $condition
* @return AbstractAction
*/
public function setCondition(AbstractCondition $condition=null) {
$this->condition = $condition;
return $this;
}
/**
* Sets the provider class
*
* @return Provider
*
* @param Provider $provider
* @return AbstractAction
*/
public function setProvider(Provider $provider) {
$this->provider = $provider;
return $this;
}
/**
......
<?php
/**
* @author Jaap Jansma <jaap.jansma@civicoop.org>
* @license AGPL-3.0
*/
namespace Civi\ActionProvider\Condition;
use \Civi\ActionProvider\Provider;
use \Civi\ActionProvider\Parameter\ParameterBagInterface;
use \Civi\ActionProvider\Parameter\ParameterBag;
use \Civi\ActionProvider\Parameter\SpecificationBag;
abstract class AbstractCondition implements \JsonSerializable {
/**
* @var ParameterBag
*/
protected $configuration;
/**
* @var ParameterBag
*/
protected $defaultConfiguration;
/**
* @var Provider
*/
protected $provider;
/**
* @param \Civi\ActionProvider\Parameter\ParameterBagInterface $parameterBag
*
* @return bool
*/
abstract public function isConditionValid(ParameterBagInterface $parameterBag);
/**
* Returns the specification of the configuration options for the actual condition.
*
* @return SpecificationBag
*/
abstract public function getConfigurationSpecification();
/**
* Returns the specification of the parameters of the actual condition.
*
* @return SpecificationBag
*/
abstract public function getParameterSpecification();
/**
* Returns the human readable title of this condition
*/
abstract public function getTitle();
/**
* @return ParameterBag
*/
public function getDefaultConfiguration() {
return $this->defaultConfiguration;
}
/**
* @return ParameterBag
*/
public function getConfiguration() {
return $this->configuration;
}
/**
* @param ParameterBag $configuration
* @return AbstractAction
*/
public function setConfiguration(ParameterBag $configuration) {
$this->configuration = $configuration;
return $this;
}
/**
* Sets the default values of this action
*/
public function setDefaults() {
$this->configuration = $this->createParameterBag();
$this->defaultConfiguration = $this->createParameterBag();
foreach($this->getConfigurationSpecification() as $spec) {
if ($spec->getDefaultValue() !== null) {
$this->configuration->setParameter($spec->getName(), $spec->getDefaultValue());
$this->defaultConfiguration->setParameter($spec->getName(), $spec->getDefaultValue());
}
}
}
/**
* Creates a parameterBag object.
*
* @return ParameterBagInterface
*/
protected function createParameterBag() {
return $this->provider->createParameterBag();
}
/**
* Sets the provider class
*
* @param Provider $provider
* @return AbstractAction
*/
public function setProvider(Provider $provider) {
$this->provider = $provider;
return $this;
}
/**
* Returns the system name of the action.
*
* We generate one based on the namespace of the class
* and the class name.
*
* @return string
* @throws \Exception
*/
public function getName() {
$reflect = new \ReflectionClass($this);
$className = $reflect->getShortName();
return $className;
}
/**
* Converts the object to an array.
*
* @return array
*/
public function toArray() {
$return['parameter_spec'] = $this->getParameterSpecification()->toArray();
$return['configuration_spec'] = $this->getConfigurationSpecification()->toArray();
$return['default_configuration'] = null;
if ($this->getDefaultConfiguration()) {
$return['default_configuration'] = $this->getDefaultConfiguration()->toArray();
}
$return['name'] = $this->getName();
$return['title'] = $this->getTitle();
return $return;
}
/**
* Returns the data structure to serialize it as a json
*/
public function jsonSerialize() {
$return['name'] = $this->getName();
$return['title'] = $this->getTitle();
// An empty array goes wrong with the default confifuration.
if (empty($return['default_configuration'])) {
$return['default_configuration'] = new \stdClass();;
}
return $return;
}
}
\ No newline at end of file
<?php
/**
* @author Jaap Jansma <jaap.jansma@civicoop.org>
* @license AGPL-3.0
*/
namespace Civi\ActionProvider\Condition;
use \Civi\ActionProvider\Parameter\ParameterBagInterface;
use \Civi\ActionProvider\Parameter\ParameterBag;
use Civi\ActionProvider\Parameter\Specification;
use \Civi\ActionProvider\Parameter\SpecificationBag;
use CRM_ActionProvider_ExtensionUtil as E;
class ParameterIsEmpty extends AbstractCondition {
/**
* @param \Civi\ActionProvider\Parameter\ParameterBagInterface $parameterBag
*
* @return bool
*/
public function isConditionValid(ParameterBagInterface $parameterBag) {
$parameter = $parameterBag->getParameter('parameter');
if (empty($parameter)) {
return true;
}
return false;
}
/**
* Returns the specification of the configuration options for the actual condition.
*
* @return SpecificationBag
*/
public function getConfigurationSpecification() {
return new SpecificationBag();
}
/**
* Returns the specification of the parameters of the actual condition.
*
* @return SpecificationBag
*/
public function getParameterSpecification() {
return new SpecificationBag(array(
new Specification('parameter', 'String', E::ts('Parameter')),
));
}
/**
* Returns the human readable title of this condition
*/
public function getTitle() {
return E::ts('Parameter is empty');
}
}
\ No newline at end of file
......@@ -24,6 +24,18 @@ class Provider {
* All the actions including the inactive ones.
*/
protected $allActions = array();
/**
* @var array
* All the condition which are available to be used in this context.
*/
protected $availableConditions = array();
/**
* @var array
* Contains all possible conditions.
*/
protected $allConditions = array();
public function __construct() {
$actions = array(
......@@ -42,13 +54,23 @@ class Provider {
new \Civi\ActionProvider\Action\Website\CreateUpdateWebsite(),
new \Civi\ActionProvider\Action\Website\GetWebsite(),
);
$conditions = array(
new \Civi\ActionProvider\Condition\ParameterIsEmpty(),
);
foreach($actions as $action) {
$action->setProvider($this);
$this->allActions[$action->getName()] = $action;
}
foreach($conditions as $condition) {
$condition->setProvider($this);
$this->allConditions[$condition->getName()] = $condition;
}
$this->availableActions = array_filter($this->allActions, array($this, 'filterActions'));
$this->availableConditions = array_filter($this->allConditions, array($this, 'filterConditions'));
}
/**
......@@ -87,6 +109,44 @@ class Provider {
}
return null;
}
/**
* Returns all available conditins
*/
public function getConditions() {
return $this->availableConditions;
}
/**
* Adds a condition to the list of available conditions.
*
* This function might be used by extensions to add their own conditions to the system.
*
* @param \Civi\ActionProvider\Condition\AbstractCondition $condition
* @return Provider
* @throws \Exception
*/
public function addCondition(\Civi\ActionProvider\Condition\AbstractCondition $condition) {
$condition->setProvider($this);
$this->allConditions[$condition->getName()] = $condition;
$this->availableConditions = array_filter($this->allConditions, array($this, 'filterConditions'));
return $this;
}
/**
* Returns a condition by its name.
*
* @return \Civi\ActionProvider\Condition\AbstractCondition|null when condition is not found.
*/
public function getConditionByName($name) {
if (isset($this->availableConditions[$name])) {
$condition = clone $this->availableConditions[$name];
$condition->setProvider($this);
$condition->setDefaults();
return $condition;
}
return null;
}
/**
* Returns a new ParameterBag
......@@ -131,5 +191,20 @@ class Provider {
protected function filterActions(\Civi\ActionProvider\Action\AbstractAction $action) {
return true;
}
/**
* Filter the conditions array and keep certain condition.
*
* This function might be override in a child class to filter out certain conditions which do
* not make sense in that context.
*
* @param \Civi\ActionProvider\Condition\AbstractCondition $condition
* The condition to filter.
* @return bool
* Returns true when the element is valid, false when the element should be disregarded.
*/
protected function filterConditions(\Civi\ActionProvider\Condition\AbstractCondition $condition) {
return true;
}
}
<div ng-if="condition" class="crm-block crm-form-block">
<ng-repeat ng-repeat="spec in condition.configuration_spec">
<div ng-if="spec.options"
crm-ui-field="{name: spec.name, title: spec.title, required: spec.required}">
<select
style="width: 90%;"
crm-ui-select
crm-ui-id="{{spec.name}}"
ui-options="{allowClear: true}"
name="{{spec.name}}"
ng-model="configuration[spec.name]"
ng-required="spec.required"
>
<option ng-repeat="(key,value) in spec.options" value="{{key}}">{{value}}</option>
</select>
</div>
<div ng-if="spec.fk_entity"
crm-ui-field="{name: spec.name, title: spec.title, required: spec.required}">
<input
crm-entityref="{entity: spec.fk_entity, select: {allowClear: true, placeholder: spec.title}}"
name="{{spec.name}}"
crm-ui-id="{{spec.name}}"
ng-model="configuration[spec.name]"
ng-required="spec.required"
/>
</div>
<div ng-if="!spec.options && !spec.fk_entity"
crm-ui-field="{name: spec.name, title: spec.title, required: spec.required}">
<input
type="text"
crm-ui-id="{{spec.name}}"
name="{{spec.name}}"
ng-model="configuration[spec.name]"
class="big crm-form-text"
ng-required="spec.required"
/>
</div>
<div class="description" ng-if="spec.description" ng-bind-html="spec.description"></div>
</ng-repeat>
<div ng-if="fields" class="crm-block crm-form-block" ng-form="input_mapper">
<h3>{{ts('Parameters')}}</h3>
<ng-repeat ng-repeat="spec in condition.parameter_spec">
<div crm-ui-field="{name: 'input_mapper.'+spec.name, title: spec.title, required: spec.required}">
<select
style="width: 90%;"
crm-ui-id="input_mapper.{{spec.name}}"
name="{{spec.name}}"
ui-jq="select2"
ui-options="{allowClear: true}"
ng-model="action.condition_configuration.parameter_mapping[spec.name]"
ng-required="spec.required"
ng-options="field.name as field.label for field in fields">
<option value=""></option>
</select>
</div>
</ng-repeat>
</div>
<div ng-if="fields" class="crm-block crm-form-block" ng-form="output_mapper">
<h3>{{ts('Map output data if condition is not valid')}}</h3>
<ng-repeat ng-repeat="spec in action.type.output_spec">
<div crm-ui-field="{name: 'output_mapper.'+spec.name, title: spec.title}">
<select
style="width: 90%;"
crm-ui-id="output_mapper.{{spec.name}}"
name="{{spec.name}}"
ui-jq="select2"
ui-options="{allowClear: true}"
ng-model="action.condition_configuration.output_mapping[spec.name]"
ng-options="field.name as field.label for field in fields">
<option value=""></option>
</select>
</div>
</ng-repeat>
</div>
</div>
\ No newline at end of file
(function(angular, $, _) {
// "crmApConditionConfiguration" is a basic skeletal directive.
// Example usage: <crm-ap-condition-configuration configuration="condition.configuration" condition="condition.type"></crm-ap-condition-configuration>
angular.module('action_provider').directive('crmApConditionConfiguration', ["crmApi", function(crmApi) {
var conditions = {};
return {
restrict: 'E',
templateUrl: '~/action_provider/crmApConditionConfiguration.html',
scope: {
action: '=',
context: '@',
configuration: '=',
fields: '=?',
mapping: '=?',
},
link: function($scope, $el, $attr) {
$scope.ts = CRM.ts(null);
$scope.condition = {};
if (!($scope.context in conditions)) {
conditions[$scope.context] = {};
}
$scope.$watch('action.condition_configuration', function(newCondition, oldCondition) {
if (!newCondition) {
$scope.condition = null;
return;
}
if ($scope.action.condition_configuration.name in conditions[$scope.context]) {
$scope.condition = conditions[$scope.context][$scope.action.condition_configuration.name];
return;
}
crmApi('ActionProvider', 'getcondition', {name: $scope.action.condition_configuration.name, context: $scope.context}).
then(function (data) {
conditions[$scope.context][$scope.action.condition_configuration.name] = data;
$scope.condition = data;
});
});
}
};
}]);
})(angular, CRM.$, CRM._);
<?php
/**
* @author Jaap Jansma <jaap.jansma@civicoop.org>
* @license AGPL-3.0
*/
use CRM_ActionProvider_ExtensionUtil as E;
/**
* ActionProvider.Getcondition API
*
* @param array $params
* @return array API result descriptor
* @see civicrm_api3_create_success
* @see civicrm_api3_create_error
* @throws API_Exception
*/
function civicrm_api3_action_provider_getcondition($params) {
$container = \Civi::service('action_provider');
if (isset($params['context'])) {
$provider = $container->getProviderByContext($params['context']);
} else {
$provider = $container->getDefaultProvider();
}
$condition = $provider->getConditionByName($params['name']);
return $condition->toArray();
}
/**
* ActionProvider.Getcondition API specification (optional)
* This is used for documentation and validation.
*
* @param array $spec description of fields supported by this API call
* @return void
* @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
*/
function _civicrm_api3_action_provider_getcondition_spec(&$spec) {
$spec['name'] = array(
'title' => E::ts('Name'),
'type' => CRM_Utils_Type::T_STRING,
'api.required' => true
);
$spec['context'] = array(
'title' => E::ts('Context'),
'type' => CRM_Utils_Type::T_STRING,
'api.required' => false
);
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment