2015-07-24 04:37:48 +02:00
|
|
|
<?php
|
|
|
|
|
2017-08-09 01:55:09 +02:00
|
|
|
namespace SilverStripe\UserForms\Model;
|
|
|
|
|
2020-01-13 00:15:34 +01:00
|
|
|
use InvalidArgumentException;
|
2017-08-09 01:55:09 +02:00
|
|
|
use LogicException;
|
|
|
|
use SilverStripe\CMS\Controllers\CMSMain;
|
2017-08-11 01:33:06 +02:00
|
|
|
use SilverStripe\Control\Controller;
|
2017-08-09 01:55:09 +02:00
|
|
|
use SilverStripe\Core\Convert;
|
|
|
|
use SilverStripe\ORM\DataObject;
|
2019-03-25 03:09:38 +01:00
|
|
|
use SilverStripe\Security\Member;
|
|
|
|
use SilverStripe\Versioned\Versioned;
|
2017-08-09 01:55:09 +02:00
|
|
|
|
2015-07-24 04:37:48 +02:00
|
|
|
/**
|
|
|
|
* A custom rule for showing / hiding an EditableFormField
|
|
|
|
* based the value of another EditableFormField.
|
|
|
|
*
|
2015-12-22 23:14:36 +01:00
|
|
|
* @method EditableFormField Parent()
|
2019-03-25 03:09:38 +01:00
|
|
|
* @method EditableFormField ConditionField()
|
2017-04-28 00:22:15 +02:00
|
|
|
*
|
|
|
|
* @property string Display
|
|
|
|
* @property string ConditionOption
|
|
|
|
* @property string FieldValue
|
2015-07-24 04:37:48 +02:00
|
|
|
*/
|
2016-07-21 07:53:59 +02:00
|
|
|
class EditableCustomRule extends DataObject
|
|
|
|
{
|
2017-08-11 01:33:06 +02:00
|
|
|
private static $condition_options = [
|
2020-01-14 00:28:04 +01:00
|
|
|
'IsBlank' => 'Is blank',
|
|
|
|
'IsNotBlank' => 'Is not blank',
|
|
|
|
'HasValue' => 'Equals',
|
|
|
|
'ValueNot' => 'Doesn\'t equal',
|
|
|
|
'ValueLessThan' => 'Less than',
|
|
|
|
'ValueLessThanEqual' => 'Less than or equal',
|
|
|
|
'ValueGreaterThan' => 'Greater than',
|
2017-08-11 01:33:06 +02:00
|
|
|
'ValueGreaterThanEqual' => 'Greater than or equal'
|
|
|
|
];
|
|
|
|
|
|
|
|
private static $db = [
|
2020-01-14 00:28:04 +01:00
|
|
|
'Display' => 'Enum("Show,Hide")',
|
2016-07-21 07:53:59 +02:00
|
|
|
'ConditionOption' => 'Enum("IsBlank,IsNotBlank,HasValue,ValueNot,ValueLessThan,ValueLessThanEqual,ValueGreaterThan,ValueGreaterThanEqual")',
|
2020-01-14 00:28:04 +01:00
|
|
|
'FieldValue' => 'Varchar(255)'
|
2017-08-11 01:33:06 +02:00
|
|
|
];
|
2016-07-21 07:53:59 +02:00
|
|
|
|
2017-08-11 01:33:06 +02:00
|
|
|
private static $has_one = [
|
2020-01-14 00:28:04 +01:00
|
|
|
'Parent' => EditableFormField::class,
|
2017-08-09 01:55:09 +02:00
|
|
|
'ConditionField' => EditableFormField::class
|
2017-08-11 01:33:06 +02:00
|
|
|
];
|
2016-07-21 07:53:59 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Built in extensions required
|
|
|
|
*
|
|
|
|
* @config
|
|
|
|
* @var array
|
|
|
|
*/
|
2017-08-11 01:33:06 +02:00
|
|
|
private static $extensions = [
|
|
|
|
Versioned::class . "('Stage', 'Live')"
|
|
|
|
];
|
|
|
|
|
|
|
|
private static $table_name = 'EditableCustomRule';
|
2016-07-21 07:53:59 +02:00
|
|
|
|
2015-12-22 23:14:36 +01:00
|
|
|
/**
|
|
|
|
* @param Member $member
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-07-21 07:53:59 +02:00
|
|
|
public function canDelete($member = null)
|
|
|
|
{
|
|
|
|
return $this->canEdit($member);
|
|
|
|
}
|
2015-12-22 23:14:36 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Member $member
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-07-21 07:53:59 +02:00
|
|
|
public function canEdit($member = null)
|
|
|
|
{
|
2015-12-22 23:14:36 +01:00
|
|
|
return $this->Parent()->canEdit($member);
|
2016-07-21 07:53:59 +02:00
|
|
|
}
|
2015-12-22 23:14:36 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Member $member
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-07-21 07:53:59 +02:00
|
|
|
public function canView($member = null)
|
|
|
|
{
|
|
|
|
return $this->Parent()->canView($member);
|
|
|
|
}
|
2015-12-22 23:14:36 +01:00
|
|
|
|
2016-07-21 07:53:59 +02:00
|
|
|
/**
|
|
|
|
* Return whether a user can create an object of this type
|
|
|
|
*
|
2015-12-22 23:14:36 +01:00
|
|
|
* @param Member $member
|
2018-05-08 07:02:01 +02:00
|
|
|
* @param array $context Virtual parameter to allow context to be passed in to check
|
2016-07-21 07:53:59 +02:00
|
|
|
* @return bool
|
|
|
|
*/
|
2017-08-11 02:36:28 +02:00
|
|
|
public function canCreate($member = null, $context = [])
|
2016-07-21 07:53:59 +02:00
|
|
|
{
|
|
|
|
// Check parent page
|
2015-12-22 23:14:36 +01:00
|
|
|
$parent = $this->getCanCreateContext(func_get_args());
|
2016-07-21 07:53:59 +02:00
|
|
|
if ($parent) {
|
2015-12-22 23:14:36 +01:00
|
|
|
return $parent->canEdit($member);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fall back to secure admin permissions
|
|
|
|
return parent::canCreate($member);
|
2016-07-21 07:53:59 +02:00
|
|
|
}
|
2015-12-22 23:14:36 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method to check the parent for this object
|
|
|
|
*
|
|
|
|
* @param array $args List of arguments passed to canCreate
|
|
|
|
* @return DataObject Some parent dataobject to inherit permissions from
|
|
|
|
*/
|
2016-07-21 07:53:59 +02:00
|
|
|
protected function getCanCreateContext($args)
|
|
|
|
{
|
2015-12-22 23:14:36 +01:00
|
|
|
// Inspect second parameter to canCreate for a 'Parent' context
|
2016-07-21 07:53:59 +02:00
|
|
|
if (isset($args[1]['Parent'])) {
|
2015-12-22 23:14:36 +01:00
|
|
|
return $args[1]['Parent'];
|
|
|
|
}
|
|
|
|
// Hack in currently edited page if context is missing
|
2016-07-21 07:53:59 +02:00
|
|
|
if (Controller::has_curr() && Controller::curr() instanceof CMSMain) {
|
2015-12-22 23:14:36 +01:00
|
|
|
return Controller::curr()->currentPage();
|
|
|
|
}
|
|
|
|
|
|
|
|
// No page being edited
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Member $member
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-07-21 07:53:59 +02:00
|
|
|
public function canPublish($member = null)
|
|
|
|
{
|
2015-12-22 23:14:36 +01:00
|
|
|
return $this->canEdit($member);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Member $member
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-07-21 07:53:59 +02:00
|
|
|
public function canUnpublish($member = null)
|
|
|
|
{
|
2015-12-22 23:14:36 +01:00
|
|
|
return $this->canDelete($member);
|
|
|
|
}
|
2017-04-28 00:22:15 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Substitutes configured rule logic with it's JS equivalents and returns them as array elements
|
2017-08-14 02:29:57 +02:00
|
|
|
*
|
2017-04-28 00:22:15 +02:00
|
|
|
* @return array
|
2017-08-14 02:29:57 +02:00
|
|
|
* @throws LogicException If the provided condition option was not able to be handled
|
2017-04-28 00:22:15 +02:00
|
|
|
*/
|
|
|
|
public function buildExpression()
|
|
|
|
{
|
|
|
|
/** @var EditableFormField $formFieldWatch */
|
|
|
|
$formFieldWatch = $this->ConditionField();
|
|
|
|
//Encapsulated the action to the object
|
|
|
|
$action = $formFieldWatch->getJsEventHandler();
|
|
|
|
|
|
|
|
// is this field a special option field
|
|
|
|
$checkboxField = $formFieldWatch->isCheckBoxField();
|
|
|
|
$radioField = $formFieldWatch->isRadioField();
|
|
|
|
$target = sprintf('$("%s")', $formFieldWatch->getSelectorFieldOnly());
|
|
|
|
$fieldValue = Convert::raw2js($this->FieldValue);
|
|
|
|
|
2017-08-11 01:33:06 +02:00
|
|
|
$conditionOptions = [
|
2017-04-28 00:22:15 +02:00
|
|
|
'ValueLessThan' => '<',
|
|
|
|
'ValueLessThanEqual' => '<=',
|
|
|
|
'ValueGreaterThan' => '>',
|
|
|
|
'ValueGreaterThanEqual' => '>='
|
2017-08-11 01:33:06 +02:00
|
|
|
];
|
|
|
|
|
2017-04-28 00:22:15 +02:00
|
|
|
// and what should we evaluate
|
|
|
|
switch ($this->ConditionOption) {
|
|
|
|
case 'IsNotBlank':
|
|
|
|
case 'IsBlank':
|
|
|
|
$expression = ($checkboxField || $radioField) ? "!{$target}.is(\":checked\")" : "{$target}.val() == ''";
|
2019-03-25 03:09:38 +01:00
|
|
|
if ((string) $this->ConditionOption === 'IsNotBlank') {
|
2017-04-28 00:22:15 +02:00
|
|
|
//Negate
|
|
|
|
$expression = "!({$expression})";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'HasValue':
|
|
|
|
case 'ValueNot':
|
|
|
|
if ($checkboxField) {
|
|
|
|
if ($formFieldWatch->isCheckBoxGroupField()) {
|
2017-05-22 05:54:28 +02:00
|
|
|
$expression = sprintf(
|
|
|
|
"$.inArray('%s', %s.filter(':checked').map(function(){ return $(this).val();}).get()) > -1",
|
|
|
|
$fieldValue,
|
|
|
|
$target
|
|
|
|
);
|
2017-04-28 00:22:15 +02:00
|
|
|
} else {
|
|
|
|
$expression = "{$target}.prop('checked')";
|
|
|
|
}
|
|
|
|
} elseif ($radioField) {
|
|
|
|
// We cannot simply get the value of the radio group, we need to find the checked option first.
|
2017-05-22 05:54:28 +02:00
|
|
|
$expression = sprintf(
|
|
|
|
'%s.closest(".field, .control-group").find("input:checked").val() == "%s"',
|
|
|
|
$target,
|
|
|
|
$fieldValue
|
|
|
|
);
|
2017-04-28 00:22:15 +02:00
|
|
|
} else {
|
|
|
|
$expression = sprintf('%s.val() == "%s"', $target, $fieldValue);
|
|
|
|
}
|
|
|
|
|
2019-03-25 03:09:38 +01:00
|
|
|
if ((string) $this->ConditionOption === 'ValueNot') {
|
2017-04-28 00:22:15 +02:00
|
|
|
//Negate
|
|
|
|
$expression = "!({$expression})";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'ValueLessThan':
|
|
|
|
case 'ValueLessThanEqual':
|
|
|
|
case 'ValueGreaterThan':
|
|
|
|
case 'ValueGreaterThanEqual':
|
2017-05-22 05:54:28 +02:00
|
|
|
$expression = sprintf(
|
|
|
|
'%s.val() %s parseFloat("%s")',
|
|
|
|
$target,
|
|
|
|
$conditionOptions[$this->ConditionOption],
|
|
|
|
$fieldValue
|
|
|
|
);
|
2017-04-28 00:22:15 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new LogicException("Unhandled rule {$this->ConditionOption}");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-08-11 01:33:06 +02:00
|
|
|
$result = [
|
2017-04-28 00:22:15 +02:00
|
|
|
'operation' => $expression,
|
|
|
|
'event' => $action,
|
2017-08-11 01:33:06 +02:00
|
|
|
];
|
2017-04-28 00:22:15 +02:00
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2018-05-08 07:02:01 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines whether the rule is satisfied, based on provided form data.
|
|
|
|
* Used for php validation of required conditional fields
|
|
|
|
*
|
|
|
|
* @param array $data Submitted form data
|
|
|
|
* @return boolean
|
2020-01-13 00:15:34 +01:00
|
|
|
* @throws LogicException Invalid ConditionOption is set for this rule.
|
2018-05-08 07:02:01 +02:00
|
|
|
*/
|
2020-01-14 00:28:04 +01:00
|
|
|
public function validateAgainstFormData(array $data)
|
2018-05-08 07:02:01 +02:00
|
|
|
{
|
|
|
|
|
|
|
|
$controllingField = $this->ConditionField();
|
|
|
|
|
|
|
|
if (!isset($data[$controllingField->Name])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$valid = false;
|
|
|
|
|
|
|
|
$targetFieldValue = $this->FieldValue;
|
|
|
|
$actualFieldValue = $data[$controllingField->Name];
|
|
|
|
|
|
|
|
switch ($this->ConditionOption) {
|
|
|
|
case 'IsNotBlank':
|
|
|
|
$valid = ($actualFieldValue !== '');
|
|
|
|
break;
|
|
|
|
case 'IsBlank':
|
|
|
|
$valid = ($actualFieldValue === '');
|
|
|
|
break;
|
|
|
|
case 'HasValue':
|
|
|
|
$valid = ($actualFieldValue === $targetFieldValue);
|
|
|
|
break;
|
|
|
|
case 'ValueNot':
|
|
|
|
$valid = ($actualFieldValue !== $targetFieldValue);
|
|
|
|
break;
|
|
|
|
case 'ValueLessThan':
|
|
|
|
$valid = ($actualFieldValue < $targetFieldValue);
|
|
|
|
break;
|
|
|
|
case 'ValueLessThanEqual':
|
|
|
|
$valid = ($actualFieldValue <= $targetFieldValue);
|
|
|
|
break;
|
|
|
|
case 'ValueGreaterThan':
|
|
|
|
$valid = ($actualFieldValue > $targetFieldValue);
|
|
|
|
break;
|
|
|
|
case 'ValueGreaterThanEqual':
|
|
|
|
$valid = ($actualFieldValue >= $targetFieldValue);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new LogicException("Unhandled rule {$this->ConditionOption}");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-04-28 00:22:15 +02:00
|
|
|
/**
|
2017-05-22 05:54:28 +02:00
|
|
|
* Returns the opposite visibility function for the value of the initial visibility field, e.g. show/hide. This
|
|
|
|
* will toggle the "hide" class either way, which is handled by CSS.
|
2017-04-28 00:22:15 +02:00
|
|
|
*
|
2019-03-25 03:09:38 +01:00
|
|
|
* @param string $initialState
|
|
|
|
* @param boolean $invert
|
2017-04-28 00:22:15 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
2019-03-25 03:09:38 +01:00
|
|
|
public function toggleDisplayText($initialState, $invert = false)
|
2017-04-28 00:22:15 +02:00
|
|
|
{
|
2019-03-25 03:09:38 +01:00
|
|
|
$action = strtolower($initialState) === 'hide' ? 'removeClass' : 'addClass';
|
|
|
|
if ($invert) {
|
|
|
|
$action = $action === 'removeClass' ? 'addClass' : 'removeClass';
|
|
|
|
}
|
|
|
|
return sprintf('%s("hide")', $action);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an event name to be dispatched when the field is changed. Matches up with the visibility classes
|
|
|
|
* added or removed in `toggleDisplayText()`.
|
|
|
|
*
|
|
|
|
* @param string $initialState
|
|
|
|
* @param bool $invert
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function toggleDisplayEvent($initialState, $invert = false)
|
|
|
|
{
|
|
|
|
$action = strtolower($initialState) === 'hide' ? 'show' : 'hide';
|
|
|
|
if ($invert) {
|
|
|
|
$action = $action === 'hide' ? 'show' : 'hide';
|
|
|
|
}
|
|
|
|
return sprintf('userform.field.%s', $action);
|
2017-04-28 00:22:15 +02:00
|
|
|
}
|
2017-05-22 05:54:28 +02:00
|
|
|
}
|