Unfortunately, a simple HTML file input on its own isn't enough to upload an image (in Craft or anywhere else) – the input simply exposes the file on your local filesystem to the browser/server. In other words, with your current setup, you're not actually uploading anything.
In my opinion, you have two choices:
- Use Assets and Craft's built-in Assets ElementSelect input (easy)
- Don't use Assets; upload the image natively using a file input (harder)
Personally, I'd recommend that unless you can't have the image be an Asset file for some reason, you should definitely go with the Assets approach. It's way easier to set up, offers a familiar user experience and you can even use image transforms and whatnot since your myImage
file will be a bona fide Asset.
Either way, here's how option 1 could look:
In your plugin's settings.html
file, replace your current call to the forms.file()
macro with this:
{{ forms.field({
label: "My Image"
}, include('_includes/forms/elementSelect', myImageSelectConfig)) }}
Then, fire up your plugin's primary class. Revise your getSettingsHtml()
method to include the myImageSelectConfig
attribute referenced in settings.html
above, so the method looks something like this:
public function getSettingsHtml()
{
return craft()->templates->render('yourpluginhandle/settings', array(
'settings' => $this->getSettings(),
'myImageSelectConfig' => array(
'id' => 'myImage',
'name' => 'myImage',
'jsClass' => 'Craft.AssetSelectInput',
'elementType' => new ElementTypeVariable(craft()->elements->getElementType(ElementType::Asset)),
'elements' => $this->getSettings()['myImage'] ? craft()->elements->getCriteria(ElementType::Asset, array('id' => $this->getSettings()['myImage'])) : null,
'sources' => array('folder:2'),
'criteria' => array('kind' => array('image'), 'localeEnabled' => null),
'limit' => 1,
'viewMode' => 'table',
'selectionLabel' => Craft::t('Add image'),
),
));
}
Note: The sources
attribute refers to the ID(s) for the Asset folder the admin should upload images to (and choose images from). You can look at the craft_assetfolders
database table to figure out which ID(s) you want. Alternatively you can remove the sources
property entirely, to enable all folders – which is probably the best option if this is going to be a publicly available plugin.
And that's it. The actual myImage
setting value stored will be an array with the ID to the selected Asset file, e.g. {"myImage":["42"]}
. If you need to pull the Asset in a template, you'll need to add a Variable class to your plugin, exposing the plugin settings:
class MyPluginVariable
{
public function settings()
{
return craft()->plugins->getPlugin('myPlugin')->getSettings();
}
}
...and in your template:
{% set assetId = craft.myPlugin.settings.myImage %}
{% set asset = craft.assets.id(assetId).first() %}
If you don't want to go with the Assets approach, you can keep your forms.file()
macro in settings.html
. However, there's a few more things you'll need to handle to get the upload working.
First, in order to actually upload a file from a file input, the enclosing form needs to have an enctype="multipart/form-data"
attribute. Unfortunately, plugin settings forms don't, and there's no real way to add it. However, you can use jQuery to hack it in – add this to your plugin's settings.html
file:
{% includeJs '$("form:first").attr("enctype", "multipart/form-data");' %}
Second, you'll need some server side code to actually handle the upload. Add the following to your plugin's primary class:
public function prepSettings($settings)
{
$imageFile = UploadedFile::getInstanceByName('settings.myImage');
if ($imageFile) {
// Where should the image be stored?
$basePath = $_SERVER['DOCUMENT_ROOT'];
$uploadFolder = '/uploads/yourpluginhandle/';
$filePath = $basePath.$uploadFolder.$imageFile->name;
// Make sure the upload folder exists
IOHelper::ensureFolderExists($basePath.$uploadFolder);
// Attempt to upload the file
if ($imageFile->saveAs($filePath)) {
// File uploaded successfully – store the relative filepath in the plugin settings
$settings['myImage'] = str_replace($basePath, '', $filePath);
} else {
// Aw.
craft()->userSession->setError(Craft::t('Unable to upload image {filename}', array(
'filename' => $imageFile->name,
)));
}
}
return $settings;
}
The above example is pretty much the bare minimum you'd need in order to upload a file from your plugin's settings page, to an arbitrary folder on your server's file system. The example prepSettings()
script will upload the file to a folder called /uploads/yourpluginhandle
in the public webroot (i.e. $_SERVER['DOCUMENT_ROOT']
), before storing the relative path to the file in the settings, if the upload was successful.
Keep in mind that file uploading is actually fairly complex (lots of things can go wrong), and also a potential security risk. You should probably consider adding some code to ensure that the file's mime type ($imageFile->type
), size ($imageFile->size
) and other properties are legit, before calling $file->saveAs()
. You might also need to create a UI for displaying and deleting the current image, and making sure old and no longer selected files are deleted from the server.
Finally, I'd posit that a vanilla browser file input is a pretty terrible user experience, compared to the Assets select input in Craft proper. There's all sorts of JavaScript libraries to help out with that, of course – in any case the above is hopefully a good starting point if you go down this road.