Bonjour tout le monde,
Me revoila pour partager un module que j’ai fait avec Claude.ai
J’utilise le super module DocEdit et j’avais un problème avec le liens des images à insérer dans les pdf avec l’éditeur WYSIWYG.
Les liens de type /htdoc/viewimage.php?modulepart=medias&entity=1&file=image/monimage.jpg
ne fonctionnent pas car DocEdit utilise getimagesize()
qui ne peut pas traiter directement les URLs et a besoin d’un chemin physique du fichier.
ATM m’a répondu que c’était pas leur problème et ce n’était clairement pas un problème de Dolibarr non plus.
Du coup j’ai cherché une solution de contournement avec un module à ma portée.
Ce module permet d’upload dans un dossier de notre choix des images dont on pourra récupérer le lien pour l’éditeur WYSIWYG
Avant ça j’uplodais avec filezilla mes images et copias collais le lien.
Mais mes collègues au bureaux galéraient avec cette manip et ça prennait un max de temps.
Structure du module :
htdocs/custom/myimages/
├── core/
│ └── modules/
│ └── modMyImages.class.php
├── langs/
│ ├── en_US/
│ │ └── myimages.lang
│ └── fr_FR/
│ └── myimages.lang
└── index.php
Le fichier descripteur modMyImages.class.php :
<?php
include_once DOL_DOCUMENT_ROOT .'/core/modules/DolibarrModules.class.php';
class modMyImages extends DolibarrModules
{
public function __construct($db)
{
global $langs, $conf;
$this->db = $db;
// Id for module (must be unique)
$this->numero = 500020;
// Key text used to identify module (for permissions, menus, etc...)
$this->rights_class = 'myimages';
// Module label (no space allowed), used if translation string 'ModuleXXXName' not found
$this->name = preg_replace('/^mod/i', '', get_class($this));
// Module description, used if translation string 'ModuleXXXDesc' not found
$this->description = "Gestion des images";
$this->editor_name = 'Votre nom';
$this->editor_url = 'http://www.example.com';
// Possible values for version are: 'development', 'experimental', 'dolibarr' or version
$this->version = '1.0';
// Key used in llx_const table to save module status enabled/disabled
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
// Name of image file used for this module
$this->picto = 'fa-upload';
// Defined all module parts (triggers, login, substitutions, menus, css, etc...)
$this->module_parts = array(
'triggers' => 0,
'login' => 0,
'substitutions' => 0,
'menus' => 0,
'theme' => 0,
'tpl' => 0,
'barcode' => 0,
'models' => 0,
'css' => array(),
'js' => array(),
'hooks' => array(),
'dir' => array("/myimages/temp")
);
// Dependencies
$this->depends = array();
$this->requiredby = array();
$this->conflictwith = array();
$this->phpmin = array(7,0);
$this->need_dolibarr_version = array(12,0);
$this->langfiles = array("myimages@myimages");
// Config page
$this->config_page_url = array("setup.php@myimages");
// Constants
$this->const = array();
// Boxes/Widgets
$this->boxes = array();
// Permissions
$this->rights = array();
$r = 0;
$this->rights[$r][0] = 500021;
$this->rights[$r][1] = 'Voir les images';
$this->rights[$r][2] = 'r';
$this->rights[$r][3] = 0;
$this->rights[$r][4] = 'read';
$r++;
$this->rights[$r][0] = 500022;
$this->rights[$r][1] = 'Uploader des images';
$this->rights[$r][2] = 'w';
$this->rights[$r][3] = 0;
$this->rights[$r][4] = 'write';
$r++;
// Main menu entries
$this->menu = array();
$r = 0;
$this->menu[$r] = array(
'fk_menu' => '', // '' = menu racine
'type' => 'top',
'titre' => 'Mes Images',
'prefix' => img_picto('', $this->picto, 'class="paddingright pictofixedwidth valignmiddle"'),
'mainmenu' => 'myimages',
'leftmenu' => '',
'url' => '/custom/myimages/index.php',
'langs' => 'myimages@myimages',
'position' => 100,
'enabled' => '1',
'perms' => '$user->rights->myimages->read',
'target' => '',
'user' => 0
);
}
/**
* Function called when module is enabled.
* The init function add permissions, constants, boxes, permissions and menus
* (defined in constructor) into Dolibarr database.
* It also creates data directories
*
* @param string $options Options when enabling module ('', 'noboxes')
* @return int 1 if OK, 0 if KO
*/
public function init($options = '')
{
$sql = array();
$result = $this->_load_tables('/myimages/sql/');
// Create extrafields during init
//include_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
//$extrafields = new ExtraFields($this->db);
return $this->_init($sql, $options);
}
/**
* Function called when module is disabled.
* Remove from database constants, boxes and permissions from Dolibarr database.
* Data directories are not deleted
*
* @param string $options Options when enabling module ('', 'noboxes')
* @return int 1 if OK, 0 if KO
*/
public function remove($options = '')
{
$sql = array();
return $this->_remove($sql, $options);
}
}
Le fichier index.php :
<?php
$res = 0;
if (! $res && file_exists("../main.inc.php"))
$res = @include "../main.inc.php";
if (! $res && file_exists("../../main.inc.php"))
$res = @include "../../main.inc.php";
if (! $res && file_exists("../../../main.inc.php"))
$res = @include "../../../main.inc.php";
if (! $res)
die("Include of main fails");
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
// Contrôle des droits
if (!$user->rights->myimages->read) accessforbidden();
$action = GETPOST('action', 'alpha');
$file = GETPOST('file', 'alpha');
$sortfield = GETPOST('sortfield', 'aZ09comma');
$sortorder = GETPOST('sortorder', 'aZ09comma');
$upload_dir = '/home/moi/www/image'; //chemin absolu du dossier
$web_url = 'https://monsite.com/image'; // url du dossier
$relative_path = '/image'; //chemin relatif
// Tri par défaut
if (!$sortfield) $sortfield="date";
if (!$sortorder) $sortorder="DESC";
// Suppression d'image
if ($action == 'delete' && $file && $user->rights->myimages->write) {
$file_path = $upload_dir.'/'.$file;
if (file_exists($file_path) && unlink($file_path)) {
setEventMessages($langs->trans("FileDeleted"), null);
} else {
setEventMessages($langs->trans("ErrorDeletingFile"), null, 'errors');
}
}
// Traitement de l'upload
if ($action == 'upload' && $user->rights->myimages->write) {
if (! empty($_FILES['userfile']['name'])) {
$upload_file = $_FILES['userfile']['name'];
// Vérification du type de fichier
$mime = dol_mimetype($upload_file);
if (! preg_match('/image/i', $mime)) {
setEventMessages($langs->trans("OnlyImageFilesAllowed"), null, 'errors');
} else {
$result = move_uploaded_file(
$_FILES['userfile']['tmp_name'],
$upload_dir.'/'.$upload_file
);
if ($result) {
chmod($upload_dir.'/'.$upload_file, 0644);
setEventMessages($langs->trans("FileTransferComplete"), null);
} else {
setEventMessages($langs->trans("ErrorFileNotUploaded"), null, 'errors');
}
}
}
}
// Load translation files required by the page
$langs->loadLangs(array("myimages@myimages"));
// CSS et JavaScript pour la modal
print '<style>
.modal {
display: none;
position: fixed;
z-index: 1000;
padding-top: 100px;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.9);
}
.modal-content {
margin: auto;
display: block;
max-width: 90%;
max-height: 90%;
}
.close {
position: absolute;
right: 35px;
top: 15px;
color: #f1f1f1;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
.table-container {
width: 100%;
margin: 0;
padding: 0;
overflow-x: auto;
}
.noborder.centpercent {
width: 100% !important;
margin: 0 !important;
border-collapse: collapse;
}
.width100 {
width: 100% !important;
}
</style>';
print '<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
alert("Lien copié !");
}, function() {
alert("Erreur lors de la copie");
});
}
var modal = null;
var modalImg = null;
document.addEventListener("DOMContentLoaded", function() {
modal = document.getElementById("imageModal");
modalImg = document.getElementById("modalImage");
modal.onclick = function(e) {
if (e.target == modal) {
modal.style.display = "none";
}
}
document.getElementsByClassName("close")[0].onclick = function() {
modal.style.display = "none";
}
});
function showImage(src) {
modalImg.src = src;
modal.style.display = "block";
}
</script>';
llxHeader();
// Ajout du JavaScript pour la copie dans le presse-papier
?>
<script type="text/javascript">
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
/* Succès */
alert('Lien copié avec succès !');
}, function() {
/* Échec */
alert('Erreur lors de la copie du lien');
});
}
</script>
<?php
print load_fiche_titre($langs->trans('MYIMAGES_MODULE_NAME'));
// Formulaire d'upload
if ($user->rights->myimages->write) {
print '<form name="formupload" action="'.$_SERVER["PHP_SELF"].'" method="POST" enctype="multipart/form-data">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="upload">';
print '<input type="file" name="userfile" class="flat">';
print '<input type="submit" class="button" value="'.$langs->trans("Upload").'">';
print '</form><br>';
}
// Modal pour l'agrandissement des images
print '<div id="imageModal" class="modal">';
print '<span class="close">×</span>';
print '<img class="modal-content" id="modalImage">';
print '</div>';
// Récupération et tri des fichiers
$files = array();
if (is_dir($upload_dir)) {
$handle = opendir($upload_dir);
if ($handle) {
while (($file = readdir($handle)) !== false) {
if (is_file($upload_dir.'/'.$file) && preg_match('/\.(jpg|jpeg|png|gif)$/i', $file)) {
$files[] = array(
'name' => $file,
'date' => filemtime($upload_dir.'/'.$file),
'url' => $web_url.'/'.$file,
'relative_url' => $relative_path.'/'.$file
);
}
}
closedir($handle);
}
}
// Tri des fichiers
if ($sortfield == 'name') {
usort($files, function($a, $b) use ($sortorder) {
return $sortorder == 'DESC' ?
strcasecmp($b['name'], $a['name']) :
strcasecmp($a['name'], $b['name']);
});
} else { // tri par date par défaut
usort($files, function($a, $b) use ($sortorder) {
return $sortorder == 'DESC' ?
$b['date'] - $a['date'] :
$a['date'] - $b['date'];
});
}
// Affichage des images
print '<div class="table-container">';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print_liste_field_titre("Image", $_SERVER["PHP_SELF"], "", "", "", "", $sortfield, $sortorder);
print_liste_field_titre("Name", $_SERVER["PHP_SELF"], "name", "", "", "", $sortfield, $sortorder);
print_liste_field_titre("Date", $_SERVER["PHP_SELF"], "date", "", "", "", $sortfield, $sortorder);
print_liste_field_titre("Copier", $_SERVER["PHP_SELF"], "", "", "", "", $sortfield, $sortorder);
print_liste_field_titre("Supprimer", $_SERVER["PHP_SELF"], "", "", "", 'class="center"', $sortfield, $sortorder);
print '</tr>';
foreach ($files as $fileinfo) {
print '<tr class="oddeven">';
print '<td><img src="'.$fileinfo['url'].'" height="50" style="cursor:pointer" onclick="showImage(\''.$fileinfo['url'].'\')" /></td>';
print '<td>'.$fileinfo['name'].'</td>';
print '<td>'.dol_print_date($fileinfo['date'], 'dayhour').'</td>';
print '<td class="center">';
print '<a class="reposition" href="#" onclick="copyToClipboard(\''.$fileinfo['relative_url'].'\'); return false;">';
print '<i class="fa fa-clipboard" aria-hidden="true"></i>';
print '</a>';
print '</td>';
print '<td class="center">';
if ($user->rights->myimages->write) {
print '<a href="'.$_SERVER["PHP_SELF"].'?action=delete&token='.newToken().'&file='.urlencode($fileinfo['name']).'" onclick="return confirm(\''.$langs->trans("ConfirmDelete").'\');">';
print img_picto('', 'delete');
print '</a>';
}
print '</td>';
print '</tr>';
}
print '</table>';
print '</div>';
llxFooter();
Le fichier en_US/myimages.lang :
// langs/en_US/myimages.lang
MYIMAGES_MODULE_NAME = Image Management
Module500020Name = Image Management
Module500020Desc = Module to upload and manage images
OnlyImageFilesAllowed = Only image files are allowed
FileTransferComplete = File transfer completed
ErrorFileNotUploaded = Error during file upload
FileDeleted = File deleted
ErrorDeletingFile = Error deleting file
ConfirmDelete = Are you sure you want to delete this image?
Upload = Upload
Image = Image
Name = Name
Size = Size
Date = Date
Link = Link
Actions = Actions
Le fichier fr_FR/myimages.lang :
// langs/fr_FR/myimages.lang
MYIMAGES_MODULE_NAME = Gestion des images
Module500020Name = Gestion des images
Module500020Desc = Module pour uploader et gérer des images
OnlyImageFilesAllowed = Seuls les fichiers image sont autorisés
FileTransferComplete = Transfert de fichier terminé
ErrorFileNotUploaded = Erreur lors du transfert du fichier
FileDeleted = Fichier supprimé
ErrorDeletingFile = Erreur lors de la suppression du fichier
ConfirmDelete = Êtes-vous sûr de vouloir supprimer cette image ?
Upload = Envoyer
Image = Image
Name = Nom
Size = Taille
Date = Date
Link = Lien
Actions = Actions
Attention à bien changer dans le code de index.php les variables :
$upload_dir = '/home/moi/www/image'; //chemin absolu du dossier
$web_url = 'https://monsite.com/image'; // url du dossier
$relative_path = '/image'; //chemin relatif
Et éventuellement (les ID doivent être uniques) dans le code de modMyImages.class.php :
// Id for module (must be unique)
$this->numero = 500020;
$this->rights[$r][0] = 500021;
$this->rights[$r][0] = 500022;
Ca m’a pris environ 3 semaines pour faire ça avec l’aide de Claude
J’ai essayé les hooks pour intercepter l’upload d’images de l’éditeur WYSIWYG mais ça marchait pas bien du coup j’ai opté pour cette solution.
Il y a surement des méthodes plus propres et plus optimisées mais c’est en dehors de mes compétences.
A moi ça m’a été utile, ça pourra peut être en aider d’autres