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

add support for custom sources #1504

Merged
merged 4 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 33 additions & 4 deletions src/fields/Categories.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
use Craft;
use craft\base\Element as BaseElement;
use craft\elements\Category as CategoryElement;
use craft\elements\conditions\ElementConditionInterface;
use craft\feedme\base\Field;
use craft\feedme\base\FieldInterface;
use craft\feedme\helpers\DataHelper;
use craft\feedme\Plugin;
use craft\fields\Categories as CategoriesField;
use craft\helpers\Db;
use craft\helpers\ElementHelper;
use craft\helpers\Json;
use craft\services\ElementSources;

/**
*
Expand Down Expand Up @@ -90,9 +93,19 @@ public function parseField(): mixed
$node = Hash::get($this->fieldInfo, 'node');
$nodeKey = null;

$groupId = null;
$customSource = null;
// Get source id's for connecting
[, $groupUid] = explode(':', $source);
$groupId = Db::idByUid('{{%categorygroups}}', $groupUid);
if (str_starts_with($source, 'custom:')) {
$customSource = ElementHelper::findSource(CategoryElement::class, $source, ElementSources::CONTEXT_FIELD);
// make sure $create is nullified; we don't want to create categories for custom sources
// because of ensuring all the conditions are met
// for example, if there's condition level == 2, then how do we ensure that and (more importantly) how do we choose a parent
$create = null;
} else {
[, $groupUid] = explode(':', $source);
$groupId = Db::idByUid('{{%categorygroups}}', $groupUid);
}

$foundElements = [];

Expand Down Expand Up @@ -140,13 +153,29 @@ public function parseField(): mixed
}

$criteria['status'] = null;
$criteria['groupId'] = $groupId;
$criteria['limit'] = $limit;
$criteria['where'] = ['=', $columnName, $dataValue];

Craft::configure($query, $criteria);

Plugin::info('Search for existing category with query `{i}`', ['i' => Json::encode($criteria)]);
if (!empty($customSource)) {
$conditionsService = Craft::$app->getConditions();
/** @var ElementConditionInterface $sourceCondition */
$sourceCondition = $conditionsService->createCondition($customSource['condition']);
$sourceCondition->modifyQuery($query);
}

// we're getting the criteria from conditions now too, so they are not included in the $criteria array;
// so, we get all the query criteria, filter out any empty or boolean ones and only show the ones that look to be filled out
$showCriteria = $criteria;
$allCriteria = $query->getCriteria();
foreach ($allCriteria as $key => $criterion) {
if (!empty($criterion) && !is_bool($criterion)) {
$showCriteria[$key] = $criterion;
}
}

Plugin::info('Search for existing category with query `{i}`', ['i' => Json::encode($showCriteria)]);

$ids = $query->ids();

Expand Down
48 changes: 44 additions & 4 deletions src/fields/Entries.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Cake\Utility\Hash;
use Craft;
use craft\base\Element as BaseElement;
use craft\elements\conditions\ElementConditionInterface;
use craft\elements\Entry as EntryElement;
use craft\errors\ElementNotFoundException;
use craft\feedme\base\Field;
Expand All @@ -13,7 +14,9 @@
use craft\feedme\Plugin;
use craft\fields\Entries as EntriesField;
use craft\helpers\Db;
use craft\helpers\ElementHelper;
use craft\helpers\Json;
use craft\services\ElementSources;
use Throwable;
use yii\base\Exception;

Expand Down Expand Up @@ -94,6 +97,7 @@ public function parseField(): mixed
$nodeKey = null;

$sectionIds = [];
$customSources = [];

if (is_array($sources)) {
foreach ($sources as $source) {
Expand All @@ -103,10 +107,21 @@ public function parseField(): mixed
$sectionIds[] = ($section->type == 'single') ? $section->id : '';
}
} else {
[, $uid] = explode(':', $source);
$sectionIds[] = Db::idByUid('{{%sections}}', $uid);
// if the source starts with "custom:", it's a custom source, and we can't treat it like a section
if (str_starts_with($source, 'custom:')) {
$customSources[] = ElementHelper::findSource(EntryElement::class, $source, ElementSources::CONTEXT_FIELD);
} else {
[, $uid] = explode(':', $source);
$sectionIds[] = Db::idByUid('{{%sections}}', $uid);
}
}
}

// if there's only one source, and it's a custom source, make sure $create is nullified;
// we don't want to create entries for custom sources because of ensuring all the conditions are met
if (count($sources) == 1 && !empty($customSources)) {
$create = null;
}
} elseif ($sources === '*') {
$sectionIds = null;
}
Expand Down Expand Up @@ -157,13 +172,38 @@ public function parseField(): mixed
}

$criteria['status'] = null;
$criteria['sectionId'] = $sectionIds;
$criteria['limit'] = $limit;
$criteria['where'] = ['=', $columnName, $dataValue];

Craft::configure($query, $criteria);

Plugin::info('Search for existing entry with query `{i}`', ['i' => Json::encode($criteria)]);
// if we have any custom sources, we want to modify the query to account for those
if (!empty($customSources)) {
$conditionsService = Craft::$app->getConditions();
foreach ($customSources as $customSource) {
/** @var ElementConditionInterface $sourceCondition */
$sourceCondition = $conditionsService->createCondition($customSource['condition']);
$sourceCondition->modifyQuery($query);
}
}

if (!empty($sectionIds)) {
// now that the custom sources have been accounted for,
// we can adjust the section id to include any regular, section sources (section ids)
$query->sectionId = array_merge($query->sectionId ?? [], $sectionIds);
}

// we're getting the criteria from conditions now too, so they are not included in the $criteria array;
// so, we get all the query criteria, filter out any empty or boolean ones and only show the ones that look to be filled out
$showCriteria = $criteria;
$allCriteria = $query->getCriteria();
foreach ($allCriteria as $key => $criterion) {
if (!empty($criterion) && !is_bool($criterion)) {
$showCriteria[$key] = $criterion;
}
}

Plugin::info('Search for existing entry with query `{i}`', ['i' => Json::encode($showCriteria)]);

$ids = $query->ids();

Expand Down
50 changes: 44 additions & 6 deletions src/fields/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Cake\Utility\Hash;
use Craft;
use craft\base\Element as BaseElement;
use craft\elements\conditions\ElementConditionInterface;
use craft\elements\db\UserQuery;
use craft\elements\User as UserElement;
use craft\errors\ElementNotFoundException;
Expand All @@ -14,7 +15,9 @@
use craft\feedme\Plugin;
use craft\fields\Users as UsersField;
use craft\helpers\Db;
use craft\helpers\ElementHelper;
use craft\helpers\Json;
use craft\services\ElementSources;
use Throwable;
use yii\base\Exception;

Expand Down Expand Up @@ -86,12 +89,16 @@ public function parseField(): mixed

// Get source id's for connecting
$groupIds = [];
$customSources = [];
$isAdmin = false;
$status = null;

if (is_array($sources)) {
// go through sources that start with "group:" and get group uid for those
foreach ($sources as $source) {
if (str_starts_with($source, 'custom:')) {
$customSources[] = ElementHelper::findSource(UserElement::class, $source, ElementSources::CONTEXT_MODAL);
}
if (str_starts_with($source, 'group:')) {
[, $uid] = explode(':', $source);
$groupIds[] = Db::idByUid('{{%usergroups}}', $uid);
Expand All @@ -111,6 +118,12 @@ public function parseField(): mixed
if (in_array(UserElement::STATUS_INACTIVE, $sources, true)) {
$status[] = UserElement::STATUS_INACTIVE;
}

// if there's only one source, and it's a custom source, make sure $create is nullified;
// we don't want to create users for custom sources because of ensuring all the conditions are met
if (count($sources) == 1 && !empty($customSources)) {
$create = null;
}
} elseif ($sources === '*') {
$groupIds = null;
}
Expand Down Expand Up @@ -147,13 +160,12 @@ public function parseField(): mixed

$ids = [];
$criteria['status'] = null;
$criteria['groupId'] = $groupIds;
$criteria['limit'] = $limit;
$criteria['where'] = ['=', $columnName, $dataValue];

// If the only source for the Users field is "admins" we don't have to bother with this query.
if (!($isAdmin && empty($groupIds))) {
$ids = $this->_findUsers($criteria);
if (!($isAdmin && empty($groupIds) && empty($customSources))) {
$ids = $this->_findUsers($criteria, $groupIds, $customSources);
$foundElements = array_merge($foundElements, $ids);
}

Expand All @@ -162,7 +174,7 @@ public function parseField(): mixed
// So if we haven't found a match with the previous query, and field sources contains "admins",
// we have to look for the user among admins too.
if ($isAdmin && count($ids) === 0) {
unset($criteria['groupId']);
$criteria['groupId'] = null;
$criteria['admin'] = true;

$ids = $this->_findUsers($criteria);
Expand Down Expand Up @@ -250,12 +262,38 @@ private function _createElement($dataValue, $groupId): ?int
* @param $criteria
* @return array|int[]
*/
private function _findUsers($criteria): array
private function _findUsers($criteria, $groupIds = null, $customSources = null): array
{
$query = UserElement::find();
Craft::configure($query, $criteria);

Plugin::info('Search for existing user with query `{i}`', ['i' => json_encode($criteria)]);
// if we have any custom sources, we want to modify the query to account for those
if (!empty($customSources)) {
$conditionsService = Craft::$app->getConditions();
foreach ($customSources as $customSource) {
/** @var ElementConditionInterface $sourceCondition */
$sourceCondition = $conditionsService->createCondition($customSource['condition']);
$sourceCondition->modifyQuery($query);
}
}

if (!empty($groupIds)) {
// now that the custom sources have been accounted for,
// we can adjust the group id to include any regular, group sources (group ids)
$query->groupId = array_merge($query->groupId ?? [], $groupIds);
}

// we're getting the criteria from conditions now too, so they are not included in the $criteria array;
// so, we get all the query criteria, filter out any empty or boolean ones and only show the ones that look to be filled out
$showCriteria = $criteria;
$allCriteria = $query->getCriteria();
foreach ($allCriteria as $key => $criterion) {
if (!empty($criterion) && !is_bool($criterion)) {
$showCriteria[$key] = $criterion;
}
}

Plugin::info('Search for existing user with query `{i}`', ['i' => json_encode($showCriteria)]);

$ids = $query->ids();

Expand Down
20 changes: 12 additions & 8 deletions src/templates/_includes/fields/categories.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,16 @@
}) }}
</div>

<div class="element-create">
{{ feedMeMacro.checkbox({
label: 'Create categories if they do not exist'|t('feed-me'),
name: 'options[create]',
value: 1,
checked: hash_get(feed.fieldMapping, optionsPath ~ '.create') ?: '',
}) }}
</div>
{# don't allow crating new categories if the source is custom, because we can't ensure all the conditions will be met;
e.g. level, date created or updated etc #}
{% if field is defined and field is not empty and field.source starts with 'custom:' == false %}
<div class="element-create">
{{ feedMeMacro.checkbox({
label: 'Create categories if they do not exist'|t('feed-me'),
name: 'options[create]',
value: 1,
checked: hash_get(feed.fieldMapping, optionsPath ~ '.create') ?: '',
}) }}
</div>
{% endif %}
{% endblock %}
19 changes: 11 additions & 8 deletions src/templates/_includes/fields/entries.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,17 @@
}) }}
</div>

<div class="element-create">
{{ feedMeMacro.checkbox({
label: 'Create entries if they do not exist'|t('feed-me'),
name: 'options[create]',
value: 1,
checked: hash_get(feed.fieldMapping, optionsPath ~ '.create') ?: '',
}) }}
</div>
{# don't allow crating new entries if the only selected source is custom, because we can't ensure all the conditions will be met #}
{% if field is defined and craft.feedme.fieldHasOnlyCustomSources(field) == false %}
<div class="element-create">
{{ feedMeMacro.checkbox({
label: 'Create entries if they do not exist'|t('feed-me'),
name: 'options[create]',
value: 1,
checked: hash_get(feed.fieldMapping, optionsPath ~ '.create') ?: '',
}) }}
</div>
{% endif %}

{% if field %}
<div class="element-groups">
Expand Down
19 changes: 11 additions & 8 deletions src/templates/_includes/fields/users.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,15 @@
}) }}
</div>

<div class="element-create">
{{ feedMeMacro.checkbox({
label: 'Create users if they do not exist'|t('feed-me'),
name: 'options[create]',
value: 1,
checked: hash_get(feed.fieldMapping, optionsPath ~ '.create') ?: '',
}) }}
</div>
{# don't allow crating new entries if the only selected source is custom, because we can't ensure all the conditions will be met #}
{% if field is defined and craft.feedme.fieldHasOnlyCustomSources(field) == false %}
<div class="element-create">
{{ feedMeMacro.checkbox({
label: 'Create users if they do not exist'|t('feed-me'),
name: 'options[create]',
value: 1,
checked: hash_get(feed.fieldMapping, optionsPath ~ '.create') ?: '',
}) }}
</div>
{% endif %}
{% endblock %}
23 changes: 23 additions & 0 deletions src/web/twig/variables/FeedMeVariable.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use craft\models\Section;
use craft\models\TagGroup;
use DateTime;
use Illuminate\Support\Collection;
use yii\di\ServiceLocator;

/**
Expand Down Expand Up @@ -356,4 +357,26 @@ public function supportedSubField($class): bool

return in_array($class, $supportedSubFields, true);
}

/**
* Check if the only sources set for a relation field are custom ones.
*
* @param mixed $field
* @return bool
*/
public function fieldHasOnlyCustomSources(mixed $field = null): bool
{
if ($field === null) {
return false;
}

if (!isset($field['sources'])) {
return false;
}

$sources = new Collection($field['sources']);
$nativeSources = $sources->filter(fn(string $source) => !str_starts_with($source, 'custom:'));

return $nativeSources->isEmpty();
}
}
Loading