File management
Often an inlay needs to provide static assets (e.g. images, css...) and sometimes dynamic assets, e.g. if an inlay's configuration allows admins to provide images. Or perhaps end-user interaction with the inlay generates assets that need to be publicly available: e.g. the grassroots petition inlay allows the pulblic to set up a new petition and upload an image for it.
We probably require that such assets have un-guessable names (e.g. names include some hash).
We need to know which inlays are using which assets, so we can delete ones that are no longer in use.
Basic idea:
<?php
class \Civi\Inlay\MyType extends \Civi\Inlay\Type {
...
/**
* custom function for our MyType inlays, called from processRequest,
* @param array $img Ready-validated image ready for storing.
*/
protected function processUploadedImage($img) {
$publicURL = $this->saveAsset('campaign-x-image', $img['physical_current_filepath']);
}
}
...
class \Civi\Inlay\Type {
...
/**
* Store a file, return its record.
*/
public function saveAsset(string $identifier, string $usedBy, string $filepath, bool $newHash = FALSE) :string {
$ext = getExtensionFrom($filepath);
$dir = getPlaceWeKeepStuff();
$existingAsset = InlayAsset::get()->addWhere('identifier', '=', $identifier)->execute()->first();
if ($existingAsset) {
$records = [$existingAsset];
if ($newHash) {
unlink($dir . $identifier . $existingAsset['hash'] . $ext);
$hash = randomHashGenerator();
}
else {
$hash = $existingAsset['hash'];
$records = [['hash' => $hash, 'identifier' => $identifier, 'inlay_id' => $this->getID()]];
}
}
move_file($filepath, $dir . $identifier . $hash . $ext);
$asset = InlayAsset::save()->setRecords($records)->execute();
// Store usage
$this->setAssetUsedBy($asset['id'], $usedBy);
return getUrlToPlaceWeKeepStuff() . $identifier . $hash . $ext;
}
public function setAssetUsedBy($assetID, string $usedBy) {
if (InlayAsset::get()->where($assetID and $usedBy)) return;
InlayAsset::save()->setRecords([[$assetID, $usedBy]]);
}
public function getAsset($identifer) {
// Simple API wrapper: fetch by $identifier.
}
}
Inlay Types are advised to use their public ID as the prefix to identifier, to claim their own namespace for files, where that's appropriate.
Using the inlay instance public ID is good because it's unique and doesn't give anything away, but e.g. #4 (closed) might need assets that are not bound to a particular instance.
This would result in some directory having files like [publicID]IdentifierHash.ext
.
That dir could have world access; we hope this is ok given that you can't guess-access files in there.