How to conciliate TinyMCE FileUpload and CakePHP - cakephp

I am using TinyMCE for my views add() and edit() of my PagesController.
Following TinyMCE documentation, I am calling TinyMCE in my js file as below :
tinymce.init({
selector: '.tinymce',
menubar: false,
plugins: 'lists link image',
toolbar: 'insertfile undo redo | styleselect | bold italic underline | alignleft aligncenter alignright | bullist numlist | link image',
branding: false,
image_caption: true,
image_class_list: [
{title: 'img-responsive', value: 'img-responsive'},
],
image_title: true,
automatic_uploads: true,
mobile: {
menubar: false,
toolbar: [ 'undo', 'bold', 'italic', 'styleselect' ]
},
images_upload_handler: function (blobInfo, success, failure) {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', '/app/webroot/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);
}
});
As you can see I am calling manually the file PostAcceptor.php.
This is working but I am conscious that this is not the optimal way to proceed.
Do I have to include the image_upload_handler directly in my views add() and edit() and create a new controller fileupload() instead of the postAcceptor.php file ?
I don't really know how to proceed properly. Also do you know if I can use getClientFilename() and moveTo() instead of postAcceptor content ?
postAcceptor.php
/***************************************************
* Only these origins are allowed to upload images *
***************************************************/
$accepted_origins = [
"http://localhost",
"http://192.168.1.1",
];
/*********************************************
* Change this line to set the upload folder *
*********************************************/
$imageFolder = "img/";
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;
}
}
// Don't attempt to process the upload on an OPTIONS request
if ($_SERVER["REQUEST_METHOD"] == "OPTIONS") {
header("Access-Control-Allow-Methods: POST, OPTIONS");
return;
}
reset($_FILES);
$temp = current($_FILES);
if (is_uploaded_file($temp["tmp_name"])) {
/*
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)), [
"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);
// Determine the base URL
$protocol =
isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on"
? "https://"
: "http://";
$baseurl =
$protocol .
$_SERVER["HTTP_HOST"] .
rtrim(dirname($_SERVER["REQUEST_URI"]), "/") .
"/";
// 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(["location" => $baseurl . $filetowrite]);
} else {
// Notify editor that the upload failed
header("HTTP/1.1 500 Server Error");
}
Thank you !

Related

CSRF token from either the request body or request headers did not match or is missing - cake php 4 [duplicate]

I have a project in Cakephp 3.6 in which 3 actions in MessageController are called by Ajax. I have a problem, however, when I send a request to one of the action, XHR returns to me this:
{
"message": "CSRF token mismatch.",
"url": "\/messages\/changepriority\/8",
"code": 403,
"file": "D:\\xampp\\htdocs\\myapp\\vendor\\cakephp\\cakephp\\src\\Http\\Middleware\\CsrfProtectionMiddleware.php",
"line": 195
}
This is one of the action what I try to call from Ajax:
public function changepriority($id=null)
{
$this->autoRender = false;
$message = $this->Messages->get($id);
$message->priority = ($message->priority === false) ? true : false;
if ($this->Messages->save($message)) {
echo json_encode($message);
}
}
And this is my ajax:
$(".email-star").click(function(){
var idmessage = this.id;
$.ajax({
headers : {
'X-CSRF-Token': $('[name="_csrfToken"]').val()
},
dataType: "json",
type: "POST",
evalScripts: true,
async:true,
url: '<?php echo Router::url(array('controller'=>'Messages','action'=>'changepriority'));?>' +'/'+idmessage,
success: function(data){
if(data['priority'] === false) {
$("#imp_" + idmessage).removeClass("fas").removeClass('full-star').addClass( "far" );
}
else {
$("#imp_" + idmessage).removeClass("far").addClass( "fas" ).addClass("full-star");
}
}
});
});
I have read the documentation about Cross Site Request Forgery, and I tried to turn off the Csrf for these action first with:
public function beforeFilter(Event $event)
{
$this->getEventManager()->off($this->Csrf);
}
and then with:
public function beforeFilter(Event $event)
{
$this->Security->setConfig('unlockedActions', ['index', 'changepriority']);
}
But nothing. The Xhr return always the CSRF token mismatch.
What can I do ?
Edit:
I change the action in this way:
public function changepriority($id=null)
{
$this->autoRender = false;
$message = $this->Messages->get($id);
$message->priority = ($message->priority === false) ? true : false;
if ($this->Messages->save($message)) {
$content = json_encode($message);
$this->response->getBody()->write($content);
$this->response = $this->response->withType('json');
return $this->response;
}
}
In that way the action works. Can it be like that?
First check your $('[name="_csrfToken"]').val() output.
If you didn't get any output, need to check csrfToken hidden field is exist or not. Just right click in your page and click View Page Source
If not exist, you don't follow proper way when you create Form. Basically, when forms are created with the Cake\View\Helper\FormHelper, a hidden field is added containing the CSRF token.
If everything is correct, add the following line inside your ajax call after header
beforeSend: function (xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('[name="_csrfToken"]').val());
},
Ps. Disabling the CSRF is not recommended by cakePHP and most of the developer aware of this. Hope this help.
beforeSend: function (xhr) {
xhr.setRequestHeader('X-CSRF-Token', <?= json_encode($this->request->getAttribute('csrfToken')) ?>);
},

get selected file in CkEditor & add custom upload button

i am using ngx-ckeditor: "0.4.0" in angular 5.
i want upload image & add custom upload button
below is my html.
<ck-editor
#ckeditor
name="html_template"
[(ngModel)]="mailModel.html_template"
[config]="ckEditorConfig">
</ck-editor>
here is my component code.
this.ckEditorConfig = {
filebrowserBrowseUrl : '/application/crm/distribution-list/create-mail',
filebrowserUploadUrl : 'http://192.168.0.107:8000/api/crm/v1.0/crm-distribution-library-files',
fileTools_requestHeaders :{
'X-Requested-With': 'XMLHttpRequest',
Authorization: 'Bearer ' + localStorage.getItem('access_token')
},
filebrowserUploadMethod : 'xhr',
removeButtons: 'Forms,Iframe,Blocks,Subscript,Superscript,Maximize,Undo',
};
with this code i am not able to get image & can't pass my custom header.
i want to get selected image & add custom 'Upload Image' button.
below is the code for add custom button in CkEditor
#ViewChild('ckeditor') ckeditor: CKEditorComponent;
ngAfterViewInit(): void {
this._addImageUploadBtn();
}
_addImageUploadBtn() {
const editor = this.ckeditor && this.ckeditor.instance;
if (!editor) {
return;
}
var that = this;
editor.ui.addButton('uploadImage', {
icon: 'https://img.icons8.com/ios/50/000000/image.png',
label: 'Upload Image',
command: 'uploadImage',
toolbar: 'insert'
});
editor.addCommand('uploadImage', {
exec: function(editor: any) {
// Remove img input.
[].slice.apply(document.querySelectorAll('.ck-editor-upload-img')).forEach((img: any) => {
img.remove();
});
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('class', 'ck-editor-upload-img');
input.style.display = 'none';
input.addEventListener('change', e => {
const file = (e.target as HTMLInputElement).files[0];
if (file) {
console.log(file);
// Do Stuff
}
},
false
);
document.body.appendChild(input);
input.click();
}
});
}
here you get the selected image file, and also get custom button click.

evaporate.js s3 direct uploads 403

I am attempting to upload files directly to the browser to S3 using evaporate js.
I followed the tutorial at jqueryajaxphp.com but I am having a problem with the signature
Signature.php
<?php
$to_sign = $_GET['to_sign'];
$secret = 'AWS_SECRET';
$hmac_sha1 = hash_hmac('sha1', $to_sign, $secret, true);
$signature = base64_encode($hmac_sha1);
echo $signature;
Upload function
function largeFileUPload(file) {
var ins = new Evaporate({
signerUrl: './includes/s3-signature.php',
aws_key: "AWS_KEY_XXXXXXX",
bucket: 'bucket-name',
awsRegion: 'eu-west-1',
cloudfront: true,
aws_url: 'http://bucket-name.s3-accelerate.amazonaws.com',
// partSize: 10 * 1024 * 1024,
s3Acceleration: true,
computeContentMd5: true,
cryptoMd5Method: function (data) { return AWS.util.crypto.md5(data, 'base64'); },
cryptoHexEncodedHash256: function (data) { return AWS.util.crypto.sha256(data, 'hex'); }
});
// http://<?=$my_bucket?>.s3-<?=$region?>.amazonaws.com
ins.add({
name: 'evaporateTest' + Math.floor(1000000000*Math.random()) + '.' + file.name.replace(/^.*\./, ''),
file: file,
xAmzHeadersAtInitiate : {
'x-amz-acl': 'public-read'
},
signParams: {
foo: 'bar'
},
complete: function(r){
console.log('Upload complete');
},
progress: function(progress){
var progress = Math.floor(progress*100);
console.log(progress);
},
error: function(msg){
console.log(msg);
}
});
}
I am getting to following response from from the s3 end point
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
I have also tried the blueimp plugin but this fails with files over 200MB
The Signature.php file form the tutorial from jqueryajaxphp.com was causing the problem. I altered the php signature file form the evaporate.js docs and it solved the problem
Signature.php
$to_sign = $_GET['to_sign'];
$secret = 'AWS_SECRET';
$formattedDate = substr($dateTime, 0, 8);
//make the Signature, notice that we use env for saving AWS keys and regions
$kSecret = "AWS4" . $secret;
$kDate = hash_hmac("sha256", $formattedDate, $kSecret, true);
$kRegion = hash_hmac("sha256", "eu-west-1", $kDate, true);
$kService = hash_hmac("sha256", 's3', $kRegion, true);
$kSigning = hash_hmac("sha256", "aws4_request", $kService, true);
$signature = hash_hmac("sha256", $to_sign, $kSigning);
echo $signature;

Download File in Angular 2 (Download Pop-up not coming)

I have a view button and on click I am calling Web API to download the word document file.
WebAPI is working fine, when I paste the download URL in browser (for example http://localhost:50963/api/Download/1022), browser is showing a pop-up to save/cancel.
I wanted to have the same behavior, i.e. when user click on View button, I need to show above download pop-up. API is getting called successfully, see below screenshot
download.service.ts
export class DownloadService {
constructor(private http: Http) {}
private downloadUrl = 'http://localhost:50963/api/Download/';
//Fetch all existing Templates
DownloadDocument(Doc_Id: number){
return this.http.get(this.downloadUrl + Doc_Id.toString())
}
}
document-list.component.ts
DownloadArticle(Doc: ArticleModel){
console.log("inside downloadarticle()",Doc.Doc_Id);
this.downloadservice.DownloadDocument(Doc.Doc_Id)
.subscribe(
err => {
console.log(err);
});
}
You need to do some workaround here. Angular 'http' service cannot give what you want. I pasted all my workable code here. You need to pick the part of your need.
retrieveJquery(fileId: number, fileName: string): void {
let that = this;
let useBase64 = false;
let iOS = false;
if (navigator.platform)
iOS = /iPad|iPhone|iPod/.test(navigator.platform);
else if (navigator.userAgent)
iOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
let android = false;
if (navigator.platform)
android = /android/.test(navigator.platform);
else if (navigator.userAgent)
android = /android/.test(navigator.userAgent);
//useBase64 = iOS;
if (iOS || android) {
window.open('cloud/api/file/retrieve?fileId=' + fileId + '&base64=-1&_access_token=' + this.utilsSvc.getToken(), '_blank');
}
else {
$.ajax({
type: "GET",
headers: { 'Authorization': this.utilsSvc.getToken() },
url: 'cloud/api/file/retrieve',
data: {
'fileId': fileId,
'base64': useBase64 ? '1' : '0'
}
,
xhrFields: {
responseType: 'blob'
}
}).fail(function (jqXHR, textStatus) {
if (jqXHR.status === 501)
alert('Please configure service url first.');
else if (jqXHR.status === 404)
alert('File not found');
else
alert('Retrieving file failed: ' + textStatus + " : " + jqXHR.responseText);
}).done(function (data) {
if (useBase64)
window.open('data:' + that.utilsSvc.getMimeType(fileName) + ';base64, ' + data, '_blank');
else {
let blob = new Blob([data]);
if (window.navigator && window.navigator.msSaveOrOpenBlob)
window.navigator.msSaveOrOpenBlob(blob, fileName);
else {
let link = document.createElement('a');
link.target = '_blank';
link.href = window.URL.createObjectURL(blob);
link.setAttribute("download", fileName);
link.click();
}
}
});
}
}

Loading content dynamically (panels) in an Ext Js Viewport

Well basically im looking on this problem, i have many components with dinamic stuff that is written in the server side with PHP.
Depending on the user my components will change, based on the role of the user.
So i need to know any ways/examples/info on how to do this.
1- I used the load function EXTJS has, but it clearly says i wont load script only plain text.
2- i used eval() but im a bit scared o this approach, like this example crate layout component (static)
var contentPanel = new Ext.Panel({
frame: true,
style: {marginTop: '10px'},
height: 315,
border: true,
bodyBorder: false,
layout: 'fit',
id: 'contentPanel'
});
var mainPanel = new Ext.Panel({
title: 'Panel Principal',
id: 'mainPanel',
border: true,
frame: true,
width: '50%',
style: {margin: '50px auto 0 auto'},
height: 400,
renderTo: Ext.getBody(),
items: [
{
html: 'Panel 1'
},
{
html: 'Panel 2'
},
contentPanel
]
})
and update the content of the layout with js files written on the server
function receiveContent(options, success, response)
{
var respuesta = response.responseText;
//console.log(respuesta);
eval(respuesta);
//console.log(options.url);
url = options.url;
url = url.substring(0,(url.search(/(\.)/)));
var contenedor = Ext.getCmp('contentPanel');
contenedor.removeAll();
var contenido = Ext.getCmp(url);
contenedor.add(contenido);
contenedor.doLayout();
}
function requestContent(panel)
{
//panel es el nombre del archivo que quiero
Ext.Ajax.request({
url: panel+'.js',
callback: receiveContent
});
}
any other way for this to be done, what i DONT want to do is making a million different components and load them ALL at login time like many people seem to say
To address your questions:
The .load method WILL load script and evaluate it once the content has finished loading, however to accomplish this you will need to set the scripts:true option, an example may be:
my_panel.load({
url: 'url_to_load.php/hmt/html/asp...',
params: {param1: param1value, param2: param2value...etc},
nocache: true,
timeout: 30,
scripts: true
});
Using eval() is fine...but seeing as the scripts:true config option above accomplishes this for javascript in the source file, you shouldnt need to use this.
Hope this helps
You might load JavaScript dynamically using something like like below - there are a hundred variations on the web. In this way, you would avoid the AJAX call and handling the response (and subsequent eval).
var aHeadNode = document.getElementById('head')[0];
var aScript = document.createElement('script');
aScript.type = 'text/javascript';
aScript.src = "someFile.js";
aHeadNode.appendChild(oScript);
What I understood from your question is that, you are looking for dynamic JS file loader with a callback handler i.e. the callback function will be called only when the file is loaded fully. I also faced similar problems at start and after searching a lot and doing some research, I developed the following code, it provides absolute Dynamic JS and CSS file loading functionality :
Class ScriptLoader: (Put it in a separate file and load it at first)
ScriptLoader = function() {
this.timeout = 30;
this.scripts = [];
this.disableCaching = false;
};
ScriptLoader.prototype = {
processSuccess : function(response) {
this.scripts[response.argument.url] = true;
window.execScript ? window.execScript(response.responseText) : window
.eval(response.responseText);
if (response.argument.options.scripts.length == 0) {
}
if (typeof response.argument.callback == 'function') {
response.argument.callback.call(response.argument.scope);
}
},
processFailure : function(response) {
Ext.MessageBox.show({
title : 'Application Error',
msg : 'Script library could not be loaded.',
closable : false,
icon : Ext.MessageBox.ERROR,
minWidth : 200
});
setTimeout(function() {
Ext.MessageBox.hide();
}, 3000);
},
load : function(url, callback) {
var cfg, callerScope;
if (typeof url == 'object') { // must be config object
cfg = url;
url = cfg.url;
callback = callback || cfg.callback;
callerScope = cfg.scope;
if (typeof cfg.timeout != 'undefined') {
this.timeout = cfg.timeout;
}
if (typeof cfg.disableCaching != 'undefined') {
this.disableCaching = cfg.disableCaching;
}
}
if (this.scripts[url]) {
if (typeof callback == 'function') {
callback.call(callerScope || window);
}
return null;
}
Ext.Ajax.request({
url : url,
success : this.processSuccess,
failure : this.processFailure,
scope : this,
timeout : (this.timeout * 1000),
disableCaching : this.disableCaching,
argument : {
'url' : url,
'scope' : callerScope || window,
'callback' : callback,
'options' : cfg
}
});
}
};
ScriptLoaderMgr = function() {
this.loader = new ScriptLoader();
this.load = function(o) {
if (!Ext.isArray(o.scripts)) {
o.scripts = [o.scripts];
}
o.url = o.scripts.shift();
if (o.scripts.length == 0) {
this.loader.load(o);
} else {
o.scope = this;
this.loader.load(o, function() {
this.load(o);
});
}
};
this.loadCss = function(scripts) {
var id = '';
var file;
if (!Ext.isArray(scripts)) {
scripts = [scripts];
}
for (var i = 0; i < scripts.length; i++) {
file = scripts[i];
id = '' + Math.floor(Math.random() * 100);
Ext.util.CSS.createStyleSheet('', id);
Ext.util.CSS.swapStyleSheet(id, file);
}
};
this.addAsScript = function(o) {
var count = 0;
var script;
var files = o.scripts;
if (!Ext.isArray(files)) {
files = [files];
}
var head = document.getElementsByTagName('head')[0];
Ext.each(files, function(file) {
script = document.createElement('script');
script.type = 'text/javascript';
if (Ext.isFunction(o.callback)) {
script.onload = function() {
count++;
if (count == files.length) {
o.callback.call();
}
}
}
script.src = file;
head.appendChild(script);
});
}
};
ScriptMgr = new ScriptLoaderMgr();
Now it can be used this way:
For CSS files loading :
ScriptMgr.loadCss([first.css', 'second.css']);
That is you just need to provide css files path in an array and pass that array to loadCss() function as an argument. No callback is required for CSS files.
For JS file loading :
ScriptMgr.load({
scripts : ['lib/jquery-1.4.2.min.js','lib/jquery.touch-gallery-1.0.0.min.js'],
callback : function() {
//Here you will do those staff needed after the files get loaded
},
scope : this
});
In this case, the same way you entered CSS files, here you just need to put that array of JS files in scripts option. The callback function is called only when all the JS files are loaded successfully. Also, if in any case, the JS files are already loaded in the browser (i.e. already this code is run once), then the control will automatically go to the callback function.

Resources