Skip to content

Commit

Permalink
Merge branch '5.x' into 6.x
Browse files Browse the repository at this point in the history
  • Loading branch information
angrybrad committed Sep 25, 2024
2 parents ff1844b + 73a3df8 commit e1305e1
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 39 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Release Notes for Feed Me

## Unreleased

- Added support for importing into relational fields that have custom sources selected. ([#1504](https://github.com/craftcms/feed-me/pull/1504))
- Fixed a bug that could occur when uploading files to an Assets field from an external URL and a new filename is provided, but we can't determine the remote file's extension. ([#1506](https://github.com/craftcms/feed-me/pull/1506))

## 6.3.0 - 2024-08-14
Expand Down
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 @@ -131,13 +144,29 @@ public function parseField(): mixed
}

$criteria['status'] = null;
$criteria['groupId'] = $groupId;
$criteria['limit'] = $limit;
$criteria[$match] = $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 @@ -148,13 +163,38 @@ public function parseField(): mixed
}

$criteria['status'] = null;
$criteria['sectionId'] = $sectionIds;
$criteria['limit'] = $limit;
$criteria[$match] = $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 @@ -138,13 +151,12 @@ public function parseField(): mixed

$ids = [];
$criteria['status'] = null;
$criteria['groupId'] = $groupIds;
$criteria['limit'] = $limit;
$criteria[$match] = $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 @@ -153,7 +165,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 @@ -241,12 +253,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 @@ -60,12 +60,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 @@ -62,14 +62,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 @@ -61,12 +61,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();
}
}

0 comments on commit e1305e1

Please sign in to comment.