Laravel - Image Upload - POST request return Error 405, but method and route exist - ajax

I'm new on Laravel development and started to build a small application for uploading images via form and drag and drop.
For some reason, when I try to upload an image using the POST route and the Controller's store method, the app only returns ERROR 405 - Method Not Allowed. I don't know why it's happening.
web.php
Route::resource('colaboradores', ColaboradoresController::class, ['only' => ['index', 'store', 'update', 'destroy']]);
My dropzone:
<form enctype="multipart/form-data" action="{{ route('administrativo.secoes.colaboradores.store') }}" id="image-upload-form">
<input type="file" name="file" id="image-input" class="dropzone-input" onchange="get_images(this)" multiple hidden>
<div class="drop-zone" id="image-dropzone" ondragover="dragOverHandler(event);" ondrop="dropHandler(event);">
<div class="inner-elements">
<label for="image-input">
<h3>Drag your pictures here</h3>
<span class="note needsclick">Or click here to manually select them.</span>
</label>
</div>
</div>
</form>
My JavaScript file with HTTP request through AJAX:
function dragOverHandler(ev) {
document.getElementById("image-dropzone").style.border = "3px solid #00f752";
ev.preventDefault();
}
function dropHandler(ev) {
document.getElementById("image-dropzone").style.border = "3px dashed #0087f7";
ev.preventDefault();
if (ev.dataTransfer.items) {
\[...ev.dataTransfer.items\].forEach((item, i) =\> {
if (item.type.includes("image")) {
const file = item.getAsFile();
upload_image(file);
}
});
}
}
function get_images() {
var total_images = document.getElementById("image-input").files.length;
if (total_images \> 0) {
for (var index = 0; index \< total_imagens; index++) {
var file = document.getElementById("image-input").files\[index\];
upload_image(file);
}
}
}
function upload_image(file) {
var form_data = new FormData();
form_data.append("file", file);
form_data.append("test", "test");
$.ajaxSetup({
headers: {
"X-CSRF-TOKEN": $("meta\[name='csrf-token'\]").attr("content")
}
});
$.ajax({
type: "PUT",
url: \`${window.location.pathname}/store/\`,
data: form_data,
cache: false,
contentType: false,
processData: false,
success: function (data) {
console.log(data);
},
error: function (data) {
console.log(data);
}
});
}
SobreController.php
<?php
namespace App\Http\Controllers\Administrativo;
use App\Http\Controllers\Controller;
use App\Models\SectionCollaborators;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class ColaboradoresController extends Controller
{
public function index()
{
//
}
public function create()
{
//
}
public function store(Request $request)
{
try {
$novo_nome = self::upload_imagem($request);
return response()->json(["imagem" => "$novo_nome", "mensagem" => "Imagem salva com sucesso."] 200);
} catch (Exception $e) {
return response()->json(["mensagem" => "Ocorreu um erro ao salvar a imagem. Tente novamente.", 500]);
}
}
private function upload_imagem(Request $request)
{
if ($request->hasFile('file') && $request->file('file')->isValid()) {
$request->validate([
'file' => 'required|image|mimes:jpeg,png,jpg,webm,svg|max:2048',
]);
$img = $request->file;
$novo_nome = Storage::disk('local')->put('public/img/colaboradores/cards', $img, 'public');
return basename($novo_nome);
}
}
public function update(Request $request)
{
//
}
private function update_text($object, $nome, $valor)
{
//
}
public function destroy(Request $request)
{
//
}
public function setId(int $value)
{
//
}
public function getId()
{
//
}
}

Related

How to pass data for a chart through an event

I have a chart, I am adding data through a controller. How do I add data via an event?
I have a database, through the controller I get the data, then I transfer it to the view and using the diagrams I display the data. I need to pass data through an event, how can this be done?
Chart:
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: [],
datasets: [{
label: 'data',
data: [],
borderWidth: 1
}]
},
options: {
scales: {
xAxes: [],
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
var updateChart = function() {
$.ajax({
url: "{{ route('chart') }}",
type: 'GET',
dataType: 'json',
success: function(data) {
myChart.data.labels = data.labels;
myChart.data.datasets[0].data = data.data;
myChart.update();
},
error: function(data){
console.log(data);
}
});
}
updateChart();
Echo.channel('events')
.listen('RealTimeMessage', (e) => {
updateChart();
});
Event:
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
class RealTimeMessage implements ShouldBroadcast
{
use SerializesModels;
public string $message;
public function __construct(string $message)
{
$this->message = $message;
}
public function broadcastOn(): Channel
{
return new Channel('events');
}
}
Controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Data;
Use App\Events\RealTimeMessage;
class ChartController extends Controller
{
public function index() {
$infos = Data::latest()->take(30)->get()->sortBy('id');
$labels = $infos->pluck('id');
$data = $infos->pluck('data');
// dd($labels);
// return view('welcome', compact('labels', 'data'));
return response()->json(compact('labels', 'data'));
}
public function add(Request $request) {
return view('add');
}
public function store(Request $request) {
Data::create($request->data);
event(new RealTimeMessage('Done!'));
return view('add');
}
}
Change the event like so: (all public properties will be broadcasted)
public string $message;
public array $data
public function __construct(string $message, array $data)
{
$this->message = $message;
$this->data = $data;
}
Then in your controller:
...
event(new RealTimeMessage('Done!', $data));
....

Vue Form component in Laravel getting 422 error on submission

I am trying to add a Vue form component to my Laravel application so I can reuse it in a few places throughout the app. But when I submit the form I get a 422 error saying that the route is not found.
Form component:
<template>
<form #submit.prevent="mail" method="POST">
</form>
</template>
<script>
import FormMixin from '../FormMixin';
export default {
mixins: [ FormMixin ],
data() {
return {
'action': 'submit',
}
}
}
</script>
Form Mixin
export default {
data() {
return {
fields: {},
errors: {},
success: false,
loaded: true,
action: '',
}
},
methods: {
mail() {
if (this.loaded) {
this.loaded = false;
this.success = false;
this.errors = {};
axios.post(this.action, this.fields).then(response => {
this.fields = {}; //Clear input fields.
this.loaded = true;
this.success = true;
}).catch(error => {
this.loaded = true;
if (error.response.status === 422) {
this.errors = error.response.data.errors || {};
}
});
}
},
},
}
Controller
public function mail(NewConctactRequest $contact) {
Mail::to('example#example.com')->send(new NewContact($contact));
return redirect()->route('thank you');
return response()->json(null, 200);
}
Web Routes
Route::get('/home', 'HomeController#index')->name('home');
Route::get('adventures', 'PageController#adventures')->name('adventures');
Route::get('crew', 'PageController#crew')->name('crew');
Route::get('events', 'PageController#events')->name('events');
Route::get('gallery', 'PageController#gallery')->name('gallery');
Route::get('thank_you', 'PageController#thank_you')->name('thank you');
Route::get('contact', 'ContactController#show')->name('contact');
Route::post('submit', 'ContactController#mail')->name('mail contact');
I have Axios installed already and the CSRF token is set in the head pf the document. When I use the form as just a standard form (not using Vue) it submits properly.

Upload multiple file with VueJs and Laravel

I try to do a single post request to upload multiple files. Now I have a functionally method that works for multiple files. But I want a single request.
submitFile(){
this.contract_file.forEach((file) =>{
let formData = new FormData();
formData.append('file', file.file);
axios.post('contracts/uploadfile/' + this.id_contract,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
}
}
).then(function(){
//
})
.catch(function(){
//
});
})
},
public function uploadFile(Request $request, Contract $contract)
{
$filename = $request->file('file')->getClientOriginalName();
$path = $request->file('file')->store($contract->id,'uploads');
$contractFile = new ContractFile();
$contractFile->fill([
'contract_id' => $contract->id,
'name' => $filename,
'path' => $path,
])->save();
}
Update:
This is what I changed,but..
let formData = []
this.contract_file.forEach((file,index) =>{
formData[index] = new FormData();
formData[index].append('file', file.file);
})
foreach($request->file('file') as $file){
//same code but I use $fille
}
Message:
Missing boundary in multipart/form-data POST data in Unknown
Update2:
<file-upload
class="btn btn-primary"
:multiple="true"
:drop="true"
:drop-directory="true"
v-model="files"
#input-filter="inputFilter"
#input-file="inputFile"
ref="upload">
<i class="fa fa-plus"></i>
Select files
</file-upload>
My answer is not properly tested since I had to adapt my code. Let me know if it doesn't work or if I'm missing something.
Basically, I built my own FormData to be more flexible and easier to reuse.
Form.vue
<template>
<div>
<input #change="upload($event)"
type="file"
name="picture"
id="new-file"
class="custom-file-input"
aria-label="picture"
multiple
>
<label class="custom-file-label" for="new-file">
<span>File...</span>
<span class="btn-primary">Browse</span>
</label>
<button #click="submit" type="button" >Submit</button>
</div>
<template>
<script>
import MyFormData from "./MyFormData";
export default {
data() {
return {
form: new MyFormData({contract_id: 5, files: []})
}
},
methods: {
upload(event) {
for (let file of event.target.files) {
try {
let reader = new FileReader();
reader.readAsDataURL(file); // Not sure if this will work in this context.
this.form.files.push(file);
} catch {}
}
},
submit(){
this.form.post('/my-url')
.catch(errors => {
throw errors;
})
.then((response) => location = response.data.redirect);
}
}
}
</script>
MyFormData.js
export default class MyFormData {
constructor(data, headers) {
// Assign the keys with the current object MyFormData so we can access directly to the data:
// (new FormData({myvalue: "hello"})).myvalue; // returns 'hello'
Object.assign(this, data);
// Preserve the originalData to know which keys we have and/or reset the form
this.originalData = JSON.parse(JSON.stringify(data));
this.form = null;
this.errors = {};
this.submitted = false;
this.headers = headers || {}
}
// https://stackoverflow.com/a/42483509/8068675
// It will build a multi-dimensional Formdata
buildFormData(data, parentKey) {
if (data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File) && !(data instanceof Blob)) {
Object.keys(data).forEach(key => {
this.buildFormData(data[key], parentKey ? `${parentKey}[${key}]` : key);
});
} else {
const value = data == null ? '' : data;
this.form.append(parentKey, value);
}
}
// Returns all the new / modified data from MyFormData
data() {
return Object.keys(this.originalData).reduce((data, attribute) => {
data[attribute] = this[attribute];
return data;
}, {});
}
post(endpoint) {
return this.submit(endpoint);
}
patch(endpoint) {
return this.submit(endpoint, 'patch');
}
delete(endpoint) {
return axios.delete(endpoint, {}, this.headers)
.catch(this.onFail.bind(this))
.then(this.onSuccess.bind(this));
}
submit(endpoint, requestType = 'post') {
this.form = new FormData();
this.form.append('_method', requestType);
this.buildFormData(this.data());
return axios.post(endpoint, this.form, {
headers: {
'Content-Type': `multipart/form-data; boundary=${this.form._boundary}`,
}
})
.catch(this.onFail.bind(this))
.then(this.onSuccess.bind(this));
}
onSuccess(response) {
this.submitted = true;
this.errors = {};
return response;
}
onFail(error) {
console.log(error);
this.errors = error.response.data.errors;
this.submitted = false;
throw error;
}
reset() {
Object.assign(this, this.originalData);
}
}
Edit Based on your note specifying you're using vue-upload-component
Your submit method should look like this
submitFile(){
let files = this.contract_file.map((obj) => obj.file));
let form = new MyFormData({files: files});
form.post('contracts/uploadfile/' + this.id_contract)
.then(function(){
//
})
.catch(function(){
//
});
},
In your controller
public function uploadFile(Request $request, Contract $contract) {
if($request->hasFile('files')){
$files = $request->file('files');
foreach ($files as $file) {
$filename = $file->getClientOriginalName();
$path = $file->store($contract->id,'uploads');
$contractFile = new ContractFile();
$contractFile->fill([
'contract_id' => $contract->id,
'name' => $filename,
'path' => $path,
])->save();
}
}
}
Adding the boundary to the Content-Type header fixed my problem. You can do it like below. Just change only submitFile() function.
submitFile(){
this.contract_file.forEach((file) =>{
let formData = new FormData();
formData.append('file', file.file);
axios.post('contracts/uploadfile/' + this.id_contract,
formData,
{
headers: {
'Content-Type': 'multipart/form-data;boundary=' + Math.random().toString().substr(2),
}
}
).then(function(){
//
})
.catch(function(){
//
});
})
},

Prestashop: Pass data from js file to controller to custom page

I'm having difficulty passing the value from my personal js to the controller and recovering it on the personal tpl page.
This module will serve to customize product after some selections and fields to fill out.
The selections pass from tabs to tabs.
The problem is that I can't get the value {$ var}
I have:
JS in root->modules->modulename->views->js->namejsfile.js
CONTROLLER in root->modules->modulename->controllers->front->controllername.php
VIEW in root->modules->modulename->views->templates->front->filename.tpl
in JS
$('#send').click(function(){
var ciao = 'cioaa';
var myUrl = prestashop.urls.base_url + 'index.php?fc=module&module=configuratore';
$.ajax({
type: 'get',
cache:false,
url: myUrl,
data: {
ajax: true,
datas:ciao,
action: 'fromAjax',
},
})
.done(function() {
console.log('Success!');
})
.fail(function() {
console.log('error');
});
});
in PHP
class ConfiguratoreTaskModuleFrontController extends ModuleFrontController
{
public function __construct()
{
parent::__construct();
}
public function init()
{
parent::init();
}
public function initContent()
{
parent::initContent();
$this->setTemplate('module:configuratore/views/templates/front/task.tpl');
}
$this->fromAjax();
}
public function fromAjax()
{
$mVar = Tools::getValue('datas');
return $this->context->smarty->assign(array('var'=>$mVar));
}
in TPL
{$var}
<?
public function fromAjax()
{
$mVar = Tools::getValue('datas');
$this->context->smarty->assign(array('var'=>$mVar));
$templateFile = 'module:configuratore/views/templates/front/task.tpl';
$html = $this->fetch($templateFile);
die($html); // pass to JS
}
in JS:
.done(function(html) {
console.log(html);
})

How to send an ajax contact form with to a recipient email address

I've currently got this:
$.ajax({
url: '/some/url',
dataType: 'json',
type: 'POST',
data: formData,
success: function(data) {
if (window.confirm('Thank you for your message. Can I erase the form?')) {
document.querySelector('.form-input').val('');
}
},
error: function(xhr, status, err) {
console.error(status, err.toString());
alert('There was some problem with sending your message.');
}
});
Instead of it going to a URL, how can I change it to send directly to a specific email address? I am using this contact form with a React app I've created.
So react component, class based.
class Foo extends Component {
popupQuestion() {
// implement method
}
sendEmail() = () => {
axios.post('/some/url', {
subject: 'mail',
to: 'someone#example.com',
body: 'something',
name: 'name'
})
.then(function (response) {
popupQuestion();
})
.catch(function (error) {
console.log(error);
return 'Error occurred. Please refresh page and try again.';
});
}
render() {
return(
<form onSubmit={this.sendEmail}>
// ...
</form>
);
}
}
And php method that will be executed on some/url
public function sendEmailAction(): bool
{
$request = // get request;
$subject = $request->get('subject');
$to = $request->get('to');
$body = $request->get('body');
$name = $request->get('name');
$transport = (new Swift_SmtpTransport('smtp.example.org', 25))
->setUsername('your username')
->setPassword('your password');
$mailer = new Swift_Mailer($transport);
$message = (new Swift_Message($subject))
->setFrom(['mymail#exmaple.com' => 'me'])
->setTo([$to => $name])
->setBody($body);
$sent = $mailer->send($message);
return $sent ? true : false;
}

Resources