I have a form in my application that allows users to create posts and while doing so upload multiple images to the post being created.
I am using Laravel Livewire and Filepond to achieve this.
The problem I am having is I need to allow the user to reorder the images (as it is a gallery and the order is important), and save the order in the database when the form in submitted.
Another issue I am running into is allowing a user to edit their post later. I need their pre-existing post images loaded in filepond, and also allow them to upload more, delete, and/or reorder.
When the user saves the post I need to be able to update my database and file system.
All info online is how to upload files, but no info on how to reorder, or pre-populate with pre-existing files.
Here is my current code for reference:
<div
x-data=""
x-init="
FilePond.setOptions({
allowMultiple: true,
allowReorder: true,
itemInsertLocation: 'after',
server: {
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
#this.upload('images', file, load, error, progress)
},
revert: (filename, load) => {
#this.removeUpload('images', filename, load)
},
load: (source, load, error, progress, abort, headers) => {
var myRequest = new Request(source);
fetch(myRequest).then(function(response) {
response.blob().then(function(myBlob) {
load(myBlob)
});
});
},
},
});
const pond = FilePond.create($refs.input, {
acceptedFileTypes: ['image/png', 'image/jpeg'],
maxFileSize: '7MB',
allowImageCrop: true,
allowReorder: true,
allowImageResize: true,
imageResizeTargetWidth: '1000px',
imageResizeTargetHeight: '1000px',
filePosterMaxHeight: '256px',
files: {{ $existingImages }} // used for when editing a post and it already has images. see php component on how I set this variable
});
"
>
<div wire:ignore wire:key="images">
<div class="form-group text-center">
<input
id="image-upload"
type="file"
x-ref="input"
multiple
data-allow-reorder="true"
data-max-file-size="3MB"
data-max-files="10"
>
</div>
</div>
</div>
My Livewire PHP component:
public $images = [];
public $existingImages;
public function mountMedia($post) {
if($post){
$this->existingImages = $post->images->map(function ($image) use ($post) {
return [
'source' => $image->id,
'options' => [
'type' => 'local',
'file' => [
'name' => $image->getUrl(),
'size' => $image->file_size,
'type' => $image->mime_type,
],
'metadata' => [
'poster' => $image->getUrl(),
'position' => $image->position
],
],
];
});
}
}
public function saveImage($file, $post, $position) {
// Create a unique random string
$randString = Str::random(3);
// Get time
$time = time();
// Set file name
$filename = $time. '-' . $randString.'-'.auth()->user()->id;
$extension = '.'.$file->getClientOriginalExtension();
// Save images for gallery
$regImage = $file->storeAs('/'. $post->id, $filename.$extension, 'post_images');
// Create a new image in db
Image::create([
'user_id' => auth()->user()->id,
'post_id' => $post->id,
'position' => $position,
'filename' => $filename,
'extension' => $extension,
'src' => 'post_images',
'mime_type' => $file->getMimeType(),
'file_size' => $file->getSize(),
]);
}
public function saveMedia($post) {
// Make sure user owns post
abort_unless($post->user_id == auth()->user()->id, 403);
// Set default position
$position = 1;
// Save each image
foreach ($this->images as $file) {
$this->saveImage($file, $post, $position);
// Increment position for next image
$position++;
}
}
}
For sorting items in Livewire I would use https://github.com/livewire/sortable.
Sortable is very easy to use.
For filepond if the original image should be used again later I would save that image as well with a relation to the edited version.
Related
I have a nuxt frontend using the vue-filepond adapter, users have the option to upload images with there post. This is then send to a laravel API that will handle the request.
<client-only>
<file-pond
name="image"
ref="pond"
class="filepond"
:allow-multiple="false"
accepted-file-types="image/jpeg, image/png"
server="http://127.0.0.1:8000/api/posts"
allowRevert="false"
:files="form.image"
/>
</client-only>
using mostly default filepond options,
data() {
return {
errors: [],
form: {
title: '',
content: '',
image: [],
}
}
},
Data is uploaded to the api like so
methods: {
createPost() {
this.$axios.$post('http://127.0.0.1:8000/api/posts', this.form)
this.$toast.show({
type: 'success',
title: 'Success',
message: 'Your post has been created'
})
}
}
Now since filePond is async the file is uploaded earlier then my form when I post it.
so in the laravel part
public function store(Request $request): void
{
if ($request->hasFile('image')) {
$path = Storage::putFile('avatars', $request->file('image'));
}
$request->validate([
'title' => 'required|string|max:24',
'content' => 'required|string|max:254',
'image' => 'nullable|image'
]);
Post::create([
'title' => $request->get('title'),
'slug' => Str::slug($request->get('title'), '-'),
'content' => $request->get('content'),
'image' => $path ?? null
]);
}
The image would be stored, but if I click submit on my form to upload a title and some content the ìmage part in the Post::create method is always NULL.
How can I make it so that filePond is not uploaded async anymore? so that when I hit submit on my form the title , content and image are all uploaded equally
Figured it out thanks to kissu and reading through filePond's docs.
const file = this.$refs.pond.getFiles()[0].file
const data = new FormData()
data.append('title', this.form.title)
data.append('content', this.form.content)
data.append('image', file)
this.$axios.$post('http://127.0.0.1:8000/api/posts', data)
and in your backend (laravel in my case)
if ($request->hasFile('image') && $request->file('image')->isValid()) {
$post->addMediaFromRequest('image')->toMediaCollection('image');
}
I need to upload large files to my site, and to do this, I used the Dropzone JS, with pion/laravel-chunk-upload, I do not understand, everything is good and true, but although this, any upload for large files is not completed, when uploading small files, I get a result, but When I try with larger files eg 5MB,
It stops at a part of uploading for Hosts eg (Hostinger)
does not work and gives an error from laravel validator for WampServer 4 (localhost)
I tried here to remove my Validator, but the same problem, I can't
upload or check if is a valid file or something like that! (for localhost)
I tried a lot but I do not understand the problem and can't find a solution, please help, this is my code:
My view:
<form action="{{ route('files') }}" enctype="multipart/form-data" class="dropzone" id="fileupload" method="POST">
#csrf
<input type="hidden" name="item_id" value="{{ $item->id }}">
<div class="fallback">
<input name="file" type="files" multiple />
</div>
</form>
Controller:
// UPLOAD FILES
protected function uploadFiles(Request $request) {
$validator = Validator::make($request->all(), [
'file' => 'required|max:3145730', // 3GB
'item_id' => 'required|numeric'
]);
$item_id = $request->item_id;
$item_data = Item::whereId($item_id)->where('user_id', Auth::id())->whereStatus(0)->first();
if (!$item_data || $validator->fails()) {
return response()->json([
'status' => true,
'error' => 'Invalid data!'
], 401);
}
if ($request->hasFile('file')) {
# CHECK IF IS FILE
if ($request->file('file')->isValid()) {
$file = $request->file('file');
# UPLOAD
$type = strtolower($file->getClientOriginalExtension());
$mime = $file->getMimeType();
$size = $file->getSize();
$width = null;
$height = null;
if (!in_array($type, ['png', 'jpeg', 'jpg', 'zip']) || !in_array($mime, ['application/octet-stream', 'application/zip', 'image/jpg', 'image/png', 'image/jpeg'])) {
return response()->json([
'status' => true,
'error' => 'You can\'t upload files of this type.'
], 401);
}
// create the file receiver
$receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request));
// check if the upload is success, throw exception or return response you need
if ($receiver->isUploaded() === false) {
throw new UploadMissingFileException();
}
// receive the file
$save = $receiver->receive();
// check if the upload has finished (in chunk mode it will send smaller files)
if ($save->isFinished()) {
// save the file and return any response you need, current example uses `move` function. If you are
// not using move, you need to manually delete the file by unlink($save->getFile()->getPathname())
if (in_array($type, ['png', 'jpeg', 'jpg'])) {
list($width, $height) = getimagesize($file);
}
return $this->saveFile($save->getFile(), $item_id, $type, $mime, $size, $width, $height);
}
// we are in chunk mode, lets send the current progress
/** #var AbstractHandler $handler */
$handler = $save->handler();
return response()->json([
"done" => $handler->getPercentageDone(),
'status' => true
]);
}
}
return response()->json([
'status' => true,
'error' => 'Invalid data! Please upload a valid file.'
], 401);
}
JS:
Dropzone.prototype.defaultOptions.dictDefaultMessage = "DRAG & DROP FILES HERE TO UPLOAD";
var myDropzone = new Dropzone("#fileupload", {
acceptedFiles: ".jpg, .jpeg, .png, .zip",
chunking: true,
method: "POST",
maxFilesize: 3072, // 3GB
chunkSize: 10000000, // 10MB
maxFiles: 6,
parallelChunkUploads: true,
});
Check your PHP.ini configuration for max size.
upload_max_filesize = 10M
post_max_size = 11M
I'm creating a module that has a custom admin 2-col page that uses ajax to populate a div, showing content based on a dropdown selection before form submission.
It all works fine, and I can see the container updated by ajax.
But when I try use a custom template for 2-col layout, I get the following inserted into the container:
An unrecoverable error occurred. The uploaded file likely exceeded the
maximum file size (50 MB) that this server supports.
There are no watchdog messages or log details, so this might suggest an apache configuration issue (https://www.drupal.org/forum/support/post-installation/2013-02-27/an-unrecoverable-error-occurred-the-uploaded-file-likely), but mod_security does not appear to be enabled, and the form does not contain any files and it's no way near 50MB! So I don't know where this is coming from. This is currently in my dev environment on my laptop and I've not faced this before, so I don't think apache config is an issue.
It strikes me that there may be a core bug in the form API for ajax with custom templates, because it works fine without a custom template... unless I'm implementing the custom template incorrectly.
A possible workaround would be to use CSS for force the container onto the RHS, but this should ideally be in the template so that admin themes can work with it.
I've put the code in pastebin: https://pastebin.com/F1zkd5rg.
or listed below:
my_module.links.menu.yml
my_module.main:
route_name: my_module.main
title: My Module
parent: system.admin
weight: -6
my_module.form_page:
route_name: my_module.form_page
title: My Module Form
parent: my_module.main
weight: -6
my_module.routing.yml
my_module.main:
path: '/admin/my_module'
defaults:
_controller: 'Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
_title: 'My Module'
requirements:
_permission: 'administrator'
my_module.form_page:
path: '/admin/my_module/form'
defaults:
_form: 'Drupal\my_module\Form\MyModuleForm'
_title: 'My Module Form'
requirements:
_permission: 'administrator'
my_module.module
<?php
/**
* Implements hook_theme_registry_alter
*/
function my_module_theme($existing, $type, $theme, $path) {
return [
'my_module_form' => [
'render element' => 'form',
],
];
}
templates/my-module-form.html.twig
<form {{ attributes }}>
<div class="layout-column layout-column--half">
{{ form.user_view }}
{{ form.submit }}
</div>
<div class="layout-column layout-column--half">
{{ form.user_list_wrapper }}
</div>
</form>
src/Form/MyModuleForm.php
<?php
/**
* #file
* Contains \Drupal\my_module\Form\MyModuleForm.
*/
namespace Drupal\my_module\Form;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Views;
/**
* Configure custom_rest settings for this site.
*/
class MyModuleForm extends FormBase {
/**
* {#inheritdoc}
*/
public function getFormId() {
return 'my_module_form';
}
/**
* {#inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
...
$form['#theme'] = 'my_module_form';
$form['user_view'] = [
'#type' => 'select',
'#title' => $this->t('Select element'),
'#options' => $userViews,
'#ajax' => [
'callback' => '::findUsers',
'event' => 'change',
'wrapper' => 'edit-user-list',
'progress' => array(
'type' => 'throbber',
'message' => t('Searching Users...'),
),
],
];
$form['user_list_wrapper'] = [
'#type' => 'container',
'#attributes' => array(
'class' => array(
'user-list-wrapper',
),
),
];
$form['user_list_wrapper']['user_list'] = [
'#type' => 'item',
'#attributes' => [
'id' => ['user_list'],
],
'#markup' => '<ul><li>None</li></ul>'
];
$form['submit'] = [
'#type' => 'submit',
'#value' => t('Submit'),
];
return $form;
}
/**
* Ajax callback to list users.
*/
public function findUsers(array &$form, FormStateInterface $form_state) {
// Create the user list HTML
$selected = $form_state->getValue('user_view');
...
$user_list = '';
...
if (strlen($user_list) == 0) {
$user_list = 'None';
} else {
$user_list = "<ul>$user_list</ul>";
}
// Generate the AJAX response
$ajax_response = new AjaxResponse();
$ajax_response->addCommand(new HtmlCommand('#edit-user-list', $user_list));
return $ajax_response;
}
public function submitForm(array &$form, FormStateInterface $form_state) {
drupal_set_message('Nothing Submitted. Just an Example.');
}
}
Thanks in advance
Example for send and received data between ajax function and action of controller in Zend framework 3
Here is a simple example of an ajax request using ZF3. You may give a try with this one. In this example we would use ZF3's default Application module.
Lets assume we would retrieve data via a ajax call from the following url.
http://yoursite.com/title
Lets create an action method for the title route in the IndexController.
public function titleAction()
{
// Initialize view
$view = new ViewModel();
// Checks if this is a Javascript request
$xmlHttpRequst = $this->getRequest()->isXmlHttpRequest();
if (! $xmlHttpRequst) {
die('Bad request');
}
/**
* Here we may pull data from database but for tests
* here we make an array of titles for the view
*/
$titles = [];
for ($i = 0; $i < 10; $i++) {
$titles[] = "Lorem ipsum dolor {$i}";
}
// Set data to be used in the view
$view->setVariable('titles', $titles);
/**
* Tell the renderer not to show the layout
* by setting setTerminal to true
*/
$view->setTerminal(true);
return $view;
}
We created a method, we need creating a view template for it.
view/application/index/title.phtml
<?php
foreach ($titles as $title) {
echo '<h2>' . $title . '</h2>';
}
Now we would create another action method in the IndexController from where we would make the ajax call.
http://yoursite.com/text
So lets make that action method too...
public function textAction()
{
return new ViewModel();
}
and view template would be like so
view/application/index/text.phtml
<h1>Handling ajax request</h1>
<button onclick="showTitle()">Show Title</button>
<div id="box"></div>
<?php
// Set url
$url = $this->serverUrl('/title'); // http://yoursite.com/title
// This is for the "url" catch
echo "<script>" . PHP_EOL;
echo "\tvar url = '{$url}';" . PHP_EOL;
echo "</script>" . PHP_EOL;
?>
<script>
function showTitle() {
$.get(url, function(data){
$('#box').html(data);
})
.done(function(){
console.log('Done!');
})
.fail(function(){
console.log('Failed!');
});
}
</script>
This script needs jQuery Javascript library to make the ajax call. So make sure that script is added in your view/layout/layout.phtml.
The last thing we need is to set up routes for the /title and /text. Lets add those two routes to the route section of module/Application/config/module.config.php
'title' => [
'type' => Literal::class,
'options' => [
'route' => '/title',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'title',
],
],
],
'text' => [
'type' => Literal::class,
'options' => [
'route' => '/text',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'text',
],
],
],
Let us know if it makes you happy!
I need to create an image upload field for posts in Magento Blog Module (AW blog).
Therefore, each post needs to contain an image.
I edited the following files:
/home/clients/websites/w_gale/public_html/gale/app/code/community/AW/Blog/Block/Manage/Blog/Edit/Tab/Form.php
added fieldset like this:
$fieldset->addField('fileinputname', 'image', array(
'label' => Mage::helper('blog')->__('Upload image'),
'required' => false,
'name' => 'fileinputname',
'required' => true,
'style' => 'height:100px;border:2px solid #999;',
));
on top of this file, in the right place, I defined form like this:
$form = new Varien_Data_Form(array(
'id' => 'edit_form',
'action' => $this->getUrl('*/*/save', array('id' => $this->getRequest()->getParam('id'))),
'method' => 'post',
'enctype' => 'multipart/form-data',
)
);
In Blogcontroller.php I added the following code, just bellow the if ($data = $this->getRequest()->getPost()) { line
if(isset($_FILES['fileinputname']['name']) && (file_exists($_FILES['fileinputname']['tmp_name']))) {
try {
$uploader = new Varien_File_Uploader('fileinputname');
$uploader->setAllowedExtensions(array('jpg','jpeg','gif','png')); // or pdf or anything
$uploader->setAllowRenameFiles(true);
$uploader->setFilesDispersion(false);
$path = Mage::getBaseDir('media') . DS ;
$uploader->save($path, $_FILES['fileinputname']['name']);
$data['imageurl'] = $_FILES['fileinputname']['name'];
} catch(Exception $e) {
}
} else {
if(isset($data['fileinputname']['delete']) && $data['fileinputname']['delete'] == 1)
$data['imageurl'] = '';
else
unset($data['fileinputname']);
}
However, the upload doesn't work. What am I doing wrong?
I added a special row in appropriate field in a database.
The frontend section displays the database value when I enter it manually.
Thanks
This code of method from data helper which uploads image. You need implement method getBaseDir() (which returns dir where you wish store your uploaded files) by yourself.
/**
* Upload image and return uploaded image file name or false
*
* #throws Mage_Core_Exception
* #param string $scope the request key for file
* #return bool|string
*/
public function uploadImage($scope)
{
$adapter = new Zend_File_Transfer_Adapter_Http();
$adapter->addValidator('ImageSize', true, $this->_imageSize);
$adapter->addValidator('Size', true, $this->_maxFileSize);
if ($adapter->isUploaded($scope)) {
// validate image
if (!$adapter->isValid($scope)) {
Mage::throwException(Mage::helper('mycompany_mymodule')->__('Uploaded image is not valid'));
}
$upload = new Varien_File_Uploader($scope);
$upload->setAllowCreateFolders(true);
$upload->setAllowedExtensions(array('jpg', 'gif', 'png'));
$upload->setAllowRenameFiles(true);
$upload->setFilesDispersion(false);
if ($upload->save($this->getBaseDir())) {
return $upload->getUploadedFileName();
}
}
return false;
}
You're using the right code. I solved the problem by using the right MYSQL data type. When I changed the data type from 'text' to 'varchar(255)' it solved the problem
And ... make sure that you add the following code:
$form = new Varien_Data_Form(array(
'id' => 'edit_form',
'action' => $this->getUrl('*/*/save', array('id' => $this->getRequest()->getParam('id'))),
'method' => 'post',
'enctype' => 'multipart/form-data',
)
);
in /app/code/community/AW/Blog/Block/Manage/Blog/Edit/Form.php
NOT: app/code/community/AW/Blog/Block/Manage/Blog/Edit/Tab/Form.php