Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/entries with category features #11749

Merged
merged 20 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
468486a
Initial work
timkelty Feb 22, 2022
e17daa4
merge in tims initial work
myleshyson Aug 6, 2022
75bb0ca
begin merging in category markup into element selector field.
myleshyson Aug 6, 2022
48868ae
move CategoryInput logic to BaseElementInput. Move CategoriesControll…
myleshyson Aug 7, 2022
75c43f9
move branch limit next to relateAncestors field in settings
myleshyson Aug 7, 2022
3c0f8d0
a little cleanup. give branch limit a default value. remove entry lan…
myleshyson Aug 7, 2022
a0005bf
Merge branch '4.3' of https://github.com/myleshyson/craft into featur…
myleshyson Aug 15, 2022
28aa6be
merge in 4.3. re-run Unknown command: "build"
myleshyson Aug 15, 2022
e7d7be6
fix conflict. also remove inputs from form data for structured elemen…
myleshyson Aug 23, 2022
cc7cffa
Add Craft.ElementFieldSettings module to handle view logic for elemen…
myleshyson Aug 30, 2022
1fe86b9
merge in 4.3 and fix conflict
myleshyson Aug 30, 2022
1a524c0
allow categories to act like entries
myleshyson Aug 30, 2022
1cc6fa1
address formatting errors. disable instructions when disabling relate…
myleshyson Sep 12, 2022
d4bcd3c
add sources validator for when relateAncestors is checked.
myleshyson Sep 12, 2022
c9f4c65
fix indentation
myleshyson Sep 12, 2022
d17cc6f
force relation save when field relates ancestors
myleshyson Sep 12, 2022
1013dbc
sync with 4.3
myleshyson Sep 12, 2022
8228d6b
updated disabled state for relateAncestors instructions
myleshyson Sep 12, 2022
cb550ec
use translation function for model errors. rename structured input ac…
myleshyson Sep 13, 2022
6da08a8
update source validation message
myleshyson Sep 13, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions src/controllers/ElementsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,57 @@ public function actionGetElementHtml(): Response
return $this->asJson(compact('html', 'headHtml'));
}

/**
* Returns HTML for a structured elements field input based on a given list
* of selected element ids.
*
* @return Response
* @throws BadRequestHttpException
* @throws ForbiddenHttpException
* @since 4.0.0
*/
public function actionStructuredInputHtml(): Response
{
timkelty marked this conversation as resolved.
Show resolved Hide resolved
$this->requireAcceptsJson();

$elementType = $this->request->getRequiredParam('elementType');
$elementIds = $this->request->getParam('elementIds', []);

$elements = [];

if (!empty($elementIds)) {
/** @var ElementInterface[] $elements */
$elements = $elementType::find()
->id($elementIds)
->siteId($this->request->getParam('siteId'))
->status(null)
->all();

// Fill in the gaps
$structuresService = Craft::$app->getStructures();
$structuresService->fillGapsInElements($elements);

// Enforce the branch limit
if ($branchLimit = $this->request->getParam('branchLimit')) {
$structuresService->applyBranchLimitToElements($elements, $branchLimit);
}
}

$html = $this->getView()->renderTemplate('_includes/forms/elementselect',
[
'elements' => $elements,
'id' => $this->request->getParam('containerId'),
'name' => $this->request->getParam('name'),
'selectionLabel' => $this->request->getParam('selectionLabel'),
'elementType' => $elementType,
'relateAncestors' => true,
]);

return $this->asJson([
'html' => $html,
]);
}

/**
* Returns the requested element, populated with any posted attributes.
*
Expand Down
145 changes: 140 additions & 5 deletions src/fields/BaseRelationField.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use craft\queue\jobs\LocalizeRelations;
use craft\services\Elements;
use craft\services\ElementSources;
use craft\web\assets\elementfieldsettings\ElementFieldSettingsAsset;
use DateTime;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Collection;
Expand Down Expand Up @@ -135,6 +136,20 @@ public static function valueType(): string
*/
public bool $showSiteMenu = false;

/**
* @var bool Whether to automatically relate structural ancestors.
*
* @since 4.0.0
*/
public bool $relateAncestors = false;

/**
* @var int|null Branch limit
*
* @since 4.0.0
*/
public ?int $branchLimit = null;

/**
* @var string|null The view mode
*/
Expand Down Expand Up @@ -249,6 +264,14 @@ public function __construct(array $config = [])
$config['showSiteMenu'] = true;
}

// if relating ancestors, then clear min/max limits, otherwise clear branch limit
if (isset($config['relateAncestors']) && !empty($config['relateAncestors'])) {
$config['maxRelations'] = null;
$config['minRelations'] = null;
} else {
$config['branchLimit'] = null;
}

parent::__construct($config);
}

Expand All @@ -259,10 +282,57 @@ public function __construct(array $config = [])
protected function defineRules(): array
{
$rules = parent::defineRules();
$rules[] = [['minRelations', 'maxRelations'], 'number', 'integerOnly' => true];
$rules[] = [['minRelations', 'maxRelations', 'branchLimit'], 'number', 'integerOnly' => true];
$rules[] = [['source', 'sources'], 'validateSources'];
return $rules;
}

/**
* Ensure only one structured source is selected when relateAncestors is true.
*
* @param string $attribute
*/
public function validateSources($attribute): void
{
if (!$this->relateAncestors) {
return;
}

$inputSources = $this->getInputSources();

if ($inputSources === null) {
$this->addError($attribute, Craft::t('app', 'One source needs to be selected when relating ancestors.'));
return;
}

if (is_string($inputSources)) {
$inputSources = [$inputSources];
}

$elementSources = ArrayHelper::whereIn(
Craft::$app->elementSources->getSources(static::elementType()),
'key',
$inputSources
);

if (count($elementSources) > 1) {
$this->addError($attribute, Craft::t('app', 'Only one source allowed when relating ancestors.'));
}

foreach ($elementSources as $elementSource) {
if (!isset($elementSource['structureId'])) {
$this->addError(
$attribute,
Craft::t(
'app',
'{source} is not a structured source. Only structured sources may be used when relating ancestors.',
['source' => $elementSource['label']]
)
);
}
}
}

/**
* @inheritdoc
*/
Expand All @@ -280,6 +350,9 @@ public function settingsAttributes(): array
$attributes[] = 'targetSiteId';
$attributes[] = 'validateRelatedElements';
$attributes[] = 'viewMode';
$attributes[] = 'allowSelfRelations';
$attributes[] = 'relateAncestors';
$attributes[] = 'branchLimit';

return $attributes;
}
Expand All @@ -304,7 +377,18 @@ public function getSettings(): array
public function getSettingsHtml(): ?string
{
$variables = $this->settingsTemplateVariables();
return Craft::$app->getView()->renderTemplate($this->settingsTemplate, $variables);
$view = Craft::$app->getView();

$view->registerAssetBundle(ElementFieldSettingsAsset::class);
$view->registerJs("new Craft.ElementFieldSettings(
'{$view->namespaceInputId('relate-ancestors')}',
'{$view->namespaceInputId('sources-field')}',
'{$view->namespaceInputId('branch-limit')}',
'{$view->namespaceInputId('min-relations')}',
'{$view->namespaceInputId('max-relations')}',
)");

return $view->renderTemplate($this->settingsTemplate, $variables);
}

/**
Expand Down Expand Up @@ -361,7 +445,7 @@ public function validateRelationCount(ElementInterface $element): void
*/
public function validateRelatedElements(ElementInterface $element): void
{
// Prevent circular relations from worrying about this entry
// Prevent circular relations from worrying about this element
$sourceId = $element->getCanonicalId();
$sourceValidates = self::$_relatedElementValidates[$sourceId][$element->siteId] ?? null;
self::$_relatedElementValidates[$sourceId][$element->siteId] = true;
Expand Down Expand Up @@ -489,6 +573,25 @@ public function normalizeValue(mixed $value, ?ElementInterface $element = null):
Craft::configure($query, $source['criteria']);
}
}

if ($this->relateAncestors) {
$structuresService = Craft::$app->getStructures();

/** @var ElementInterface[] $structureElements */
$structureElements = (clone($query))
->status(null)
->all();

// Fill in any gaps
$structuresService->fillGapsInElements($structureElements);

// Enforce the branch limit
if ($this->branchLimit) {
$structuresService->applyBranchLimitToElements($structureElements, $this->branchLimit);
}

$query->id(ArrayHelper::getColumn($structureElements, 'id'));
}
} else {
$query->id(false);
}
Expand Down Expand Up @@ -757,6 +860,10 @@ public function getEagerLoadingMap(array $sourceElements): array|null|false
}
}

if ($this->relateAncestors) {
$criteria['orderBy'] = ['structureelements.lft' => SORT_ASC];
}

return [
'elementType' => static::elementType(),
'map' => $map,
Expand Down Expand Up @@ -805,7 +912,7 @@ public function afterElementSave(ElementInterface $element, bool $isNew): void
{
// Skip if nothing changed, or the element is just propagating and we're not localizing relations
if (
$element->isFieldDirty($this->handle) &&
($element->isFieldDirty($this->handle) || $this->relateAncestors) &&
(!$element->propagating || $this->localizeRelations)
) {
/** @var ElementQueryInterface|Collection $value */
Expand All @@ -823,6 +930,32 @@ public function afterElementSave(ElementInterface $element, bool $isNew): void
$targetIds = $this->_all($value, $element)->ids();
}

if ($this->relateAncestors) {
$structuresService = Craft::$app->getStructures();

/** @var ElementInterface $class */
$class = static::elementType();

/** @var ElementInterface[] $structureElements */
$structureElements = $class::find()
->id($targetIds)
->drafts(null)
->revisions(null)
->provisionalDrafts(null)
->status(null)
->all();

// Fill in any gaps
$structuresService->fillGapsInElements($structureElements);

// Enforce the branch limit
if ($this->branchLimit) {
$structuresService->applyBranchLimitToElements($structureElements, $this->branchLimit);
}

$targetIds = ArrayHelper::getColumn($structureElements, 'id');
}

/** @var int|int[]|false|null $targetIds */
Craft::$app->getRelations()->saveRelations($this, $element, $targetIds);

Expand Down Expand Up @@ -1065,7 +1198,9 @@ protected function inputTemplateVariables(array|ElementQueryInterface $value = n
'condition' => $this->getSelectionCondition(),
'criteria' => $selectionCriteria,
'showSiteMenu' => ($this->targetSiteId || !$this->showSiteMenu) ? false : 'auto',
'allowSelfRelations' => $this->allowSelfRelations,
'allowSelfRelations' => (bool)$this->allowSelfRelations,
'relateAncestors' => (bool)$this->relateAncestors,
'branchLimit' => $this->branchLimit,
'sourceElementId' => !empty($element->id) ? $element->id : null,
'disabledElementIds' => $disabledElementIds,
'limit' => $this->allowLimit ? $this->maxRelations : null,
Expand Down
36 changes: 10 additions & 26 deletions src/fields/Categories.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
use craft\base\ElementInterface;
use craft\elements\Category;
use craft\elements\db\CategoryQuery;
use craft\elements\db\ElementQueryInterface;
use craft\gql\arguments\elements\Category as CategoryArguments;
use craft\gql\interfaces\elements\Category as CategoryInterface;
use craft\gql\resolvers\elements\Category as CategoryResolver;
Expand Down Expand Up @@ -64,40 +63,36 @@ public static function valueType(): string
return CategoryQuery::class;
}

/**
* @inheritdoc
*/
public bool $allowLimit = false;

/**
* @inheritdoc
*/
public bool $allowMultipleSources = false;

/**
* @var int|null Branch limit
*/
public ?int $branchLimit = null;

/**
* @inheritdoc
*/
protected string $settingsTemplate = '_components/fieldtypes/Categories/settings.twig';
public bool $relateAncestors = true;

/**
* @inheritdoc
*/
protected string $inputTemplate = '_components/fieldtypes/Categories/input.twig';
protected string $settingsTemplate = '_components/fieldtypes/elementfieldsettings';

/**
* @inheritdoc
*/
protected ?string $inputJsClass = 'Craft.CategorySelectInput';
protected bool $sortable = false;

/**
* @inheritdoc
*/
protected bool $sortable = false;
public function __construct(array $config = [])
{
// allow categories to limit selection if `relateAncestors` isn't checked
$config['allowLimit'] = true;

parent::__construct($config);
}
myleshyson marked this conversation as resolved.
Show resolved Hide resolved

/**
* @inheritdoc
Expand Down Expand Up @@ -144,17 +139,6 @@ protected function inputHtml(mixed $value, ?ElementInterface $element = null): s
return parent::inputHtml($value, $element);
}

/**
* @inheritdoc
*/
protected function inputTemplateVariables(array|ElementQueryInterface $value = null, ?ElementInterface $element = null): array
{
$variables = parent::inputTemplateVariables($value, $element);
$variables['branchLimit'] = $this->branchLimit;

return $variables;
}

public function getEagerLoadingMap(array $sourceElements): array|null|false
{
$map = parent::getEagerLoadingMap($sourceElements);
Expand Down
Loading