Wall
Une instance de Wall gère un dépôt d'images JPEG, PNG ou GIF et de documents PDF présenté avec un mur de vignettes. L'utilisateur peut sélectionner un fichier local ou glisser et déposer un fichier sur le mur pour automatiquement le télécharger sur le serveur. En un clic, il peut récupérer un fichier enregistré sur le serveur ou le détruire. Le code du serveur en PHP gère les copies des fichiers et la génération des vignettes affichées sur le mur. L'accès aux fichiers sur un mur est protégé. Ajouter la gestion d'autres types de fichiers comme des documents LibreOffice est un exercice simple.
Pour éditer des fichiers directement dans un navigateur avec CODE – Collabora Online Development Edition à partir d'un mur de documents, voir Collaboractor.
RAPPEL : La disposition et le style d'une interface sont à la discrétion du programmeur. Aucun modèle graphique n'est imposé. Coder l'apparence d'une instance de Wall prend quelques instants. Les exemples sur cette page utilisent les icônes de Font Awesome.
IMPORTANT : Les fichiers sur un mur sur ce serveur sont automatiquement détruits toutes les 20 minutes.
- Responder
- View
- Wall
- View
Cliquez sur le bouton de chargement pour ouvrir un fichier local et le télécharger sur le mur. Sélectionnez un fichier JPG, PNG ou GIF, un document en PDF. NOTE : La taille maximum du fichier est configurée à 5 Mo. Ouvrez un dossier contenant des images ou des documents avec l'explorateur de votre système de fichiers. Faites glisser et déposer un fichier JPG, PNG, GIF ou PDF sur l'espace à gauche des boutons de contrôle. Le fichier est transféré par blocs de 100 ko. La progression est affichée par un pourcentage. En cas d'erreur, le bouton de téléchargement vire au rouge.
Déplacez le pointeur de la souris sur une image pour afficher le nom du fichier.
Cliquez sur une image pour sélectionner un fichier et cliquez sur la poubelle pour supprimer ce fichier. Si le serveur retourne une erreur, la poubelle vire au rouge.
Cliquez sur une image et appuyez sur ou appuyez sur Maj et cliquez sur une image pour ouvrir un fichier.
Pour lancer le téléchargement d'une image ou d'un document à partir d'un fichier avec un bouton, le code peut exécuter la méthode uploadFile
du mur quand le bouton est cliqué.
IMPORTANT : Pour des raisons de sécurité, uploadFile
doit être appelée en réponse à une interaction de l'utilisateur.
<script>
document.getElementById('btn_upload_file').onclick = () => wall.uploadFile();
</script>
Dans la console du navigateur, affichez la liste des fichiers sur le mur :
wall.files
Array [ "1.pdf", "2.jpg"]
- function Wall(options = false) {
- options = options || {};
- let draganddrop = options.draganddrop ? true : false;
- let tagURL = options.tagURL;
- let deleteURL = options.deleteURL;
- let uploadURL = options.uploadURL;
- let fileURL = options.fileURL;
- if (! (typeof tagURL === 'undefined' || tagURL === null || typeof tagURL === 'string'))
- throw new TypeError();
- if (! (typeof deleteURL === 'undefined' || deleteURL === null || typeof deleteURL === 'string'))
- throw new TypeError();
- if (! (typeof uploadURL === 'undefined' || uploadURL === null || typeof uploadURL === 'string'))
- throw new TypeError();
- if (! (typeof fileURL === 'undefined' || fileURL === null || typeof fileURL === 'string'))
- throw new TypeError();
- if (uploadURL) {
- let filetypes = options.filetypes;
- if (!Array.isArray(filetypes) && filetypes.length > 0 && filetypes.every((e) => typeof e === 'string'))
- throw new TypeError();
- this._filetypes = filetypes;
- let maxfiles = options.maxfiles;
- if (maxfiles === undefined)
- maxfiles = 10;
- else if (!Number.isInteger(maxfiles))
- throw new TypeError();
- else if (maxfiles < 1)
- throw new RangeError();
- this._maxfiles = maxfiles;
- let maxfilesize = options.maxfilesize;
- if (maxfilesize === undefined)
- maxfilesize = 1000000;
- else if (!Number.isInteger(maxfilesize))
- throw new TypeError();
- else if (maxfilesize < 100000)
- throw new RangeError();
- this._maxfilesize = maxfilesize;
- let chunksize = options.chunksize;
- if (chunksize === undefined)
- chunksize = 100000;
- else if (!Number.isInteger(chunksize))
- throw new TypeError();
- else if (chunksize < 10000)
- throw new RangeError();
- this._chunksize = chunksize;
- }
- View.call(this);
- this._draganddrop = draganddrop;
- this._tagURL = tagURL;
- this._deleteURL = deleteURL;
- this._uploadURL = uploadURL;
- this._fileURL = fileURL;
- this._tagsWidget = null;
- this._deleteWidget = null;
- this._uploadWidget = null;
- this._downloadWidget = null;
- this._statusWidget = null;
- this._uploading = false;
- this._slots = {};
- this._tag = null;
- this._error = null;
- }
- Wall.prototype = Object.create(View.prototype);
- Object.defineProperty(Wall.prototype, 'constructor', { value: Wall, enumerable: false, writable: true });
La classe Wall hérite de la classe View.
Le paramètre options
du constructeur est un objet qui configure les options maxfiles
, maxfilesize
, filetypes
, chunksize
, draganddrop
, tagURL
, uploadURL
, deleteURL
et fileURL
.
maxfiles
spécifie le nombre maximum de fichiers que l'espace de l'utilisateur peut contenir, 10 par défaut. Provoque une erreur RangeError si < 1.
maxfilesize
spécifie la taille maximum d'un fichier, 1 Mo par défaut. Provoque une erreur RangeError si < 100 ko.
filetypes
spécifie la liste des types MIME des fichiers que le mur accepte de télécharger. Par défaut, tous les types de fichiers sont acceptés.
chunksize
spécifie la taille des blocs de données envoyés au serveur, 100 ko par défaut. Provoque une erreur RangeError si < 10 ko.
Si draganddrop
, false
par défaut, vaut true
, l'instance accepte de télécharger un fichier que l'utilisateur a déposé sur l'interface.
tagURL
est une chaîne de caractères qui spécifie l'URL du GET envoyé au serveur pour obtenir la vignette d'un fichier.
uploadURL
est une chaîne de caractères qui spécifie l'URL du POST envoyé au serveur pour télécharger un fichier.
deleteURL
est une chaîne de caractères qui spécifie l'URL du POST envoyé au serveur pour détruire un fichier.
fileURL
est une chaîne de caractères qui spécifie l'URL du GET envoyé au serveur pour récupérer un fichier.
tagURL
, uploadURL
, deleteURL
et fileURL
sont optionnels.
- Object.defineProperty(Wall.prototype, 'files', {
- get: function() {
- return Object.keys(this._slots);
- },
- set: function(filelist) {
- let slots = {};
- if (filelist) {
- if (!Array.isArray(filelist))
- throw new TypeError();
- for (let filename of filelist) {
- if (typeof filename !== 'string')
- throw new TypeError();
- slots[filename] = null;
- }
- }
- this._slots = slots;
- if (this._tagsWidget)
- this.resetTagsWidget();
- if (this.interfaced())
- this.resetWidget();
- }
- });
files
est un accesseur qui retourne ou change la liste des fichiers gérés par this
.
filelist
est un tableau de chaînes de caractères, e.g. ["1.pdf", "2.jpg"]
.
filelist
est normalement construit par le code sur le serveur.
- Object.defineProperty(Wall.prototype, 'tag', {
- get: function() {
- return this._tag;
- }
- });
tag
est un accesseur qui retourne le nom du fichier sélectionné dans this
.
- Wall.prototype.resetTagsWidget = function() {
- this._tagsWidget.innerHTML = '';
- for (let filename in this._slots) {
- const img = document.createElement('img');
- img.src = `${this._tagURL}/${encodeURI(filename)}`;
- img.title = filename;
- img.addEventListener('click', (e) => this._clickImage(e, filename));
- this._slots[filename] = img;
- this._tagsWidget.appendChild(img);
- }
- return this;
- };
resetTagsWidget
affiche les vignettes des fichiers gérés par this
.
- Wall.prototype.resetWidget = function() {
- if (this._uploadWidget) {
- if (!this._uploading && Object.keys(this._slots).length < this._maxfiles)
- this._uploadWidget.classList.remove('disabled');
- else
- this._uploadWidget.classList.add('disabled');
- if (this._error == 'upload')
- this._uploadWidget.classList.add('inerror');
- else
- this._uploadWidget.classList.remove('inerror');
- }
- if (this._deleteWidget) {
- if (!this._uploading && this._tag)
- this._deleteWidget.classList.remove('disabled');
- else
- this._deleteWidget.classList.add('disabled');
- if (this._error == 'delete')
- this._deleteWidget.classList.add('inerror');
- else
- this._deleteWidget.classList.remove('inerror');
- }
- if (this._downloadWidget) {
- if (this._tag)
- this._downloadWidget.classList.remove('disabled');
- else
- this._downloadWidget.classList.add('disabled');
- }
- return this;
- };
resetWidget
rend actif ou inactif les différents boutons de contrôle de l'interface selon la configuration et l'état de this
.
- Wall.prototype.setWidget = function(w) {
- View.prototype.setWidget.call(this, w);
- this._tagsWidget = this._tagURL ? w.querySelector('.tags') : null;
- this._fileWidget = this._uploadURL ? w.querySelector('.fileinput') : null;
- if (this._fileWidget && this._fileWidget.tagName != 'INPUT')
- this._fileWidget = null;
- this._uploadWidget = w.querySelector('.fileupload');
- this._deleteWidget = w.querySelector('.filedelete');
- this._downloadWidget = w.querySelector('.filedownload');
- this._statusWidget = w.querySelector('.filestatus');
- if (this._draganddrop && this._tagsWidget) {
- this._tagsWidget.addEventListener('drop', (e) => {
- const dt = e.dataTransfer;
- e.preventDefault();
- if (dt.types.indexOf('Files') != -1) {
- this._uploadFile(dt.files[0]);
- }
- });
- this._tagsWidget.addEventListener('dragenter', (e) => {
- const dt = e.dataTransfer;
- if (dt.types.indexOf('Files') != -1) {
- e.preventDefault();
- }
- });
- this._tagsWidget.addEventListener('dragleave', (e) => {
- e.preventDefault();
- });
- this._tagsWidget.addEventListener('dragover', (e) => {
- const dt = e.dataTransfer;
- e.preventDefault();
- dt.dropEffect = dt.types.indexOf('Files') != -1 && !this._uploading && Object.keys(this._slots).length < this._maxfiles ? 'copy' : 'none';
- });
- }
- if (this._fileWidget)
- this._fileWidget.hidden = true;
- if (this._uploadWidget) {
- this._uploadWidget.classList.add('disabled');
- if (this._uploadURL && this._fileWidget) {
- this._uploadWidget.addEventListener('click', () => {
- if (!this._uploadWidget.classList.contains('disabled'))
- this._fileWidget.click();
- });
- this._fileWidget.addEventListener('change', (e) => {
- if (e.target.value) {
- this._uploadFile(e.target.files[0]);
- }
- });
- }
- else
- this._uploadWidget = null;
- }
- if (this._deleteWidget) {
- this._deleteWidget.classList.add('disabled');
- if (this._deleteURL) {
- this._deleteWidget.addEventListener('click', () => {
- if (!this._deleteWidget.classList.contains('disabled'))
- this.deleteFile();
- });
- }
- else
- this._deleteWidget = null;
- }
- if (this._downloadWidget) {
- this._downloadWidget.classList.add('disabled');
- if (this._fileURL) {
- this._downloadWidget.addEventListener('click', () => {
- if (!this._downloadWidget.classList.contains('disabled'))
- this.downloadFile();
- });
- }
- else
- this._downloadWidget = null;
- }
- return this;
- };
setWidget
redéfinit la méthode héritée de la classe View.
setWidget
initialise les éléments de l'interface de this
avec les différents composants graphiques attendus dans w
, i. e. les propriétés _tagsWidget
, _uploadWidget
, _fileWidget
, _deleteWidget
, _downloadWidget
et _statusWidget
de this
.
Si _draganddrop
de this
vaut true
et si _tagsWidget
n'est pas null
, setWidget
programme l'appel de la méthode interne _uploadFile
en réponse à un glisser et déposer d'un fichier sur _tagsWidget
.
- Wall.prototype.destroyWidget = function() {
- View.prototype.destroyWidget.call(this);
- if (this._tagsWidget) {
- for (let filename in this._slots)
- this._slots[filename] = null;
- }
- this._tagsWidget = null;
- this._deleteWidget = null;
- this._uploadWidget = null;
- this._fileWidget = null;
- this._downloadWidget = null;
- this._statusWidget = null;
- this._tag = null;
- return this;
- };
destroyWidget
redéfinit la méthode héritée de la classe View.
Elle met tous les widgets de l'interface à null
.
- Wall.prototype.uploadFile = function() {
- if (this._uploading)
- return this;
- if (this._fileWidget)
- this._fileWidget.click();
- return this;
- };
uploadFile
ouvre l'explorateur de fichiers du navigateur.
Si un fichier est sélectionné par l'utilisateur, la méthode interne _uploadFile
est appelée avec en argument le descripteur du fichier.
Pour des raisons de sécurité, uploadFile
doit être appelée en réponse à une interaction de l'utilisateur.
- Wall.prototype._uploadFile = function(fd) {
- if (!this._uploadURL)
- return this;
- const filename = fd.name;
- const filesize = fd.size;
- const filetype = fd.type;
- if (filename in this._slots || Object.keys(this._slots).length >= this._maxfiles) {
- this._error = 'upload';
- if (this.interfaced())
- this.resetWidget();
- return this;
- }
- if ((this._filetypes && this._filetypes.indexOf(filetype) == -1) || (this._maxfilesize && filesize > this._maxfilesize)) {
- this._error = 'upload';
- if (this.interfaced())
- this.resetWidget();
- return this;
- }
- const uploadurl = this._uploadURL;
- const chunksize = this._chunksize;
- const filereader = new FileReader();
- filereader.onloadend = (e) => postdata(e.target.result);
- let offset = 0, progress = 0, blob;
- const uploadslice = () => {
- blob = fd.slice(offset, offset + chunksize);
- filereader.readAsDataURL(blob);
- if (this._statusWidget) {
- progress = Math.floor(((offset + blob.size) / filesize) * 100);
- this._statusWidget.innerText = `${progress}%`;
- }
- };
- const postdata = (data) => {
- $.post(uploadurl, {file_name: filename, file_size: filesize, file_type: filetype, file_offset: offset, file_data: data})
- .done(() => {
- offset += blob.size;
- if (offset < filesize)
- uploadslice();
- else {
- if (this._statusWidget)
- this._statusWidget.innerText = '';
- this._slots[filename] = null;
- this.respondTo('wallFileAdded', this, filename);
- if (this._tagsWidget) {
- const img = document.createElement('img');
- img.src = `${this._tagURL}/${encodeURI(filename)}?nocache=${Date.now()}`;
- img.title = filename;
- img.addEventListener('click', (e) => this._clickImage(e, filename));
- this._slots[filename] = img;
- this._tagsWidget.appendChild(img);
- }
- this._uploading = false;
- this._error = null;
- if (this.interfaced())
- this.resetWidget();
- }
- })
- .fail(() => {
- if (this._statusWidget)
- this._statusWidget.innerText = '';
- this._uploading = false;
- this._error = 'upload';
- if (this.interfaced())
- this.resetWidget();
- });
- };
- this._uploading = true;
- if (this.interfaced())
- this.resetWidget();
- uploadslice();
- return this;
- };
_uploadFile
lit le fichier local décrit par fd
et le télécharge sur le serveur.
_uploadFile
est une méthode interne appelée en réponse à un clic sur le widget de saisie d'un nom de fichier, i. e. _fileWidget
, ou en réponse à un glisser déposer d'un fichier sur le widget qui montre les vignettes, i.e. _tagsWidget
.
- Wall.prototype.deleteFile = function() {
- if (!this._deleteURL)
- return this;
- if (!this._tag)
- return this;
- if (this._uploading)
- return this;
- const deleteurl = this._deleteURL;
- const filename = this._tag;
- const deletefile = () => {
- $.post(deleteurl, {file_name: filename} )
- .done(() => {
- if (this._slots[filename])
- this._slots[filename].remove();
- delete this._slots[filename];
- this.respondTo('wallFileDeleted', this, filename);
- if (this._tag == filename) {
- this._tag = null;
- this.respondTo('wallSelectionChanged', this);
- }
- this._error = null;
- if (this.interfaced())
- this.resetWidget();
- })
- .fail(() => {
- this._error = 'delete';
- if (this.interfaced())
- this.resetWidget();
- });
- };
- deletefile();
- return this;
- };
deleteFile
demande au serveur de détruire le fichier qui a été sélectionné sur le mur.
- Wall.prototype.downloadFile = function() {
- if (!this._fileURL)
- return this;
- if (!this._tag)
- return this;
- const url = `${this._fileURL}/${encodeURI(this._tag)}`;
- window.open(url);
- return this;
- };
downloadFile
demande au navigateur de récupérer le fichier qui a été sélectionné sur le mur.
- Wall.prototype.selectTag = function(filename) {
- if (this._tag === filename)
- return this;
- if (this._slots[filename] === undefined)
- return this;
- if (this._tag && this._slots[this._tag])
- this._slots[this._tag].classList.remove('selected');
- this._tag = filename;
- if (this._slots[this._tag])
- this._slots[this._tag].classList.add('selected');
- if (this.interfaced())
- this.resetWidget();
- return this;
- };
selectTag
sélectionne le fichier spécifié par id
.
- Wall.prototype.unselectTag = function() {
- if (!this._tag)
- return this;
- if (this._slots[this._tag])
- this._slots[this._tag].classList.remove('selected');
- this._tag = null;
- if (this.interfaced())
- this.resetWidget();
- return this;
- };
unselectTag
désélectionne le fichier qui est sélectionné.
- Wall.prototype._clickImage = function(e, filename) {
- if (e.shiftKey) {
- if (!this._fileURL)
- return;
- const url = `${this._fileURL}/${encodeURI(filename)}`;
- window.open(url);
- }
- else {
- if (this._tag == filename)
- this.unselectTag();
- else
- this.selectTag(filename);
- this.respondTo('wallSelectionChanged', this);
- }
- };
_clickImage
est une méthode interne qui en réponse à un clic sur la vignette d'un fichier, le sélectionne ou le désélectionne, ou si la touche Maj est enfoncée, récupère le fichier correspondant.
Serveur
Un fichier est copié dans un espace disque réservé à un utilisateur sur le serveur en réponse à un POST par une fonction qui en extrait le nom, le type MIME et la taille du fichier, le bloc de données qui est transféré et sa position dans le fichier. Un bloc de données est encodé en BASE64. Lorsque le dernier bloc de données est enregistré, le serveur génère la vignette du fichier, une image réduite d'une image ou de la première page d'un document.
Un fichier est détruit par le serveur en réponse à un POST par une fonction qui en extrait le nom d'un fichier. Le serveur détruit aussi la vignette associée au fichier.
L'accès à un fichier ou à une vignette se fait indirectement par un GET qui déclenche une action sur le serveur qui retourne le fichier demandé dans l'espace disque réservé à l'utilisateur.
IMPORTANT : Un accès direct à un fichier dans le dossier d'un utilisateur est au mieux rejeté par une directive Deny from all
pour l'intégralité du répertoire géré par un mur.
Le code du serveur qui répond à un POST pour copier un fichier, à un POST pour détruire un fichier et à un GET pour récupérer un fichier ou la vignette d'un fichier est programmé pour iZend. Le fichier wall.inc s'installe dans le dossier models. Les fichiers wallfile.php, wallfileupload.php et wallfiledelete.php s'installent dans le dossier actions. Pour activer les URL /wallfile, /wallupload et /walldelete, des entrées sont ajoutées dans le fichier aliases.inc. Adaptez ce code à votre environnement de développement.
Chaque utilisateur, identifié par sa session, a un dossier réservé dans le répertoire wall à la racine du site. Les vignettes sont dans le sous-dossier tags du dossier d'un utilisateur.
Pour renvoyer un fichier ou une vignette, le serveur utilise le module Xsendfile d'Apache. Pour installer et activer ce module :
$ sudo apt-get install libapache2-mod-xsendfile
$ sudo a2enmod xsendfile
Pour activer ce module et le configurer pour un site, ajoutez les lignes suivante dans le fichier de configuration du site pour Apache :
XsendFilePath /var/www/mysite.net/wall
<Directory /var/www/mysite.net/wall>
Deny from all
</Directory>
Remplacez /var/www/mysite.net par le dossier racine du site. Notez qu'un accès direct au contenu de ce répertoire est rejeté.
Le code qui gère les dossiers des utilisateurs est dans le fichier wall.inc.
- require_once 'dirclear.php';
Inclut le code de la fonction dirclear
.
if (!is_dir($dir)) {
return false;
}
dirclearaux($dir);
return true;
}
function dirclearaux($dir) {
$handle = opendir($dir);
while (($file = readdir($handle)) !== false) {
if ($file == '.' || $file == '..') {
continue;
}
$filepath = $dir . DIRECTORY_SEPARATOR . $file;
if (is_link($filepath)) {
unlink($filepath);
}
else if (is_dir($filepath)) {
dirclearaux($filepath);
rmdir($filepath);
}
else {
unlink($filepath);
}
}
closedir($handle);
}
dirclear
supprime récursivement tous les fichiers et tous les sous-répertoires qui sont dans le répertoire $dir
.
Voir dirclear dans la documentation de la librairie d'iZend.
- define('WALL_DIR', ROOT_DIR . DIRECTORY_SEPARATOR . 'wall');
- define('WALL_DIR_MODE', 0775);
- define('WALL_NFILES', 20);
- define('WALL_FILE_MAX_SIZE', 5*1000000);
- define('WALL_FILE_TYPES', array(
- 'application/pdf',
- 'image/jpeg',
- 'image/png',
- 'image/gif'
- ));
- define('WALL_TAG_WIDTH', 180);
- define('PNG_QUALITY', 6);
WALL_DIR
définit l'emplacement des dossiers des utilisateurs, i.e. le répertoire wall à la racine du site.
WALL_DIR_MODE
définit le mode de création d'un dossier, plus précisément en mode écriture pour le groupe d'exécution d'Apache.
WALL_NFILES
définit le nombre maximum de fichiers dans un dossier utilisateur et WALL_FILE_MAX_SIZE
la taille maximum d'un fichier.
WALL_FILE_TYPES
liste les types de fichiers gérés par le mur.
WALL_TAG_WIDTH
donne la largeur d'une vignette et PNG_QUALITY
la niveau de qualité de son PNG.
- define('GS_COVER', 'gs -q -o "%s" -dBATCH -dNOPAUSE -dSAFER -sDEVICE=png16m -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -dAutoRotatePages=/None -dUseCropBox -r96 -dFirstPage=1 -dLastPage=1 "%s" 2>&1');
GS_COVER
définit la ligne de commande utilisée pour générer une image de la première page d'un document PDF.
Le premier %s
est remplacé par le nom du fichier de sortie, un fichier PNG temporaire.
Le second %s
est remplacé par le nom du fichier en entrée, le document PDF dans le dossier de l'utilisateur.
- function wall_directory($wall_id) {
- return WALL_DIR . DIRECTORY_SEPARATOR . $wall_id;
- }
- function wall_tags_directory($wall_id) {
- return wall_directory($wall_id) . DIRECTORY_SEPARATOR . 'tags';
- }
wall_directory
retourne l'emplacement du dossier $wall_id
dans WALL_DIR
, i. e. le dossier d'un utilisateur.
wall_tags_directory
retourne l'emplacement des vignettes du dossier $wall_id
.
- function wall_create_directory($wall_id) {
- $dir=wall_directory($wall_id);
- if (file_exists($dir)) {
- return true;
- }
- if (!@mkdir($dir, WALL_DIR_MODE)) {
- return false;
- }
- $subdir = wall_tags_directory($wall_id);
- if (!@mkdir($subdir, WALL_DIR_MODE)) {
- return false;
- }
- return true;
- }
wall_create_directory
vérifie si le dossier $wall_id
existe et si non, le crée avec le sous-dossier pour les vignettes.
- function wall_delete_directory($wall_id) {
- $dir=wall_directory($wall_id);
- dirclear($dir);
- return @rmdir($dir);
- }
wall_delete_directory
détruit tout le contenu du dossier $wall_id
.
- function wall_contents($wall_id, $sort=true) {
- $filelist=false;
- $dir=wall_directory($wall_id);
- $files=glob($dir . DIRECTORY_SEPARATOR . '*.*', GLOB_NOSORT);
- if ($files) {
- if ($sort) {
- usort($files, function($f1, $f2) {
- $t1=filemtime($f1);
- $t2=filemtime($f2);
- if ($t1 == $t2) {
- return 0;
- }
- return ($t1 < $t2) ? -1 : 1;
- });
- }
- $filelist=array();
- foreach ($files as $f) {
- $filelist[]=pathinfo($f, PATHINFO_BASENAME);
- }
- }
- return $filelist;
- }
wall_contents
retourne les noms de tous les fichiers JPG, PNG, GIF ou PDF dans le dossier $wall_id
.
Si $sort
vaut true
, les fichiers sont triés par la date et l'heure de leur dernière modification.
- function wall_file($wall_id, $fname) {
- return wall_directory($wall_id) . DIRECTORY_SEPARATOR . $fname;
- }
- function wall_file_tag($wall_id, $fname) {
- return wall_tags_directory($wall_id) . DIRECTORY_SEPARATOR . str_replace('.', '_', $fname) . '.png';
- }
wall_file
retourne le nom complet du fichier $fname
dans le dossier $wall_id
.
wall_file_tag
retourne le nom complet de la vignette du fichier $fname
dans le dossier $wall_id
.
- function wall_tag_pdf($wall_id, $fname, $pdffile) {
- $tmpdir=ini_get('upload_tmp_dir') ?: ROOT_DIR . DIRECTORY_SEPARATOR . 'tmp';
- $tmpfile=$tmpdir . DIRECTORY_SEPARATOR . uniqid('pdf') . '.png';
- $cmdline=sprintf(GS_COVER, $tmpfile, $pdffile);
- @exec($cmdline, $output, $ret);
- if ($ret != 0) {
- return false;
- }
- $r = wall_create_tag($wall_id, $fname, $tmpfile);
- @unlink($tmpfile);
- return $r;
- }
wall_tag_pdf
crée la vignette du fichier $fname
dans $wall_id
du document dans $pdffile
.
wall_tag_pdf
crée un fichier temporaire contenant l'image de la première page du document.
- function wall_tag_img($wall_id, $fname, $imgfile) {
- return wall_create_tag($wall_id, $fname, $imgfile);
- }
wall_tag_img
crée la vignette du fichier $fname
dans $wall_id
avec l'image dans $imgfile
.
- function wall_create_tag($wall_id, $fname, $imgfile) {
- $fileinfo=@getimagesize($imgfile);
- if (!$fileinfo) {
- return false;
- }
- $width=$fileinfo[0];
- $height=$fileinfo[1];
- $imgtype=$fileinfo[2];
- $img=false;
- switch ($imgtype) {
- case IMAGETYPE_JPEG:
- $img=imagecreatefromjpeg($imgfile);
- break;
- case IMAGETYPE_PNG:
- $img=imagecreatefrompng($imgfile);
- break;
- case IMAGETYPE_GIF:
- $img=imagecreatefromgif($imgfile);
- break;
- default:
- break;
- }
- if (!$img) {
- return false;
- }
- if ($imgtype == IMAGETYPE_JPEG) {
- $exif = @exif_read_data($imgfile);
- if (!empty($exif['Orientation'])) {
- switch ($exif['Orientation']) {
- case 3:
- $deg=180;
- break;
- case 6:
- $deg=-90;
- break;
- case 8:
- $deg=90;
- break;
- default:
- $deg=0;
- break;
- }
- if ($deg != 0) {
- $img = imagerotate($img, $deg, 0);
- if ($deg=90 or $deg=-90) {
- $width=$fileinfo[1];
- $height=$fileinfo[0];
- }
- }
- }
- }
- if ($width > WALL_TAG_WIDTH) {
- $w=WALL_TAG_WIDTH;
- $h=$height * (WALL_TAG_WIDTH / $width);
- }
- else {
- $w=$width;
- $h=$height;
- }
- $imgcopy = imagecreatetruecolor($w, $h);
- if ($imgtype == IMAGETYPE_PNG or $imgtype == IMAGETYPE_GIF) {
- $bg=imagecolorallocate($imgcopy, 255, 255, 255);
- imagefill($imgcopy, 0, 0, $bg);
- }
- if ($width > WALL_TAG_WIDTH) {
- imagecopyresampled($imgcopy, $img, 0, 0, 0, 0, $w, $h, $width, $height);
- }
- else {
- imagecopy($imgcopy, $img, 0, 0, 0, 0, $w, $h);
- }
- $filecopy=wall_file_tag($wall_id, $fname);
- if (!@imagepng($imgcopy, $filecopy, PNG_QUALITY)) {
- return false;
- }
- clearstatcache(true, $filecopy);
- imagedestroy($imgcopy);
- return true;
- }
wall_create_tag
crée la vignette du fichier $fname
dans $wall_id
à partir de l'image dans le file $imgfile
.
Si l'image est plus large que WALL_TAG_WIDTH
, la vignette a une largeur de WALL_TAG_WIDTH
pixels et une hauteur préservant le rapport d'aspect de l'image.
Si l'image est un PNG ou un GIF, la vignette a un fond blanc.
Si l'image est un JPG, la vignette est correctement orientée.
- function wall_init_file($wall_id, $fname, $file, $mimetype) {
- switch ($mimetype) {
- case 'image/jpeg':
- case 'image/png':
- case 'image/gif':
- return wall_tag_img($wall_id, $fname, $file);
- case 'application/pdf':
- return wall_tag_pdf($wall_id, $fname, $file);
- default:
- break;
- }
- return false;
- }
wall_init_file
crée la vignette du fichier $fname
dans $wall_id
à partir du fichier $file
dont le contenu est du type MIME $mimetype
.
wall_init_file
retourne le résultat de la fonction wall_tag_img
ou de la fonction wall_tag_pdf
ou false
si $mimetype
n'est pas pris en charge.
NOTE : Gérer plus de types de fichiers est relativement facile. Pour générer les vignettes des fichiers édités avec LibreOffice, essayez unoconv - Universal Office Converter.
- require_once 'filemimetype.php';
Inclut le code de la fonction file_mime_type
.
$mime=false;
if (function_exists('finfo_file')) {
$finfo = finfo_open(FILEINFO_MIME);
$mime = @finfo_file($finfo, $file);
finfo_close($finfo);
}
else if (substr(PHP_OS, 0, 3) == 'WIN') {
$mime = mime_content_type($file);
}
else {
$file = escapeshellarg($file);
$cmd = "file -iL $file";
exec($cmd, $output, $r);
if ($r == 0) {
$mime = substr($output[0], strpos($output[0], ': ')+2);
}
}
if (!$mime) {
return false;
}
if ($encoding) {
return $mime;
}
return substr($mime, 0, strpos($mime, '; '));
}
file_mime_type
retourne une chaîne de caractères donnant le type MIME du fichier $file
, e.g. image/jpeg, et si $encoding
vaut true
, le jeu de caractères du fichier précédé par un ; (POINT-VIRGULE), e.g. image/jpeg; charset=binary.
Voir file_mime_type dans la documentation de la librairie d'iZend.
- require_once 'validatefilename.php';
Inclut le code de la fonction validate_filename
.
return preg_match('/^[[:alnum:]]+[[:alnum:]_-]*(\.[[:alnum:]]+)?$/', $name);
}
validate_filename
retourne true
si $name
commence par un caractère alphanumérique suivi d'une série de caractères alphanumériques, de soulignés ou de tirets terminée en option par un point et au moins un caractère alphanumérique.
Voir validate_filename dans la documentation de la librairie d'iZend.
NOTE : Adaptez cette fonction à la syntaxe des noms de fichiers acceptés par le site. IMPORTANT : Ne donnez pas accès à des fichiers en dehors du dossier de l'utilisateur. N'autorisez pas en particulier le caractère / (BARRE OBLIQUE).
return preg_match('/^[0-9\pL][0-9\pL \._+-]*(\.[[:alnum:]]+)$/u', $name);
}
Dans cette version, les caractères accentués sont acceptés et un nom de fichier doit commencer par une lettre ou un chiffre et se terminer par une extension avec en option entre les deux, des lettres, des chiffres, des espaces, des points, des soulignées, des plus ou des tirets.
- require_once 'models/wall.inc';
Inclut le code de la fonction wall_file
.
- function wallfile($lang, $arglist=false) {
Définit la fonction wallfile
qui est appelée par l'URL /wallfile du site.
Les arguments $lang
et $arglist
sont préparés par iZend.
Adaptez ce code à votre environnement de développement.
$lang
vaut false
.
Le contenu retourné par cette actions est indépendant du langage de l'utilisateur.
$arglist
est un tableau qui contient le reste de l'URL de la requête et ses paramètres , e.g. array(0 => '1.jpg')
pour /wallfile/1.jpg et array(0 => '1.png', 'nocache' => '1600709132578')
pour /wallfile/1.png?nocache=1600709132578.
- if (!isset($_SESSION['wall_id'])) {
- goto badrequest;
- }
- $wall_id=$_SESSION['wall_id'];
Extrait l'identifiant du mur de l'utilisateur enregistré dans la session.
Retourne une erreur HTTP 400 Bad Request
si un identifiant n'est pas trouvé dans la session.
- $name=false;
- if (is_array($arglist)) {
- if (isset($arglist[0])) {
- $name=$arglist[0];
- }
- }
- if (!$name or !validate_filename($name)) {
- goto badrequest;
- }
- $file=wall_file($wall_id, $name);
- if (!file_exists($file)) {
- goto notfound;
- }
Vérifie que l'URL contient un nom de fichier valide.
Construit le nom complet du fichier demandé à partir du répertoire de l'utilisateur et du nom de fichier passé dans l'URL.
EXEMPLE : L'URL /wallfile/1.png a été déconstruite pour en extraire l'action, /wallfile correspondant à la fonction wallfile
, puis le reste de l'URL a été décomposé dans le tableau passé en argument à la fonction, /1.png donnant le tableau array(0 => '1.png')
.
Si l'utilisateur a l'identifiant 5f690ce2c930d
, le nom complet du fichier est /wall/5f690ce2c930d/1.png dans le répertoire racine du site.
Retourne une erreur HTTP 404 Not Found
si le fichier demandé n'est pas trouvé dans l'espace de l'utilisateur.
- $filesize=filesize($file);
- $filetype=file_mime_type($file);
- if (!$filetype) {
- $filetype = 'application/octet-stream';
- }
Calcule la taille et le type du fichier demandé.
- header("X-Sendfile: $file");
- header("Content-Type: $filetype");
- header("Content-Length: $filesize");
- return false;
Retourne un document HTTP avec seulement en en-tête une directive X-Sendfile
pour qu'Apache retourne directement le fichier spécifié.
- badrequest:
- header('HTTP/1.1 400 Bad Request');
- return false;
Retourne un simple en-tête 400 Bad Request
.
- header("Content-Type: $filetype");
- header("Content-Length: $filesize");
- return false;
Retourne un simple en-tête 404 Not Found
.
- function walltag($lang, $arglist=false) {
Définit la fonction walltag
qui est appelée par l'URL /walltag du site.
La fonction walltag
est pratiquement identique à la fonction wallfile
.
- require_once 'validatefilename.php';
- require_once 'models/wall.inc';
Inclut le code des fonctions validate_filename
, wall_file
et wall_init_file
, les constantes WAL_FILE_MAX_SIZE
et WAL_FILE_TYPES
.
- function wallupload($lang, $arglist=false) {
Définit la fonction wallupload
qui est appelée par l'URL /wallupload du site.
Les arguments $lang
et $arglist
ne sont pas utilisés.
Le contenu retourné par cette action est indépendant du langage de l'utilisateur.
Aucun argument ou paramètre n'est attendu dans l'URL.
- if (!isset($_SESSION['wall_id'])) {
- goto badrequest;
- }
- $wall_id=$_SESSION['wall_id'];
Extrait l'identifiant du mur de l'utilisateur enregistré dans la session.
Retourne une erreur HTTP 400 Bad Request
si un identifiant n'est pas trouvé dans la session.
- $maxfilesize=WALL_FILE_MAX_SIZE;
- $filetypes=WALL_FILE_TYPES;
Initialise $maxfilesize
à WALL_FILE_MAX_SIZE
et filetypes
à WAL_FILE_TYPES
, la liste des types MIME supportés.
- $name=$type=$data=false;
- $size=$offset=0;
Initialise les variables des arguments du POST.
- if (isset($_POST['file_name'])) {
- $name=$_POST['file_name'];
- }
- if (isset($_POST['file_size'])) {
- $size=$_POST['file_size'];
- }
- if (isset($_POST['file_type'])) {
- $type=$_POST['file_type'];
- }
- if (isset($_POST['file_offset'])) {
- $offset=$_POST['file_offset'];
- }
- if (isset($_POST['file_data'])) {
- $data=explode(';base64,', $_POST['file_data']);
- $data=is_array($data) && isset($data[1]) ? base64_decode($data[1]) : false;
- }
Extrait les arguments du POST. Le bloc de données encodé en BASE64 est décodé.
- if (!$name or !validate_filename($name)) {
- goto badrequest;
- }
- if (!$type or !in_array($type, $filetypes)) {
- goto badrequest;
- }
- if (!is_numeric($offset) or $offset < 0) {
- goto badrequest;
- }
- if (!is_numeric($size) or $size < 0 or ($maxfilesize and $size > $maxfilesize)) {
- goto badrequest;
- }
Vérifie les arguments du POST.
- if (!$data) {
- goto badrequest;
- }
- $datasize=strlen($data);
- if ($offset + $datasize > $size) {
- goto badrequest;
- }
Vérifie la taille des données envoyées dans le POST.
Obtient le nom complet du fichier dans l'espace de l'utilisateur.
- $fout = @fopen($file, $offset == 0 ? 'wb' : 'cb');
- if ($fout === false) {
- goto internalerror;
- }
Ouvre le fichier en mode binaire. Si le POST contient le premier bloc de données, le fichier est créé.
- $r = fseek($fout, $offset);
- if ($r == -1) {
- goto internalerror;
- }
Déplace le pointeur du fichier à la position du bloc de données.
- $r = fwrite($fout, $data);
- if ($r === false) {
- goto internalerror;
- }
Écrit le bloc de données.
- if ($offset + $datasize < $size) {
- return false;
- }
Retourne un simple en-tête 200 Ok
si plus de blocs de données sont attendus.
- if (!wall_init_file($wall_id, $name, $file, $type)) {
- goto internalerror;
- }
- return false;
Quand tout le fichier est sauvegardé, appelle wall_init_file
pour créer la vignette du fichier.
- badrequest:
- header('HTTP/1.1 400 Bad Request');
- return false;
Retourne un simple en-tête 400 Bad Request
.
- internalerror:
- @unlink($file);
- header('HTTP/1.1 500 Internal Error');
Retourne un simple en-tête 500 Internal Error
.
- require_once 'validatefilename.php';
- require_once 'models/wall.inc';
Inclut le code des fonctions validate_filename
, wall_file
et wall_file_tag
.
- function walldelete($lang, $arglist=false) {
Définit la fonction wallfiledelete
qui est appelée par l'URL /walldelete du site.
Les arguments $lang
et $arglist
ne sont pas utilisés.
Le contenu retourné par cette action est indépendant du langage de l'utilisateur.
Aucun argument ou paramètre n'est attendu dans l'URL.
- if (!isset($_SESSION['wall_id'])) {
- goto badrequest;
- }
- $wall_id=$_SESSION['wall_id'];
Extrait l'identifiant du mur de l'utilisateur enregistré dans la session.
Retourne une erreur HTTP 400 Bad Request
si un identifiant n'est pas trouvé dans la session.
- $name=false;
Initialise le nom du fichier en argument du POST.
- if (isset($_POST['file_name'])) {
- $name=$_POST['file_name'];
- }
Extrait le nom du fichier du POST.
- if (!$name or !validate_filename($name)) {
- goto badrequest;
- }
Vérifie le nom du fichier.
- $file=wall_file($wall_id, $name);
- if (!file_exists($file)) {
- goto badrequest;
- }
Cherche le fichier dans l'espace de l'utilisateur.
Retourne une erreur HTTP 400 Bad Request
le fichier spécifié n'est pas trouvé.
- $r = @unlink($file);
- if (!$r) {
- goto internalerror;
- }
Détruit le fichier.
- $file=wall_file_tag($wall_id, $name);
- $r = @unlink($file);
- if (!$r) {
- goto internalerror;
- }
- return false;
Détruit la vignette du fichier.
- badrequest:
- header('HTTP/1.1 400 Bad Request');
- return false;
Retourne un simple en-tête 400 Bad Request
.
- internalerror:
- header('HTTP/1.1 500 Internal Error');
- return false;
- }
Retourne un simple en-tête 500 Internal Error
.
Test
- <?php $debug=true; ?>
Mettre $debug
à true
permet d'accéder dans la console du navigateur à la variable wall
, l'instance de Wall.
Si $debug
vaut false
, tout le code en JavaScript est protégé par une fonction de fermeture.
- require_once 'models/wall.inc';
- if (!isset($_SESSION['wall_id'])) {
- $wall_id = uniqid();
- $_SESSION['wall_id'] = $wall_id;
- }
- else {
- $wall_id = $_SESSION['wall_id'];
- }
- wall_create_directory($wall_id);
- $filelist=wall_contents($wall_id);
Inclut le code des fonctions wall_create_directory
et wall_contents
.
Initialise l'identifiant du mur de l'utilisateur et l'enregistre dans la session, si nécessaire, sinon le récupère.
Crée le mur de l'utilisateur, i.e. crée l'espace disque de l'utilisateur, s'il n'est pas déjà créé.
Récupère la liste des fichiers sur le mur de l'utilisateur.
- <?php $tag_url='/walltag'; ?>
- <?php $file_url='/wallfile'; ?>
- <?php $upload_url='/wallupload'; ?>
- <?php $delete_url='/walldelete'; ?>
- <?php $filetypes=array('image/jpeg', 'image/png', 'image/gif', 'application/pdf'); ?>
- <?php $maxfilesize=5*1000000; ?>
- <?php $chunksize=100000; ?>
- <?php $maxfiles=10; ?>
Configure les options du mur. Adaptez les URL à votre environnement.
- <?php $id=uniqid('id'); ?>
Définit l'identifiant de la <div>
qui encadre le HTML du mur.
- <div id="<?php echo $id; ?>" class="noprint">
- <div class="ojs_wall">
- <div class="tags"></div>
- <div class="filecontrols">
- <span class="fileupload"><i class="fas fa-lg fa-fw fa-upload"></i></span>
- <span class="filedelete"><i class="fas fa-lg fa-fw fa-trash"></i></span>
- <span class="filedownload"><i class="fas fa-lg fa-fw fa-download"></i></span>
- <span class="filestatus"></span>
- </div>
- <input type="file" class="fileinput"/>
- </div>
- </div>
Crée les widgets de l'interface. Notez les noms des classes des différents composants qui sont attendus par la méthode setWidget
d'un mur.
- <?php head('javascript', '/objectivejs/Objective.js'); ?>
- <?php head('javascript', '/objectivejs/Responder.js'); ?>
- <?php head('javascript', '/objectivejs/View.js'); ?>
- <?php head('javascript', '/objectivejs/Wall.js'); ?>
Inclut le code de toutes les classes nécessaires.
RAPPEL : La fonction head
de la librairie iZend ajoute une balise telle que <script src="/objectivejs/Objective.js"></script>
à la section <head>
du document HTML. Adaptez le code à votre environnement de développement.
- <?php if (!$debug): ?>
- (function() {
- <?php endif; ?>
Isole tout le code en JavaScript dans une fonction de fermeture si $debug
vaut false
.
- const options = {
- draganddrop: true,
- maxfiles: <?php echo $maxfiles; ?>,
- maxfilesize: <?php echo $maxfilesize; ?>,
- filetypes: [<?php echo implode(',', array_map(function($s) { return "'$s'"; }, $filetypes)); ?>],
- tagURL: '<?php echo $tag_url; ?>',
- fileURL: '<?php echo $file_url; ?>',
- deleteURL: '<?php echo $delete_url; ?>',
- uploadURL: '<?php echo $upload_url; ?>',
- chunksize: <?php echo $chunksize; ?>
- }
- const wall = new Wall(options);
Crée une instance de Wall avec les options souhaitées.
- const container = document.querySelector('#<?php echo $id; ?>');
Récupère la <div>
qui encadre le HTML du programme.
- wall.setManagedWidget(container.querySelector('.ojs_wall')).resetWidget();
Crée et affiche l'interface du mur.
- const filelist = [<?php echo implode(',', array_map(function($s) { return "'$s'"; }, $filelist)); ?>];
- wall.files = filelist;
Configure et affiche les vignettes du mur à partir de la liste des fichiers de l'utilisateur.
- <?php if (!$debug): ?>
- })();
- <?php endif; ?>
Ferme la fonction qui isole le code en JavaScript si $debug
vaut false
.
Commentaires