I'm running application on my website and for some articles and reviews im using TinyMCE editor. Works perfectly, storing to DB also works.
But i have a need to add local images to articles and upon submitting a form i want to save them to dynamic location on server (based from where i save).
Can i even do this using expressJS and tinyMce editor?
I've tried setting needed stuff in tinymce.init method, I even have upload section when selecting Image in TinyMce UI, but as soon as i select image, i'm getting HTTP error 404. What i did to solve this, is to create POST route in express JS where i suppose i end up in, but i have no idea what i'm doing.
This is my TinyMCE init:
tinymce.init({
selector: '#tinySelector',
plugins: 'advlist autolink link code image lists charmap print preview textcolor media',
toolbar: 'undo redo | image code',
relative_urls: true,
document_base_url: 'http://www.example.com/',
images_upload_url: '/upload.php',
images_upload_hand: function (blobInfo, success, failure) {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', '/upload.php');
xhr.onload = function () {
var json;
if (xhr.status != 200) {
failure('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
if (!json || typeof json.location != 'string') {
failure('Invalid JSON: ' + xhr.responseText);
return;
}
success(json.location);
};
formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
xhr.send(formData);
}
});
Express route:
router.post('/upload.php', ensureAuthenticated, (req, res) => {
console.log('testis');
});
Php file:
<?php
/***************************************************
* Only these origins are allowed to upload images *
***************************************************/
$accepted_origins = array("http://localhost", "http://192.168.1.1", "http://example.com");
/*********************************************
* Change this line to set the upload folder *
*********************************************/
$imageFolder = "/public/tinyImages/";
reset ($_FILES);
$temp = current($_FILES);
if (is_uploaded_file($temp['tmp_name'])){
if (isset($_SERVER['HTTP_ORIGIN'])) {
// same-origin requests won't set an origin. If the origin is set, it must be valid.
if (in_array($_SERVER['HTTP_ORIGIN'], $accepted_origins)) {
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
} else {
header("HTTP/1.1 403 Origin Denied");
return;
}
}
/*
If your script needs to receive cookies, set images_upload_credentials : true in
the configuration and enable the following two headers.
*/
// header('Access-Control-Allow-Credentials: true');
// header('P3P: CP="There is no P3P policy."');
// Sanitize input
if (preg_match("/([^\w\s\d\-_~,;:\[\]\(\).])|([\.]{2,})/", $temp['name'])) {
header("HTTP/1.1 400 Invalid file name.");
return;
}
// Verify extension
if (!in_array(strtolower(pathinfo($temp['name'], PATHINFO_EXTENSION)), array("gif", "jpg", "png"))) {
header("HTTP/1.1 400 Invalid extension.");
return;
}
// Accept upload if there was no origin, or if it is an accepted origin
$filetowrite = $imageFolder . $temp['name'];
move_uploaded_file($temp['tmp_name'], $filetowrite);
// Respond to the successful upload with JSON.
// Use a location key to specify the path to the saved image resource.
// { location : '/your/uploaded/image/file'}
echo json_encode(array('location' => $filetowrite));
} else {
// Notify editor that the upload failed
header("HTTP/1.1 500 Server Error");
}
?>
I expect to select a file, see it in tinyMce editor, upon submitting saving image for start to some predefined path.
Related
So I am working on a blog CMS and I am trying give my TinyMCE WYSIWYG editor the ability to upload images when a user is creating a post. I'm using the actual code from the TinyMCE Docs (https://www.tiny.cloud/docs/general-configuration-guide/upload-images/), but when I attempt to upload an image, it gets stuck loading and gives an error "Uncaught SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse () at XMLHttpRequest.xhr.onload". Any ideas on what may be causing this? I've seen a few questions about this online, but they all seem to involve jQuery which I am not using. And sorry if this is too much of a question. Thanks for your time.
JavaScript (Top of body)
<script>
function example_image_upload_handler (blobInfo, success, failure, progress) {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', 'upload.php');
xhr.upload.onprogress = function (e) {
progress(e.loaded / e.total * 100);
};
xhr.onload = function() {
var json;
if (xhr.status === 403) {
failure('HTTP Error: ' + xhr.status, { remove: true });
return;
}
if (xhr.status < 200 || xhr.status >= 300) {
failure('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
if (!json || typeof json.location != 'string') {
failure('Invalid JSON: ' + xhr.responseText);
return;
}
success(json.location);
};
xhr.onerror = function () {
failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
};
formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
xhr.send(formData);
};
tinymce.init({
selector: '#tinymce',
height: 450,
plugins: [
'advlist autolink link image lists charmap print preview hr anchor pagebreak',
'searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking',
'table emoticons template paste help'
],
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | ' +
'bullist numlist outdent indent | link image | print preview media fullpage | ' +
'forecolor backcolor emoticons | help',
menu: {
favs: {title: 'My Favorites', items: 'code visualaid | searchreplace | emoticons'}
},
menubar: 'favs file edit view insert format tools table help',
content_css: 'css/content.css',
images_upload_handler: example_image_upload_handler
});
</script>
PHP (upload.php)
<?php
$accepted_origins = array("http://localhost");
$imageFolder = "post-img/";
reset ($_FILES);
$temp = current($_FILES);
if (is_uploaded_file($temp['tmp_name'])){
if (isset($_SERVER['HTTP_ORIGIN'])) {
// same-origin requests won't set an origin. If the origin is set, it must be valid.
if (in_array($_SERVER['HTTP_ORIGIN'], $accepted_origins)) {
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
} else {
header("HTTP/1.1 403 Origin Denied");
return;
}
}
/*
If your script needs to receive cookies, set images_upload_credentials : true in
the configuration and enable the following two headers.
*/
// header('Access-Control-Allow-Credentials: true');
// header('P3P: CP="There is no P3P policy."');
// Sanitize input
if (preg_match("/([^\w\s\d\-_~,;:\[\]\(\).])|([\.]{2,})/", $temp['name'])) {
header("HTTP/1.1 400 Invalid file name.");
return;
}
// Verify extension
if (!in_array(strtolower(pathinfo($temp['name'], PATHINFO_EXTENSION)), array("gif", "jpg", "png"))) {
header("HTTP/1.1 400 Invalid extension.");
return;
}
// Accept upload if there was no origin, or if it is an accepted origin
$filetowrite = $imageFolder . $temp['name'];
move_uploaded_file($temp['tmp_name'], $filetowrite);
// Respond to the successful upload with JSON.
// Use a location key to specify the path to the saved image resource.
// { location : '/your/uploaded/image/file'}
echo json_encode(array('location' => $filetowrite));
} else {
// Notify editor that the upload failed
header("HTTP/1.1 500 Server Error");
}
?>
Thanks for the replies... It turns out I just needed the JS, PHP, and the upload folder to all be in the same folder. Once I did that, it worked no problem. The code was good.
Your json is returning html which is start with < tag instead of json response there. May be server error occurred.
Using rails 6 and trying to upload files using this drag and drop JS framework to local disk but am getting "Failed to load resource: the server responded with a status of 404 (Not Found)". This is due to the url variable not being defined.
Console error: ActionController::RoutingError (No route matches [POST] "/undefined".
I've followed all the steps from here: https://edgeguides.rubyonrails.org/active_storage_overview.html.
JS Code:
import { DirectUpload } from "#rails/activestorage"
function uploadFile(file) {
const input = document.querySelector('input[type=file]')
console.log(input)
// your form needs the file_field direct_upload: true, which
// provides data-direct-upload-url
const url = input.dataset.directUploadUrl <-- returns "undefined"
console.log(url)
const upload = new DirectUpload(file, url)
upload.create((error, blob) => {
if (error) {
// Handle the error
} else {
// Add an appropriately-named hidden input to the form with a
// value of blob.signed_id so that the blob ids will be
// transmitted in the normal upload flow
const hiddenField = document.createElement('input')
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("value", blob.signed_id);
hiddenField.name = input.name
document.querySelector('form').appendChild(hiddenField)
}
})
HTML:
<div id="drop-area">
<form class="my-form">
<p>Upload multiple files with the file dialog or by dragging and dropping images onto the dashed region</p>
<input type="file" id="fileElem" multiple accept="image/*" onchange="handleFiles(this.files)" data-direct-upload = "true">
<label class="button" for="fileElem">Select some files</label>
</form>
<progress id="progress-bar" max=100 value=0></progress>
<div id="gallery" /></div>
</div>
<%= javascript_pack_tag 'dropzone.js' %>
I'm wondering if active storage doesn't like form data that isn't neatly packaged in embedded ruby code like so:
<%= form.file_field :attachments, multiple: true, direct_upload: true %>
Method 2:
If I try to send the file without using the active storage DirectUpload method I get "Failed to load resource: the server responded with a status of 400 (Bad Request)" with a console output of ActionController::ParameterMissing (param is missing or the value is empty: blob)
here's that JS code:
function uploadFile(file, i) {
var url2 = 'rails/active_storage/direct_uploads'
var xhr = new XMLHttpRequest()
var formData = new FormData()
xhr.open('POST', url2, true)
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
//Update progress (can be used to show progress indicator)
xhr.upload.addEventListener("progress", function(e) {
updateProgress(i, (e.loaded * 100.0 / e.total) || 100)
})
xhr.addEventListener('readystatechange', function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
updateProgress(i, 100) // <- Add this
}
else if (xhr.readyState == 4 && xhr.status != 200) {
// Error. Inform the user
}
})
formData.append('upload_preset', 'ujpu6gyk')
formData.append('file', file)
xhr.send(formData)
}
after some research into what URL to use, 'rails/active_storage/direct_uploads' seemed to get past the 404 message and instead throw 400. I can't believe this has been so difficult just to upload a file to local disk. Please help!
so I haven't exactly furthered my understanding of active storage, but I did find a solution that worked. In method 1, I simply changed the URL that was returning "undefined" to manually 'rails/active_storage/direct_uploads'.
The JS code now looks like:
function uploadFile(file) {
// const input = document.querySelector('input[type=file]')
// console.log(input)
// your form needs the file_field direct_upload: true, which
// provides data-direct-upload-url
const url = 'rails/active_storage/direct_uploads' //input.dataset.directUploadUrl
console.log(url)
const upload = new DirectUpload(file, url)
upload.create((error, blob) => {
if (error) {
// Handle the error
} else {
// Add an appropriately-named hidden input to the form with a
// value of blob.signed_id so that the blob ids will be
// transmitted in the normal upload flow
const hiddenField = document.createElement('input')
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("value", blob.signed_id);
hiddenField.name = input.name
document.querySelector('form').appendChild(hiddenField)
}
})
Once uploaded, I can do blob operations on the files like so (from within Ruby) without having to download them to temporary directories:
blob = ActiveStorage::Blob.first
blob.open do |tempfile|
puts tempfile.path #do some processing
puts blob.filename
end
I just ran into a similar issue, when you use the erb helper
<%= form.file_field :images, direct_upload: true %>
the generated input contains a data-direct-upload-url like so
<input data-direct-upload-url="http://localhost:3000/rails/active_storage/direct_uploads" type="file" name="post[images]" id="post_images">
In my case I had a few other file inputs already on the page and so the default query select from the active storage example
const input = document.querySelector('input[type=file]')
was returning another input without the direct upload url, resulting in a similar error as what you experienced.
I am trying to sign a huge video upload, because I want to upload it directly to S3. It works on localhost, but on my live site it fails to sign the request because of:
Mixed Content: The page at 'https://www.example.com/profile' was loaded
over HTTPS, but requested an insecure XMLHttpRequest endpoint
'http://www.example.com/sign_s3/?file_name=17mbvid.mp4&file_type=video/mp4'.
This request has been blocked; the content must be served over HTTPS.
I am hosting everything on heroku, every page is already using HTTPS and its not possible to open it in HTTP, because I redirect all traffic to HTTPS. I am using the letsencrypt SSL certificate.
So far I have no idea where to look, the only information I found, is that I need a valid SSL certificate, which I have.
Here is the JS function:
function getSignedRequest(file) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/sign_s3?file_name=" + file.name + "&file_type=" + file.type);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('got signed request');
var response = JSON.parse(xhr.responseText);
console.log(response);
console.log('uploadFile', response.url)
uploadFile(file, response.data, response.url);
} else {
console.log("Could not get signed URL.");
}
}
};
//console.log('send');
xhr.send();
}
Right after the error in the console I see this console log:
Could not get signed URL.
which means it fails here:
if (xhr.status === 200)
On the server:
#app.route('/sign_s3/', methods=["GET", "POST"])
#login_required
#check_confirmed
def sign_s3():
if "localhost" in request.url_root:
file_name = str(current_user.id) + "local-profil-video." + request.args.get('file_name').split(".")[-1]
else:
file_name = str(current_user.id) + "-profil-video." + request.args.get('file_name').split(".")[-1]
file_type = request.args.get('file_type')
session = boto3.Session(
aws_access_key_id=app.config['MY_AWS_ID'],
aws_secret_access_key=app.config['MY_AWS_SECRET'],
region_name='eu-central-1'
)
s3 = session.client('s3')
presigned_post = s3.generate_presigned_post(
Bucket = 'adultpatreon',
Key = 'videos/' + file_name,
Fields = {"acl": "public-read", "Content-Type": file_type},
Conditions = [
{"acl": "public-read"},
{"Content-Type": file_type}
],
ExpiresIn = 3600
)
if current_user.profile_video != None:
delete_file_from_aws("videos/", current_user.profile_video)
setattr(current_user, "profile_video", file_name)
db_session.commit()
return json.dumps({'data': presigned_post, 'url': 'https://s3.eu-central-1.amazonaws.com/mybucket/' + 'videos/' + file_name})
After many hours of researching I decided to rebuild this function and use AJAX get, which I am more familiar with. I also changed the way I pass/recieve the query string arguments to the best way, which is actually used in flask/python.
function getSignedRequest(file) {
$.ajax({
url : "/sign_s3/" + file.name + "/" + file.type,
type : "get",
success : function(response) {
console.log("success file up, follow", response);
var json_response = JSON.parse(response);
console.log(json_response);
uploadFile(file, json_response.data, json_response.url);
},
error : function(xhr) {
console.log("file up failed", xhr);
}
});
}
And on server side I changed how file.name and file.type are recieved:
# Sign request for direct file upload through client for video
#app.route('/sign_s3/<path:file_name_data>/<path:file_type_data>', methods=["GET", "POST"])
#login_required
#check_confirmed
def sign_s3(file_name_data, file_type_data):
#etc...
Now it works perfectly. I think they way I was recieving the query string arguments on the server was not correct, probably it would also work with the old getSignedRequest function (untested).
I am trying to download a file from an url (chrome drive file) in javascript; and want to send it to my backend (php - laravel).
var url = file.downloadUrl !== undefined ? file.webContentLink : file.exportLinks['application/pdf'];
console.log(url) // if I go to url, it downloads the file
if (url !== undefined) {
var remote = new XMLHttpRequest();
remote.open('GET', url);
remote.setRequestHeader('Authorization', 'Bearer ' + gapi.client.getToken().access_token);
remote.setRequestHeader('Access-Control-Allow-Origin', '*');
remote.onload = function(e) {
vm.handle_download(remote.responseText, file, 200); // do something with the fetched content;
};
remote.onerror = function(e) {
vm.handle_download('error response', null, remote.statusText);
};
remote.send();
} else vm.handle_download('no downloadable url', {
file: null,
status: 'error'
});
and on handle
handle_download: function (content, file, status) {
if (status !== 200) {
console.log('error occured on status')
return;
}
}
Failed to load https://drive.google.com/uc?id=1D12321ofd4CNG-m9_Mp4aiDcnibNf&export=download: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://test.dev' is therefore not allowed access.
This is an intended behavior due to same origin policy in the web. Since you're doing this for testing purposes, try this Allow-Control-Allow-Origin chrome extension.
You can read more about how to implement this in Using CORS tutorial.
This SO post may also offer additional insight.
I want to add feature to my tinyMCE 4.X. It is file uploader. I'm trying to do it many ways, but no one worked. I'm using this code:
tinymce.init({
selector: "textarea[name=obsah], textarea[name=perex]",
theme: "modern",
paste_data_images: true,
plugins: [
"advlist autolink lists link image charmap print preview hr anchor pagebreak",
"searchreplace wordcount visualblocks visualchars code fullscreen",
"insertdatetime media nonbreaking save table contextmenu directionality",
"emoticons template paste textcolor colorpicker textpattern"
],
toolbar1: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
toolbar2: "print preview media | forecolor backcolor emoticons",
image_title: true,
automatic_uploads: true,
images_upload_url: '/admin',
file_picker_types: 'image',
file_picker_callback: function(cb, value, meta) {
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.onchange = function() {
var file = this.files[0];
var id = 'blobid' + (new Date()).getTime();
var blobCache = tinymce.activeEditor.editorUpload.blobCache;
var blobInfo = blobCache.create(id, file);
blobCache.add(blobInfo);
cb(blobInfo.blobUri(), { title: file.name });
};
input.click();
},
});
After I choose the image, it shows in the area, that is OK, but when I click submit, $_POST and $_FILES are empty and console is talking about error in JSON unexpected error. Can you help me please? How to send multiple images ?
Thank you
You can use the following code to upload image using tinyMCE 4.x as suggested by its documentation.
https://www.tinymce.com/docs/configure/file-image-upload/
tinymce.init({
selector: 'textarea', // change this value according to your HTML
images_upload_handler: function (blobInfo, success, failure) {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', 'postAcceptor.php');
xhr.onload = function() {
var json;
if (xhr.status != 200) {
failure('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
if (!json || typeof json.location != 'string') {
failure('Invalid JSON: ' + xhr.responseText);
return;
}
success(json.location);
};
formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
xhr.send(formData);
}
});
To use this code you just need to create postAcceptor.php file pn your server. Here is the link of postAcceptor.php
https://www.tinymce.com/docs/advanced/php-upload-handler/
<?php
/*******************************************************
* Only these origins will be allowed to upload images *
******************************************************/
$accepted_origins = array("http://localhost", "http://192.168.1.1", "http://example.com");
/*********************************************
* Change this line to set the upload folder *
*********************************************/
$imageFolder = "images/";
reset ($_FILES);
$temp = current($_FILES);
if (is_uploaded_file($temp['tmp_name'])){
if (isset($_SERVER['HTTP_ORIGIN'])) {
// same-origin requests won't set an origin. If the origin is set, it must be valid.
if (in_array($_SERVER['HTTP_ORIGIN'], $accepted_origins)) {
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
} else {
header("HTTP/1.0 403 Origin Denied");
return;
}
}
/*
If your script needs to receive cookies, set images_upload_credentials : true in
the configuration and enable the following two headers.
*/
// header('Access-Control-Allow-Credentials: true');
// header('P3P: CP="There is no P3P policy."');
// Sanitize input
if (preg_match("/([^\w\s\d\-_~,;:\[\]\(\).])|([\.]{2,})/", $temp['name'])) {
header("HTTP/1.0 500 Invalid file name.");
return;
}
// Verify extension
if (!in_array(strtolower(pathinfo($temp['name'], PATHINFO_EXTENSION)), array("gif", "jpg", "png"))) {
header("HTTP/1.0 500 Invalid extension.");
return;
}
// Accept upload if there was no origin, or if it is an accepted origin
$filetowrite = $imageFolder . $temp['name'];
move_uploaded_file($temp['tmp_name'], $filetowrite);
// Respond to the successful upload with JSON.
// Use a location key to specify the path to the saved image resource.
// { location : '/your/uploaded/image/file'}
echo json_encode(array('location' => $filetowrite));
} else {
// Notify editor that the upload failed
header("HTTP/1.0 500 Server Error");
}
?>