Skip to content

Commit

Permalink
Merge pull request #10393 from craftcms/feature/dev-78-relational-fie…
Browse files Browse the repository at this point in the history
…ld-conditions

Relational field conditions
  • Loading branch information
brandonkelly authored Jan 21, 2022
2 parents 2bde47e + 8a39b01 commit 662b285
Show file tree
Hide file tree
Showing 18 changed files with 204 additions and 2 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Element indexes can now be filtered by element attributes and custom field values. ([#9192](https://github.com/craftcms/cms/discussions/9192), [#9450](https://github.com/craftcms/cms/discussions/9450), [#9462](https://github.com/craftcms/cms/discussions/9462), [#9483](https://github.com/craftcms/cms/discussions/9483))
- Admins can now create custom element sources from the Customize Sources modal. ([#8423](https://github.com/craftcms/cms/discussions/8423))
- Field layout tabs, fields, and UI elements can now be conditionally shown based on properties of the current user and/or element being edited. ([#8099](https://github.com/craftcms/cms/discussions/8099), [#8154](https://github.com/craftcms/cms/discussions/8154))
- Assets, Entries, and Users fields have new condition settings that can be used to further limit which elements should be relatable, beyond the existing field settings. ([#10393](https://github.com/craftcms/cms/pull/10393))
- Added the “Inactive” user status, which can be used by users which can’t be signed into. ([#8963](https://github.com/craftcms/cms/discussions/8963))
- Added “Credentialed” and “Inactive” user sources.
- Added the “Deactivate…” user action for pending and active users.
Expand Down Expand Up @@ -52,6 +53,7 @@
- Added `craft\behaviors\SessionBehavior::getNotice()`.
- Added `craft\controllers\AssetIndexesController`.
- Added `craft\controllers\ConditionsController`.
- Added `craft\controllers\ElementIndexesController::$condition`.
- Added `craft\controllers\FsController`.
- Added `craft\controllers\ImageTransformsController`.
- Added `craft\db\Migration::dropAllForeignKeysToTable()`.
Expand Down Expand Up @@ -127,6 +129,9 @@
- Added `craft\fields\Assets::$restrictedLocationSource`, which replaces `$singleUploadLocationSource`.
- Added `craft\fields\Assets::$restrictedLocationSubpath`, which replaces `$singleUploadLocationSubpath`.
- Added `craft\fields\Assets::$restrictLocation`, which replaces `$useSingleFolder`.
- Added `craft\fields\BaseRelationField::createSelectionCondition()`.
- Added `craft\fields\BaseRelationField::getSelectionCondition()`.
- Added `craft\fields\BaseRelationField::setSelectionCondition()`.
- Added `craft\fields\conditions\DateFieldConditionRule`.
- Added `craft\fields\conditions\FieldConditionRuleInterface`.
- Added `craft\fields\conditions\FieldConditionRuleTrait`.
Expand Down
12 changes: 12 additions & 0 deletions src/base/conditions/BaseElementSelectConditionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace craft\base\conditions;

use craft\base\ElementInterface;
use craft\elements\conditions\ElementConditionInterface;
use craft\helpers\Cp;

/**
Expand Down Expand Up @@ -46,6 +47,16 @@ protected function sources(): ?array
return null;
}

/**
* Returns the element condition that filters which elements can be selected.
*
* @return ElementConditionInterface|null
*/
protected function selectionCondition(): ?ElementConditionInterface
{
return null;
}

/**
* Returns the criteria that determines which elements can be selected.
*
Expand Down Expand Up @@ -98,6 +109,7 @@ protected function inputHtml(): string
'elementType' => $this->elementType(),
'sources' => $this->sources(),
'criteria' => $this->criteria(),
'condition' => $this->selectionCondition(),
'single' => true,
]);
}
Expand Down
35 changes: 35 additions & 0 deletions src/controllers/ElementIndexesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class ElementIndexesController extends BaseElementsController
*/
protected ?array $source = null;

/**
* @var ElementConditionInterface|null
* @since 4.0.0
*/
protected ?ElementConditionInterface $condition = null;

/**
* @var array|null
*/
Expand Down Expand Up @@ -95,6 +101,7 @@ public function beforeAction($action): bool
$this->context = $this->context();
$this->sourceKey = $this->request->getParam('source') ?: null;
$this->source = $this->source();
$this->condition = $this->condition();

if ($action->id !== 'filter-hud') {
$this->viewState = $this->viewState();
Expand Down Expand Up @@ -407,6 +414,15 @@ public function actionFilterHud(): Response
}
}

if ($this->condition) {
foreach ($this->condition->getConditionRules() as $rule) {
/** @var ElementConditionRuleInterface $rule */
foreach ($rule->getExclusiveQueryParams() as $param) {
$condition->queryParams[] = $param;
}
}
}

$condition->queryParams[] = 'site';
$condition->queryParams[] = 'status';

Expand Down Expand Up @@ -452,6 +468,20 @@ protected function source(): ?array
return $source;
}

/**
* Returns the condition that should be applied to the element query.
*
* @return ElementConditionInterface|null
* @return 4.0.0
*/
protected function condition(): ?ElementConditionInterface
{
if ($conditionConfig = $this->request->getBodyParam('condition')) {
return Craft::$app->getConditions()->createCondition($conditionConfig);
}
return null;
}

/**
* Returns the current view state.
*
Expand Down Expand Up @@ -493,6 +523,11 @@ protected function elementQuery(): ElementQueryInterface
$sourceCondition->modifyQuery($query);
}

// Was a condition provided?
if (isset($this->condition)) {
$this->condition->modifyQuery($query);
}

// Override with the request's params
if ($criteria = $this->request->getBodyParam('criteria')) {
if (isset($criteria['trashed'])) {
Expand Down
11 changes: 11 additions & 0 deletions src/fields/Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use craft\base\ElementInterface;
use craft\db\Table as DbTable;
use craft\elements\Asset;
use craft\elements\conditions\ElementCondition;
use craft\elements\db\AssetQuery;
use craft\elements\db\ElementQuery;
use craft\errors\FsObjectNotFoundException;
Expand Down Expand Up @@ -727,6 +728,16 @@ public function getInputSelectionCriteria(): array
return $criteria;
}

/**
* @inheritdoc
*/
protected function createSelectionCondition(): ?ElementCondition
{
$condition = Asset::createCondition();
$condition->queryParams = ['volume', 'volumeId', 'kind'];
return $condition;
}

/**
* Returns any files that were uploaded to the field.
*
Expand Down
87 changes: 87 additions & 0 deletions src/fields/BaseRelationField.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use Craft;
use craft\base\BlockElementInterface;
use craft\base\conditions\ConditionInterface;
use craft\base\EagerLoadingFieldInterface;
use craft\base\Element;
use craft\base\ElementInterface;
Expand All @@ -17,6 +18,7 @@
use craft\db\Query;
use craft\db\QueryAbortedException;
use craft\db\Table as DbTable;
use craft\elements\conditions\ElementConditionInterface;
use craft\elements\db\ElementQuery;
use craft\elements\db\ElementQueryInterface;
use craft\elements\db\ElementRelationParamParser;
Expand Down Expand Up @@ -199,6 +201,13 @@ public static function valueType(): string
*/
protected bool $sortable = true;

/**
* @var ElementConditionInterface|array|null
* @see getSelectionCondition()
* @see setSelectionCondition()
*/
private $_selectionCondition = null;

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -270,6 +279,20 @@ public function settingsAttributes(): array
return $attributes;
}

/**
* @inheritdoc
*/
public function getSettings(): array
{
$settings = parent::getSettings();

if ($selectionCondition = $this->getSelectionCondition()) {
$settings['selectionCondition'] = $selectionCondition->getConfig();
}

return $settings;
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -906,10 +929,28 @@ protected function settingsTemplateVariables(): array
/** @var ElementInterface|string $elementType */
$elementType = $this->elementType();

$selectionCondition = $this->getSelectionCondition() ?? $this->createSelectionCondition();
if ($selectionCondition) {
$selectionCondition->mainTag = 'div';
$selectionCondition->id = 'selection-condition';
$selectionCondition->name = 'selectionCondition';
$selectionCondition->forProjectConfig = true;

$selectionConditionHtml = Cp::fieldHtml($selectionCondition->getBuilderHtml(), [
'label' => Craft::t('app', 'Selectable {type} Condition', [
'type' => $elementType::pluralDisplayName(),
]),
'instructions' => Craft::t('app', 'Only allow {type} to be selected if they match following rules:', [
'type' => $elementType::pluralLowerDisplayName(),
]),
]);
}

return [
'field' => $this,
'elementType' => $elementType::lowerDisplayName(),
'pluralElementType' => $elementType::pluralLowerDisplayName(),
'selectionCondition' => $selectionConditionHtml ?? null,
];
}

Expand Down Expand Up @@ -970,6 +1011,7 @@ protected function inputTemplateVariables($value = null, ?ElementInterface $elem
'name' => $this->handle,
'elements' => $value,
'sources' => $this->getInputSources($element),
'condition' => $this->getSelectionCondition(),
'criteria' => $selectionCriteria,
'showSiteMenu' => ($this->targetSiteId || !$this->showSiteMenu) ? false : 'auto',
'allowSelfRelations' => $this->allowSelfRelations,
Expand Down Expand Up @@ -1016,6 +1058,51 @@ public function getInputSelectionCriteria(): array
return $event->criteria;
}

/**
* Returns the element condition that should be used to determine which elements are selectable by the field.
*
* @return ElementConditionInterface|null
* @since 4.0.0
*/
public function getSelectionCondition(): ?ElementConditionInterface
{
if ($this->_selectionCondition !== null && !$this->_selectionCondition instanceof ConditionInterface) {
$this->_selectionCondition = Craft::$app->getConditions()->createCondition($this->_selectionCondition);
}

return $this->_selectionCondition;
}

/**
* Sets the element condition that should be used to determine which elements are selectable by the field.
*
* @param ElementConditionInterface|string|array{class: string}|null $condition
* @since 4.0.0
*/
public function setSelectionCondition($condition): void
{
if ($condition instanceof ConditionInterface && !$condition->getConditionRules()) {
$condition = null;
}

// Don't instantiate it unless we actually end up needing it.
// Avoids an infinite recursion bug (ElementCondition::conditionRuleTypes() => getAllFields() => setSelectionCondition() => ...)
$this->_selectionCondition = $condition;
}

/**
* Creates an element condition that should be used to determine which elements are selectable by the field.
*
* The condition’s `queryParams` property should be set to any element query params that are already covered by other field settings.
*
* @return ElementConditionInterface|null
* @since 4.0.0
*/
protected function createSelectionCondition(): ?ElementConditionInterface
{
return null;
}

/**
* Returns the site ID that target elements should have.
*
Expand Down
11 changes: 11 additions & 0 deletions src/fields/Entries.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use Craft;
use craft\db\Table as DbTable;
use craft\elements\conditions\ElementCondition;
use craft\elements\db\EntryQuery;
use craft\elements\Entry;
use craft\gql\arguments\elements\Entry as EntryArguments;
Expand Down Expand Up @@ -106,4 +107,14 @@ public function getEagerLoadingGqlConditions(): ?array
'sectionId' => array_values($sectionIds),
];
}

/**
* @inheritdoc
*/
protected function createSelectionCondition(): ?ElementCondition
{
$condition = Entry::createCondition();
$condition->queryParams = ['section', 'sectionId'];
return $condition;
}
}
11 changes: 11 additions & 0 deletions src/fields/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use Craft;
use craft\db\Table as DbTable;
use craft\elements\conditions\ElementCondition;
use craft\elements\db\UserQuery;
use craft\elements\User;
use craft\gql\arguments\elements\User as UserArguments;
Expand Down Expand Up @@ -105,4 +106,14 @@ public function getEagerLoadingGqlConditions(): ?array

return ['groupId' => array_values($groupIds)];
}

/**
* @inheritdoc
*/
protected function createSelectionCondition(): ?ElementCondition
{
$condition = User::createCondition();
$condition->queryParams = ['group', 'groupId'];
return $condition;
}
}
11 changes: 11 additions & 0 deletions src/fields/conditions/RelationalFieldConditionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Craft;
use craft\base\conditions\BaseElementSelectConditionRule;
use craft\base\ElementInterface;
use craft\elements\conditions\ElementConditionInterface;
use craft\elements\db\ElementQueryInterface;
use craft\fields\BaseRelationField;
use Illuminate\Support\Collection;
Expand Down Expand Up @@ -60,6 +61,16 @@ protected function sources(): ?array
return (array)$field->getInputSources();
}

/**
* @inheritdoc
*/
protected function selectionCondition(): ?ElementConditionInterface
{
/** @var BaseRelationField $field */
$field = $this->field();
return $field->getSelectionCondition();
}

/**
* @inheritdoc
*/
Expand Down
2 changes: 2 additions & 0 deletions src/templates/_components/fieldtypes/Assets/input.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
{% set elements = elements ?? [] -%}
{% set jsClass = jsClass ?? 'Craft.BaseElementSelectInput' -%}
{% set sources = sources ?? null -%}
{% set condition = condition ?? null -%}
{% set criteria = criteria ?? null -%}
{% set storageKey = storageKey ?? null -%}

Expand Down Expand Up @@ -40,6 +41,7 @@
name: name|namespaceInputName,
elementType: elementType,
sources: sources,
condition: condition ? condition.getConfig() : null,
criteria: criteria,
sourceElementId: sourceElementId,
viewMode: viewMode,
Expand Down
2 changes: 2 additions & 0 deletions src/templates/_components/fieldtypes/Assets/settings.twig
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@

<hr>

{{ block('selectionConditionField') }}

{{ forms.checkboxField({
label: 'Show unpermitted volumes'|t('app'),
instructions: 'Whether to show volumes that the user doesn’t have permission to view.'|t('app'),
Expand Down
Loading

0 comments on commit 662b285

Please sign in to comment.