Using ElFinder on Symfony: I can't select images - symfony

I want to use ElFinder as my file manager in my Symfony project. I followed the doc on GitHub. In my routes.yaml:
elfinder:
resource: '#FMElfinderBundle/Resources/config/routing.yaml'
security.yaml
- { path: ^/efconnect, role: [ROLE_USER] }
- { path: ^/elfinder, role: [ROLE_USER] }
And finally on fm_elfinder.yaml
fm_elfinder:
instances:
default:
locale: '%locale%' # defaults to current request locale
editor: ckeditor # other options are tinymce, tinymce4, fm_tinymce, form, simple, custom
connector:
roots:
uploads:
driver: LocalFileSystem
path: uploads
upload_max_size: 2M
Then I added it on a Admin element on Sonata, like this:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Contenu')
->add('published', CheckboxType::class, ['required' => false, 'label' => 'Publier'])
->add('title', TextType::class, ['required' => true, 'label' => 'Titre'])
->add('textLink', TextType::class, ['required' => true, 'label' => 'Texte du lien'])
->add('media', ElFinderType::class, array(
'label' => 'Photo',
'enable' => true,
'required' => true,
'instance' => 'default',
'attr' => array('class' => 'form-control')
)
)
->end();
}
Then I go on Sonata Admin, and when I try to add an image, a window open, I added an jpeg but then when i click on it, nothing seems to happen. Like, I can select it but my window stay open and my field doesn't fill up with the name of the image.
Thanks for your help.

Ok I got it to work on my easyadmin forms. This is the easyAdmin 2 configuration :
easy_admin:
entities:
BlogPost:
class: App\Entity\BlogPost
form:
fields:
- { property: 'title', label: 'page.title' }
- { property: 'image', type: 'App\Form\ElFinderType', label: 'blog.image.file', type_options: { enable: true, instance: 'single' } }
(I copied the vendor/helios-ag/fm-elfinder-bundle/src/Form/Type/ElFinderType.php file to App\Form\ElFinderType.php. This is not required).
I set the fm_elfinder connector to simple :
# src/packages/fm_elfinder.yaml
fm_elfinder:
instances:
single:
locale: fr
editor: simple
theme: smoothness
relative_path: false
connector:
roots:
uploads:
driver: LocalFileSystem
path: uploads/images
show_hidden: false
upload_allow: [ 'image/png', 'image/jpg', 'image/jpeg' ]
upload_deny: [ 'all' ]
upload_max_size: 2M
Next I copied two of the template files from the bundle to my templates\bundles directory :
{# templates/bundles/FMElfinderBundle/Form/elfinder_widget.html.twig #}
{% block elfinder_widget %}
<div class="buttons">
<input type="button" {{ block('widget_attributes') }}
data-type="elfinder-remove-button" value="Delete" class="{% if value is empty %}hide{% endif %} remove-button"/>
<input type="button" {{ block('widget_attributes') }}
data-type="elfinder-add-button" value="Add" class="{% if value is not empty %}hide{% endif %} add-button"/>
</div>
{% if value is not empty %}
<img src="{{ value }}" alt="">
{% endif %}
<input type="text" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}
data-type="elfinder-input-field" class="hide"/>
{% if enable and instance is defined %}
<script type="text/javascript" charset="utf-8">
const addButtonSelector = '[data-type="elfinder-add-button"][id="{{ id }}"]';
const removeButtonSelector = '[data-type="elfinder-remove-button"][id="{{ id }}"]';
const inputSelector = '[data-type="elfinder-input-field"][id="{{ id }}"]';
live('click', addButtonSelector, () => {
window.open("{{ path('elfinder', {'instance': instance, 'homeFolder': homeFolder }) }}?id={{ id }}", "popupWindow", "height=450, width=900");
});
removeButtonListener()
function live(eventType, elementQuerySelector, cb) {
document.addEventListener(eventType, function (event) {
const qs = document.querySelectorAll(elementQuerySelector);
if (qs) {
let el = event.target, index = -1;
while (el && ((index = Array.prototype.indexOf.call(qs, el)) === -1)) {
el = el.parentElement;
}
if (index > -1) {
cb.call(el, event);
}
}
})
}
function setValue(value) {
document.querySelector(inputSelector).value = value;
}
function displayImage(value) {
const inputElement = document.querySelector(inputSelector);
const parent = inputElement.parentElement
const image = document.createElement("img");
image.src = value;
parent.append(image)
const removeButton = document.querySelector(removeButtonSelector);
const addButton = document.querySelector(addButtonSelector);
removeButton.classList.remove('hide')
addButton.classList.add('hide')
removeButtonListener()
}
function removeButtonListener() {
const removeButtonElement = document.querySelector(removeButtonSelector);
if(removeButtonElement){
removeButtonElement.addEventListener('click', () => {
const addButtonElement = document.querySelector(addButtonSelector);
removeButtonElement.classList.remove('hide')
const parent = removeButtonElement.closest('.form-widget')
const inputElement = parent.querySelector('[data-type="elfinder-input-field"]');
inputElement.value = null
const imageElement = parent.getElementsByTagName('img')[0]
if (imageElement) {
parent.removeChild(imageElement)
}
removeButtonElement.classList.add('hide')
addButtonElement.classList.remove('hide')
document.querySelector(inputSelector).value = null;
})
}
}
</script>
{% endif %}
{% endblock %}
This first one contains the fields and the JavaScript needed to fill them in. I added a Add button. This will open the elfinder, once an image gets selected this button becomes hidden and a Remove button will be added.
Next is the ElFinder window :
{# templates/bundles/FMElfinderBundle/Elfinder/simple.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<script data-main="{{ path('ef_main_js') }}"
src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script>
function getUrlParam(paramName) {
const reParam = new RegExp('(?:[\?&]|&)' + paramName + '=([^&]+)', 'i');
const match = window.location.search.match(reParam);
return (match && match.length > 1) ? match[1] : '' ;
}
define('elFinderConfig', {
// elFinder options (REQUIRED)
// Documentation for client options:
// https://github.com/Studio-42/elFinder/wiki/Client-configuration-options
defaultOpts: {
url: '{{ path('ef_connect', { 'instance': instance, 'homeFolder': homeFolder } ) }}',
lang: '{{ locale }}',
onlyMimes: {{ onlyMimes|raw }},
getFileCallback: function (file) {
window.opener.setValue(file.url);
window.opener.displayImage(file.url);
window.close();
},
commandsOptions: {
edit: {
extraOptions: {
// set API key to enable Creative Cloud image editor
// see https://console.adobe.io/
creativeCloudApiKey: '',
// browsing manager URL for CKEditor, TinyMCE
// uses self location with the empty value
managerUrl: ''
}
},
quicklook: {
// to enable CAD-Files and 3D-Models preview with sharecad.org
sharecadMimes: ['image/vnd.dwg', 'image/vnd.dxf', 'model/vnd.dwf', 'application/vnd.hp-hpgl', 'application/plt', 'application/step', 'model/iges', 'application/vnd.ms-pki.stl', 'application/sat', 'image/cgm', 'application/x-msmetafile'],
// to enable preview with Google Docs Viewer
googleDocsMimes: ['application/pdf', 'image/tiff', 'application/vnd.ms-office', 'application/msword', 'application/vnd.ms-word', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/postscript', 'application/rtf'],
// to enable preview with Microsoft Office Online Viewer
// these MIME types override "googleDocsMimes"
officeOnlineMimes: ['application/vnd.ms-office', 'application/msword', 'application/vnd.ms-word', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation']
}
},
// bootCallback calls at before elFinder boot up
bootCallback: function (fm, extraObj) {
/* any bind functions etc. */
fm.bind('init', function () {
});
// for example set document.title dynamically.
var title = document.title;
fm.bind('open', function () {
var path = '',
cwd = fm.cwd();
if (cwd) {
path = fm.path(cwd.hash) || null;
}
document.title = path ? path + ':' + title : title;
}).bind('destroy', function () {
document.title = title;
});
}
},
managers: {
// 'DOM Element ID': { /* elFinder options of this DOM Element */ }
'elfinder': {}
}
});
</script>
</head>
<body>
<!-- Element where elFinder will be created (REQUIRED) -->
<div id="elfinder"></div>
</body>
</html>
The main thing added here to make it work is the getFileCallback function :
getFileCallback: function (file) {
window.opener.setValue(file.url);
window.opener.displayImage(file.url);
window.close();
},
This now works fine for my single image field and does not affect the CKEditor integration.

Related

Adding Custom Web Assets for FullCalendar (Symfony / EasyAdmin)

Symfony : 6.1.4
EasyAdmin : 4.3.5
According to the documentation for adding Custom Web Assets :
Use the configureAssets() method in the dashboard and/or the CRUD controllers to add your own CSS and JavaScript files
So I want to add the FullCalendar assets.
class DashboardController extends AbstractDashboardController
{
//...
public function configureAssets(): Assets
{
return parent::configureAssets()
->addWebpackEncoreEntry('admin')
->addCssFile('https://cdn.jsdelivr.net/npm/fullcalendar#5.11.3/main.min.css')
->addJsFile('https://cdn.jsdelivr.net/npm/fullcalendar#5.11.3/main.min.js');
}
//...
#[Route('/admin', name: 'admin')]
public function index(): Response
{
return $this->render('admin/index.html.twig');
}
}
admin/index.html.twig :
// ...
{% block main %}
<div id="booking"></div>
{% endblock %}
{% block javascripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
var calendarEl = document.getElementById('booking');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
locale: 'fr',
buttonText: {
today: 'Aujourd\'hui',
month: 'Mois',
week: 'Semaine'
},
timeZone: 'Europe/Paris',
headerToolbar: {
start: 'prev,next today',
center: 'title',
end: 'dayGridMonth,timeGridWeek'
},
events: {{ data|raw }},
navLinks: true
});
calendar.render();
});
</script>
{% endblock %}
But the calendar is not displayed.
No errors in my browser console.

xhr.send is not sending data in symfony how do i fix it?

I'm having a problem with xhr.send(JSON.stringify(donnees)) , im trying to edit with calendar but it's not sending data.
here is my code.
this is the twig part :`
{% extends 'base-back.html.twig' %}
{% block body %}
<br>
{% block title %}<center><h1>Vols Calendar</h1></center>{% endblock %}
<br><br><br>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar#5.6.0/main.min.js" integrity="sha256-ekrJn2FeZaiUFq99QswpQCUTME/HxaDfX7R8INzKJGE=" crossorigin="anonymous"></script>
<div id="calendrier">
<script src="{{ asset('assets/js/fullcalendar.js') }}"></script>
</div>
{% block stylesheet %}
<style>
#calendrier{
width: 600px;
margin: auto;
height:60%;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar#5.6.0/main.min.css" integrity="sha256-uq9PNlMzB+1h01Ij9cx7zeE2OR2pLAfRw3uUUOOPKdA=" crossorigin="anonymous">
{% endblock %}
{% block javascript %}
<script>
window.onload = () =>{
let calendarElt=document.querySelector("#calendrier");
let calendar =new FullCalendar.Calendar(calendarElt,{
initialView: 'dayGridMonth',
locale: 'fr',
timeZone: 'Afrique/Tunisie',
headerToolbar: {
start: 'prev,next today',
center: 'title',
end: 'dayGridMonth'
},
events: {{data|raw}},
//can edit
editable: true,
//can make it 2 days for example
eventResizableFromStart: true
});
//get an object when you move an event
calendar.on('eventChange', (e)=>{
let url =`/vols/api/${e.event.id}/edit`
let donnees={
"destination_aller":e.event.extendedProps.destination_aller,
"destination_retour":e.event.extendedProps.destination_retour,
"title":e.event.title,
"start":e.event.start,
"date_retour":e.event.extendedProps.date_retour,
"passagers":e.event.extendedProps.passagers,
"cabine":e.event.extendedProps.cabine,
}
console.log(donnees);
let xhr = new XMLHttpRequest()
xhr.open("PUT", url)
xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");
xhr.send(JSON.stringify(donnees))
})
calendar.render();
}
</script>
{% endblock %}{% endblock %}
and this is my controller :
/**
* #Route("/calendar", name="calendar")
*/
public function calendar()
{
$repository = $this->getDoctrine()->getRepository(vols::class);
$event = $repository->findAll();
$rdvs=[];
foreach($event as $event){
$rdvs[]=[
'id' => $event->getId(),
'destination_aller' => $event->getDestinationAller(),
'destination_retour'=>$event->getDestinationRetour(),
'title' => $event->getVoyage(),
'start' => $event->getDateDepart()->format('Y-m-d'),
'date_retour' => $event->getDateRetour(),
'passagers' => $event-> getPassagers(),
'cabine'=>$event->getCabine(),
];
}
$data= json_encode($rdvs);
return $this->render('vols/Callender.html.twig', compact('data'));
}
/**
* #Route("/api/{id}/edit", name="editCalendar", methods={"PUT"})
*/
public function editCalendar(vols $calendar, Request $request) {
//récuperation de données
$donnees=json_decode($request->getContent());
if(
isset($donnees->destination_aller) && !empty($donnees->destination_aller) &&
isset($donnees->destination_retour) && !empty($donnees->destination_retour) &&
isset($donnees->title) && !empty($donnees->title) &&
isset($donnees->start) && !empty($donnees->start) &&
isset($donnees->date_retour) && !empty($donnees->date_retour) &&
isset($donnees->passagers) && !empty($donnees->passagers) &&
isset($donnees->cabine) && !empty($donnees->cabine)
){
//données completes, on initialise un code
$code = 200;
//verification de l'id existe
if(!$calendar){
//on instancie un rende
$calendar = new vols;
//change code
$code=201;
}
//on hydrate l'objet avec les données
$calendar->setDestinationAller($donnees->destination_aller);
$calendar->setDestinationRetour($donnees->destination_retour);
$calendar->setVoyage($donnees->title);
$calendar->setDateDepart(new DateTime($donnees->start));
$calendar->setDateRetour(new DateTime($donnees->date_retour));
$calendar->setPassagers($donnees->passagers);
$calendar->setCabine($donnees->cabine);
$em=$this->getDoctrine()->getManager();
$em->persist($calendar);
$em->flush();
//return code
return new Response('Ok', $code);
}else{
//données non completes
return new Response('data not complete', 404);
}
return $this->render('vols/calenderEdit.html.twig');
}
my data is sent but i can't edit it
thank you.

Cannot read properties of undefined (reading 'toUpperCase') with select2 and Pugx

For an autocomplete with symfony, I use the bundle PUGX but using select2 version 3.5.2-browserify throw me an error :
Cannot read properties of undefined (reading 'toUpperCase')
My JS file :
import $ from 'jquery';
import 'select2/select2';
import '#pugx/autocompleter-bundle/js/autocompleter-select2';
$('#postal_code_search_zipcode').autocompleter({
url_list: '/postalCode_search',
url_get: '/postalCode_get/',
// min_length: 3,
otherOptions: {
minimumInputLength: 3,
formatNoMatches: 'Pas trouvé',
formatSearching: 'Je cherche',
formatInputTooShort: 'Insérer au minimum les 3 premiers chiffre'
}
})
My Controller :
{
#[Route('/postalCode_search', name: "postalCode_search", defaults:["_format" => "json"])]
public function searchPostalCode(Request $request, CityRepository $cityRepository): Response
{
$query = $request->query->get('q');
$results = $cityRepository->findLikePostalCode($query);
return $this->render('json/postal_code.json.twig', ['postalCodes' => $results]);
}
#[Route('/postalCode_get', name: "postalCode_get")]
public function getPostalCode(string $id, CityRepository $cityRepository): Response
{
$postalCode = $cityRepository->find($id);
if (null !== $postalCode){
$result = $postalCode->getZipcode().' '.$postalCode->getName();
}else{
$result = 'Aucun résultat';
}
return $this->json($result);
}
}
My json twig:
[{% for postalCode in postalCodes -%}
{{ {id: postalCode.id, label: postalCode.zipcode ~ ' ' ~ postalCode.name, value: postalCode.zipcode ~ ' ' ~ postalCode.name}|json_encode|raw }}
{%- if not loop.last %},
{% endif -%}
{%- endfor %}
]
If I use jquery-ui instead, when typing, it show me all I need but not with select2, any idea?

How can I add rows generated from object via twig to my datatable?

This is how I add rows to my datatables:
$(document).on('change', '.item-select', function() {
var optionValue = $(this).val();
var optionText = $('.item-select option[value="'+optionValue+'"]').text();
if (optionValue) {
table.row.add({
"id": 'test',
"name": 'test',
"type": 'test',
}).draw();
$('option', this).first().prop('selected', true);
}
});
I have an object columns :
array:3 [▼
"id" => ReflectionProperty {#7030 ▶}
"name" => ReflectionProperty {#7031 ▶}
"type" => ReflectionProperty {#7034 ▶}
]
Now I like to replace the hard coded fields with the fields from my object. This is my approach:
$(document).on('change', '.item-select', function() {
var optionValue = $(this).val();
var optionText = $('.item-select option[value="'+optionValue+'"]').text();
if (optionValue) {
table.row.add({
{% for key, value in columns %}
{ "{{ key }}": 'test'},
{% endfor %}
}).draw();
$('option', this).first().prop('selected', true);
}
});
The error in the console is this:
SyntaxError: expected property name, got '{'
You must remove curly brackets ...
$(document).on('change', '.item-select', function() {
var optionValue = $(this).val();
var optionText = $('.item-select option[value="'+optionValue+'"]').text();
if (optionValue) {
table.row.add({
{% for key, value in columns %}
"{{ key }}": 'test',
{% endfor %}
}).draw();
$('option', this).first().prop('selected', true);
}
});

configuring more than one {{> uploader}} in one page

I have an {{> uploader}} to get the user selected image. the problem is i want to add another {{> uploader}} with different configuration to get another input from user ".zip format". using {{> uploader}} twice, the same code will function for both of them.
is it possible to use {{> uploader}} twice and give each one its different configuration?
this is the code to configure the type of images the user can upload, how can i set this restriction to {{>uploader config="1"}}
Slingshot.fileRestrictions( "string", {
allowedFileTypes: [ "image/png", "image/jpeg", "image/gif" ],
maxSize: null
});
This is how i did it
Template.upload.rendered = function(){
if (upload.instance().data['config'] === '1') {
Slingshot.fileRestrictions( "string", {
allowedFileTypes: [ "image/png", "image/jpeg", "image/gif" ],
maxSize: null
});
} else if (upload.instance().data['config'] === '2') {
Slingshot.fileRestrictions( "string", {
allowedFileTypes: [ "image/png", "image/jpeg", "image/gif" ],
maxSize: null
});
}
}
but i get this error
TypeError: Cannot set property 'rendered' of undefined
at server/fileName.js
Update: look like you are using the code from here as an example
You can pass the flag to the template in order to brach the configuration inside the uploader
{{> uploader config="1"}}
{{> uploader config="2"}}
Then inside the template, depending on the flag, you can do
// uploader.js
Template.uploader.events({
'change input[type="file"]' ( event, template ) {
Modules.client.uploadToAmazonS3( { event: event, template: template, config: Template.instance().data['config'] } );
}
});
Now, change the upload-to-amazon.js
let _uploadFileToAmazon = ( file, config ) => {
var uploader;
if (config === '1') {
uploader = new Slingshot.Upload( "uploadToAmazonS3Cg1" );
} else {
uploader = new Slingshot.Upload( "uploadToAmazonS3Cg2" );
}
uploader.send( file, ( error, url ) => {
if ( error ) {
Bert.alert( error.message, "warning" );
_setPlaceholderText();
} else {
_addUrlToDatabase( url );
}
});
};
let upload = ( options ) => {
template = options.template;
let file = _getFileFromInput( options.event );
let config = options.config;
_setPlaceholderText( `Uploading ${file.name}...` );
_uploadFileToAmazon( file, config );
};
And finally change the server/slingshot.js
Slingshot.fileRestrictions( "uploadToAmazonS3Cg1", {
....
});
Slingshot.fileRestrictions( "uploadToAmazonS3Cg2", {
....
});
Slingshot.createDirective( "uploadToAmazonS3Cg1", Slingshot.S3Storage, {
...
});
Slingshot.createDirective( "uploadToAmazonS3Cg2", Slingshot.S3Storage, {
...
});

Resources