Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/craftcms/feed-me into de…
Browse files Browse the repository at this point in the history
…velop
  • Loading branch information
angrybrad committed Nov 25, 2023
2 parents 95801c4 + 0f80072 commit 7d5d6b9
Showing 1 changed file with 120 additions and 27 deletions.
147 changes: 120 additions & 27 deletions src/helpers/AssetHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
use craft\helpers\Json;
use craft\helpers\StringHelper;
use craft\helpers\UrlHelper;
use craft\models\AssetIndexData;
use craft\models\FsListing;
use craft\models\VolumeFolder;
use Throwable;
use yii\base\Exception;
use yii\base\InvalidArgumentException;
Expand Down Expand Up @@ -79,6 +82,72 @@ public static function downloadFile($srcName, $dstName, int $chunkSize = 1, bool
return $status;
}

/**
* Check if the file exists in the FS - if it does, index it (like via asset indexing) and return it
* otherwise return false and proceed with creating the asset
*
* @param $urlFromFeed
* @param $fieldInfo
* @param $feed
* @param $field
* @param $element
* @param $folderId
* @param $newFilename
* @return AssetElement|bool
* @throws \craft\errors\AssetDisallowedExtensionException
* @throws \craft\errors\FsException
* @throws \craft\errors\MissingAssetException
* @throws \craft\errors\VolumeException
* @throws \yii\base\InvalidConfigException
*/
public static function indexExistingFile($urlFromFeed, string $conflict, $field = null, $element = null, $folderId = null, $newFilename = null): AssetElement|bool
{
// if the conflict strategy is to replace or create, just bail straight away
// and allow for creation of the asset even the file exists
if ($conflict == AssetElement::SCENARIO_REPLACE || $conflict == AssetElement::SCENARIO_CREATE) {
return false;
}
$folder = self::_getAssetFolder($folderId, $field, $element);
$volume = $folder->getVolume();
$filename = $newFilename ? AssetsHelper::prepareAssetName($newFilename, false) : self::getRemoteUrlFilename($urlFromFeed);

// get the url/path of the asset we're supposed to end up with
$asset = new AssetElement();
$asset->setFilename($filename);
$asset->folderId = $folder->id;
$asset->folderPath = $folder->path;
$asset->volumeId = $volume->id;
$targetUrl = AssetsHelper::generateUrl($volume->getFs(), $asset);

$rootUrl = $volume->getRootUrl() ?? '';
$targetPath = str_replace($rootUrl, '', $targetUrl);

// check if it exists
if (!$volume->fileExists($targetPath)) {
// if it doesn't - proceed with createAsset()
return false;
}

// if it does - index it
$listing = new FsListing([
'dirname' => pathinfo($targetPath, PATHINFO_DIRNAME),
'basename' => pathinfo($targetPath, PATHINFO_BASENAME),
'type' => 'file',
'dateModified' => $volume->getDateModified($targetPath),
'fileSize' => $volume->getFileSize($targetPath),
]);

$indexEntry = new AssetIndexData([
'volumeId' => $volume->id,
'uri' => $listing->getUri(),
'size' => $listing->getFileSize(),
'timestamp' => $listing->getDateModified(),
'isDir' => $listing->getIsDir(),
]);

return Craft::$app->getAssetIndexer()->indexFileByEntry($indexEntry);
}

/**
* @param array $urls
* @param $fieldInfo
Expand All @@ -102,30 +171,36 @@ public static function fetchRemoteImage(array $urls, $fieldInfo, $feed, $field =
// user has set to use that instead, so we're good to proceed.
foreach ($urls as $url) {
try {
$filename = $newFilename ? AssetsHelper::prepareAssetName($newFilename, false) : self::getRemoteUrlFilename($url);
$indexedAsset = self::indexExistingFile($url, $conflict, $field, $element, $folderId, $newFilename);

$fetchedImage = $tempFeedMePath . $filename;
if ($indexedAsset instanceof AssetElement) {
$uploadedAssets[] = $indexedAsset->id;
} else {
$filename = $newFilename ? AssetsHelper::prepareAssetName($newFilename, false) : self::getRemoteUrlFilename($url);

// But also check if we've downloaded this recently, use the copy in the temp directory
$cachedImage = FileHelper::findFiles($tempFeedMePath, [
'only' => [$filename],
'recursive' => false,
]);
$fetchedImage = $tempFeedMePath . $filename;

Plugin::info('Fetching remote image `{i}` - `{j}`', ['i' => $url, 'j' => $filename]);
// But also check if we've downloaded this recently, use the copy in the temp directory
$cachedImage = FileHelper::findFiles($tempFeedMePath, [
'only' => [$filename],
'recursive' => false,
]);

if (!$cachedImage) {
self::downloadFile($url, $fetchedImage, 1, true, $feed['id']);
} else {
$fetchedImage = $cachedImage[0];
}
Plugin::info('Fetching remote image `{i}` - `{j}`', ['i' => $url, 'j' => $filename]);

$result = self::createAsset($fetchedImage, $filename, $folderId, $field, $element, $conflict, Hash::get($feed, 'updateSearchIndexes'));
if (!$cachedImage) {
self::downloadFile($url, $fetchedImage, 1, true, $feed['id']);
} else {
$fetchedImage = $cachedImage[0];
}

if ($result) {
$uploadedAssets[] = $result;
} else {
Plugin::error('Failed to create asset from `{i}`', ['i' => $url]);
$result = self::createAsset($fetchedImage, $filename, $folderId, $field, $element, $conflict, Hash::get($feed, 'updateSearchIndexes'));

if ($result) {
$uploadedAssets[] = $result;
} else {
Plugin::error('Failed to create asset from `{i}`', ['i' => $url]);
}
}
} catch (Throwable $e) {
if ($field) {
Expand Down Expand Up @@ -215,15 +290,7 @@ public static function createBase64Image($base64, $fieldInfo, $feed, $field = nu
private static function createAsset(string $tempFilePath, string $filename, ?int $folderId, ?Assets $field, ?ElementInterface $element, string $conflict, bool $updateSearchIndexes): bool|int
{
$assets = Craft::$app->getAssets();

if (!$folderId) {
if (!$field) {
throw new InvalidArgumentException('$folderId and $field cannot both be null.');
}
$folderId = $field->resolveDynamicPathToFolderId($element);
}

$folder = $assets->findFolder(['id' => $folderId]);
$folder = self::_getAssetFolder($folderId, $field, $element);

// Create the new asset (even if we're setting it to replace)
$asset = new AssetElement();
Expand Down Expand Up @@ -369,4 +436,30 @@ public static function getRemoteUrlExtension($url): string

return StringHelper::toLowerCase($extension);
}

/**
* Find and return folder for an asset by folder id or field and element
*
* @param int|null $folderId
* @param Assets|null $field
* @param ElementInterface|null $element
* @return VolumeFolder
*/
private static function _getAssetFolder(?int $folderId, ?Assets $field, ?ElementInterface $element): VolumeFolder
{
if (!$folderId) {
if (!$field) {
throw new InvalidArgumentException('$folderId and $field cannot both be null.');
}
$folderId = $field->resolveDynamicPathToFolderId($element);
}

$folder = Craft::$app->getAssets()->findFolder(['id' => $folderId]);

if (!$folder) {
throw new InvalidArgumentException('Cannot find folder by ID.');
}

return $folder;
}
}

0 comments on commit 7d5d6b9

Please sign in to comment.