85

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.

Objective
  • Responder
    • View
      • Wall

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.

<p class="noprint"><button id="btn_upload_file" type="submit" class="narrow" title=""><i class="fa fa-upload"></i></button></p>
<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"]
  1. function Wall(options = false) {
  2.     options = options || {};
  3.  
  4.     let draganddrop = options.draganddrop ? true : false;
  5.  
  6.     let tagURL = options.tagURL;
  7.  
  8.     let deleteURL = options.deleteURL;
  9.     let uploadURL = options.uploadURL;
  10.  
  11.     let fileURL = options.fileURL;
  12.  
  13.     if (! (typeof tagURL === 'undefined' || tagURL === null || typeof tagURL === 'string'))
  14.         throw new TypeError();
  15.  
  16.     if (! (typeof deleteURL === 'undefined' || deleteURL === null || typeof deleteURL === 'string'))
  17.         throw new TypeError();
  18.  
  19.     if (! (typeof uploadURL === 'undefined' || uploadURL === null || typeof uploadURL === 'string'))
  20.         throw new TypeError();
  21.  
  22.     if (! (typeof fileURL === 'undefined' || fileURL === null || typeof fileURL === 'string'))
  23.         throw new TypeError();
  24.  
  25.     if (uploadURL) {
  26.         let filetypes = options.filetypes;
  27.  
  28.         if (!Array.isArray(filetypes) && filetypes.length > 0 && filetypes.every((e) => typeof e === 'string'))
  29.             throw new TypeError();
  30.  
  31.         this._filetypes = filetypes;
  32.  
  33.         let maxfiles = options.maxfiles;
  34.  
  35.         if (maxfiles === undefined)
  36.             maxfiles = 10;
  37.         else if (!Number.isInteger(maxfiles))
  38.             throw new TypeError();
  39.         else if (maxfiles < 1)
  40.             throw new RangeError();
  41.  
  42.         this._maxfiles = maxfiles;
  43.  
  44.         let maxfilesize = options.maxfilesize;
  45.  
  46.         if (maxfilesize === undefined)
  47.             maxfilesize = 1000000;
  48.         else if (!Number.isInteger(maxfilesize))
  49.             throw new TypeError();
  50.         else if (maxfilesize < 100000)
  51.             throw new RangeError();
  52.  
  53.         this._maxfilesize = maxfilesize;
  54.  
  55.         let chunksize = options.chunksize;
  56.  
  57.         if (chunksize === undefined)
  58.             chunksize = 100000;
  59.         else if (!Number.isInteger(chunksize))
  60.             throw new TypeError();
  61.         else if (chunksize < 10000)
  62.             throw new RangeError();
  63.  
  64.         this._chunksize = chunksize;
  65.     }
  66.  
  67.     View.call(this);
  68.  
  69.     this._draganddrop = draganddrop;
  70.  
  71.     this._tagURL = tagURL;
  72.  
  73.     this._deleteURL = deleteURL;
  74.     this._uploadURL = uploadURL;
  75.  
  76.     this._fileURL = fileURL;
  77.  
  78.     this._tagsWidget = null;
  79.  
  80.     this._deleteWidget = null;
  81.     this._uploadWidget = null;
  82.     this._downloadWidget = null;
  83.     this._statusWidget = null;
  84.  
  85.     this._uploading = false;
  86.  
  87.     this._slots = {};
  88.  
  89.     this._tag = null;
  90.  
  91.     this._error = null;
  92. }
  93.  
  94. Wall.prototype = Object.create(View.prototype);
  95.  
  96. 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.

  1. Object.defineProperty(Wall.prototype, 'files', {
  2.     get:    function() {
  3.         return Object.keys(this._slots);
  4.     },
  5.     set:    function(filelist) {
  6.         let slots = {};
  7.  
  8.         if (filelist) {
  9.             if (!Array.isArray(filelist))
  10.                 throw new TypeError();
  11.  
  12.             for (let filename of filelist) {
  13.                 if (typeof filename !== 'string')
  14.                     throw new TypeError();
  15.  
  16.                 slots[filename] = null;
  17.             }
  18.         }
  19.  
  20.         this._slots = slots;
  21.  
  22.         if (this._tagsWidget)
  23.             this.resetTagsWidget();
  24.  
  25.         if (this.interfaced())
  26.             this.resetWidget();
  27.     }
  28. });

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.

  1. Object.defineProperty(Wall.prototype, 'tag', {
  2.     get:    function() {
  3.         return this._tag;
  4.     }
  5. });

tag est un accesseur qui retourne le nom du fichier sélectionné dans this.

  1. Wall.prototype.resetTagsWidget = function() {
  2.     this._tagsWidget.innerHTML = '';
  3.  
  4.     for (let filename in this._slots) {
  5.         const img = document.createElement('img');
  6.  
  7.         img.src = `${this._tagURL}/${encodeURI(filename)}`;
  8.  
  9.         img.title = filename;
  10.  
  11.         img.addEventListener('click', (e) => this._clickImage(e, filename));
  12.  
  13.         this._slots[filename] = img;
  14.  
  15.         this._tagsWidget.appendChild(img);
  16.     }
  17.  
  18.     return this;
  19. };

resetTagsWidget affiche les vignettes des fichiers gérés par this.

  1. Wall.prototype.resetWidget = function() {
  2.     if (this._uploadWidget) {
  3.         if (!this._uploading && Object.keys(this._slots).length < this._maxfiles)
  4.             this._uploadWidget.classList.remove('disabled');
  5.         else
  6.             this._uploadWidget.classList.add('disabled');
  7.  
  8.         if (this._error == 'upload')
  9.             this._uploadWidget.classList.add('inerror');
  10.         else
  11.             this._uploadWidget.classList.remove('inerror');
  12.     }
  13.  
  14.     if (this._deleteWidget) {
  15.         if (!this._uploading && this._tag)
  16.             this._deleteWidget.classList.remove('disabled');
  17.         else
  18.             this._deleteWidget.classList.add('disabled');
  19.  
  20.         if (this._error == 'delete')
  21.             this._deleteWidget.classList.add('inerror');
  22.         else
  23.             this._deleteWidget.classList.remove('inerror');
  24.     }
  25.  
  26.     if (this._downloadWidget) {
  27.         if (this._tag)
  28.             this._downloadWidget.classList.remove('disabled');
  29.         else
  30.             this._downloadWidget.classList.add('disabled');
  31.     }
  32.  
  33.     return this;
  34. };

resetWidget rend actif ou inactif les différents boutons de contrôle de l'interface selon la configuration et l'état de this.

  1. Wall.prototype.setWidget = function(w) {
  2.     View.prototype.setWidget.call(this, w);
  3.  
  4.     this._tagsWidget = this._tagURL ? w.querySelector('.tags') : null;
  5.  
  6.     this._fileWidget = this._uploadURL ? w.querySelector('.fileinput') : null;
  7.  
  8.     if (this._fileWidget && this._fileWidget.tagName != 'INPUT')
  9.         this._fileWidget = null;
  10.  
  11.     this._uploadWidget = w.querySelector('.fileupload');
  12.     this._deleteWidget = w.querySelector('.filedelete');
  13.     this._downloadWidget = w.querySelector('.filedownload');
  14.     this._statusWidget = w.querySelector('.filestatus');
  15.  
  16.     if (this._draganddrop && this._tagsWidget) {
  17.         this._tagsWidget.addEventListener('drop', (e) => {
  18.             const dt = e.dataTransfer;
  19.  
  20.             e.preventDefault();
  21.  
  22.             if (dt.types.indexOf('Files') != -1) {
  23.                 this._uploadFile(dt.files[0]);
  24.             }
  25.         });
  26.  
  27.         this._tagsWidget.addEventListener('dragenter', (e) => {
  28.             const dt = e.dataTransfer;
  29.  
  30.             if (dt.types.indexOf('Files') != -1) {
  31.                 e.preventDefault();
  32.             }
  33.         });
  34.  
  35.         this._tagsWidget.addEventListener('dragleave', (e) => {
  36.             e.preventDefault();
  37.         });
  38.  
  39.         this._tagsWidget.addEventListener('dragover', (e) => {
  40.             const dt = e.dataTransfer;
  41.  
  42.             e.preventDefault();
  43.  
  44.             dt.dropEffect = dt.types.indexOf('Files') != -1 && !this._uploading && Object.keys(this._slots).length < this._maxfiles ? 'copy' : 'none';
  45.         });
  46.     }
  47.  
  48.     if (this._fileWidget)
  49.         this._fileWidget.hidden = true;
  50.  
  51.     if (this._uploadWidget) {
  52.         this._uploadWidget.classList.add('disabled');
  53.  
  54.         if (this._uploadURL && this._fileWidget) {
  55.             this._uploadWidget.addEventListener('click', () => {
  56.                 if (!this._uploadWidget.classList.contains('disabled'))
  57.                     this._fileWidget.click();
  58.             });
  59.  
  60.             this._fileWidget.addEventListener('change', (e) => {
  61.                 if (e.target.value) {
  62.                     this._uploadFile(e.target.files[0]);
  63.                 }
  64.             });
  65.         }
  66.         else
  67.             this._uploadWidget = null;
  68.     }
  69.  
  70.     if (this._deleteWidget) {
  71.         this._deleteWidget.classList.add('disabled');
  72.  
  73.         if (this._deleteURL) {
  74.             this._deleteWidget.addEventListener('click', () => {
  75.                 if (!this._deleteWidget.classList.contains('disabled'))
  76.                     this.deleteFile();
  77.             });
  78.         }
  79.         else
  80.             this._deleteWidget = null;
  81.     }
  82.  
  83.     if (this._downloadWidget) {
  84.         this._downloadWidget.classList.add('disabled');
  85.  
  86.         if (this._fileURL) {
  87.             this._downloadWidget.addEventListener('click', () => {
  88.                 if (!this._downloadWidget.classList.contains('disabled'))
  89.                     this.downloadFile();
  90.             });
  91.         }
  92.         else
  93.             this._downloadWidget = null;
  94.     }
  95.  
  96.     return this;
  97. };

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.

  1. Wall.prototype.destroyWidget = function() {
  2.     View.prototype.destroyWidget.call(this);
  3.  
  4.     if (this._tagsWidget) {
  5.         for (let filename in this._slots)
  6.             this._slots[filename] = null;
  7.     }
  8.  
  9.     this._tagsWidget = null;
  10.  
  11.     this._deleteWidget = null;
  12.  
  13.     this._uploadWidget = null;
  14.     this._fileWidget = null;
  15.  
  16.     this._downloadWidget = null;
  17.  
  18.     this._statusWidget = null;
  19.  
  20.     this._tag = null;
  21.  
  22.     return this;
  23. };

destroyWidget redéfinit la méthode héritée de la classe View. Elle met tous les widgets de l'interface à null.

  1. Wall.prototype.uploadFile = function() {
  2.     if (this._uploading)
  3.         return this;
  4.  
  5.     if (this._fileWidget)
  6.         this._fileWidget.click();
  7.  
  8.     return this;
  9. };

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.

  1. Wall.prototype._uploadFile = function(fd) {
  2.     if (!this._uploadURL)
  3.         return this;
  4.  
  5.     const filename = fd.name;
  6.     const filesize = fd.size;
  7.     const filetype = fd.type;
  8.  
  9.     if (filename in this._slots || Object.keys(this._slots).length >= this._maxfiles) {
  10.         this._error = 'upload';
  11.  
  12.         if (this.interfaced())
  13.             this.resetWidget();
  14.  
  15.         return this;
  16.     }
  17.  
  18.     if ((this._filetypes && this._filetypes.indexOf(filetype) == -1) || (this._maxfilesize && filesize > this._maxfilesize)) {
  19.         this._error = 'upload';
  20.  
  21.         if (this.interfaced())
  22.             this.resetWidget();
  23.  
  24.         return this;
  25.     }
  26.  
  27.     const uploadurl = this._uploadURL;
  28.     const chunksize = this._chunksize;
  29.  
  30.     const filereader = new FileReader();
  31.  
  32.     filereader.onloadend = (e) => postdata(e.target.result);
  33.  
  34.     let offset = 0, progress = 0, blob;
  35.  
  36.     const uploadslice = () => {
  37.         blob = fd.slice(offset, offset + chunksize);
  38.         filereader.readAsDataURL(blob);
  39.  
  40.         if (this._statusWidget) {
  41.             progress = Math.floor(((offset + blob.size) / filesize) * 100);
  42.             this._statusWidget.innerText = `${progress}%`;
  43.         }
  44.  
  45.     };
  46.  
  47.     const postdata = (data) => {
  48.         $.post(uploadurl, {file_name: filename, file_size: filesize, file_type: filetype, file_offset: offset, file_data: data})
  49.             .done(() => {
  50.                 offset += blob.size;
  51.  
  52.                 if (offset < filesize)
  53.                     uploadslice();
  54.                 else {
  55.                     if (this._statusWidget)
  56.                         this._statusWidget.innerText = '';
  57.  
  58.                     this._slots[filename] = null;
  59.  
  60.                     this.respondTo('wallFileAdded', this, filename);
  61.  
  62.                     if (this._tagsWidget) {
  63.                         const img = document.createElement('img');
  64.  
  65.                         img.src = `${this._tagURL}/${encodeURI(filename)}?nocache=${Date.now()}`;
  66.  
  67.                         img.title = filename;
  68.  
  69.                         img.addEventListener('click', (e) => this._clickImage(e, filename));
  70.  
  71.                         this._slots[filename] = img;
  72.  
  73.                         this._tagsWidget.appendChild(img);
  74.                     }
  75.  
  76.                     this._uploading = false;
  77.  
  78.                     this._error = null;
  79.  
  80.                     if (this.interfaced())
  81.                         this.resetWidget();
  82.                 }
  83.             })
  84.             .fail(() => {
  85.                 if (this._statusWidget)
  86.                     this._statusWidget.innerText = '';
  87.  
  88.                 this._uploading = false;
  89.  
  90.                 this._error = 'upload';
  91.  
  92.                 if (this.interfaced())
  93.                     this.resetWidget();
  94.             });
  95.     };
  96.  
  97.     this._uploading = true;
  98.  
  99.     if (this.interfaced())
  100.         this.resetWidget();
  101.  
  102.     uploadslice();
  103.  
  104.     return this;
  105. };

_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.

  1. Wall.prototype.deleteFile = function() {
  2.     if (!this._deleteURL)
  3.         return this;
  4.  
  5.     if (!this._tag)
  6.         return this;
  7.  
  8.     if (this._uploading)
  9.         return this;
  10.  
  11.     const deleteurl = this._deleteURL;
  12.  
  13.     const filename = this._tag;
  14.  
  15.     const deletefile = () => {
  16.         $.post(deleteurl, {file_name: filename} )
  17.             .done(() => {
  18.                 if (this._slots[filename])
  19.                     this._slots[filename].remove();
  20.  
  21.                 delete this._slots[filename];
  22.  
  23.                 this.respondTo('wallFileDeleted', this, filename);
  24.  
  25.                 if (this._tag == filename) {
  26.                     this._tag = null;
  27.  
  28.                     this.respondTo('wallSelectionChanged', this);
  29.                 }
  30.  
  31.                 this._error = null;
  32.  
  33.                 if (this.interfaced())
  34.                     this.resetWidget();
  35.             })
  36.             .fail(() => {
  37.                 this._error = 'delete';
  38.  
  39.                 if (this.interfaced())
  40.                     this.resetWidget();
  41.             });
  42.     };
  43.  
  44.     deletefile();
  45.  
  46.     return this;
  47. };

deleteFile demande au serveur de détruire le fichier qui a été sélectionné sur le mur.

  1. Wall.prototype.downloadFile = function() {
  2.     if (!this._fileURL)
  3.         return this;
  4.  
  5.     if (!this._tag)
  6.         return this;
  7.  
  8.     const url = `${this._fileURL}/${encodeURI(this._tag)}`;
  9.  
  10.     window.open(url);
  11.  
  12.     return this;
  13. };

downloadFile demande au navigateur de récupérer le fichier qui a été sélectionné sur le mur.

  1. Wall.prototype.selectTag = function(filename) {
  2.     if (this._tag === filename)
  3.         return this;
  4.  
  5.     if (this._slots[filename] === undefined)
  6.         return this;
  7.  
  8.     if (this._tag && this._slots[this._tag])
  9.         this._slots[this._tag].classList.remove('selected');
  10.  
  11.     this._tag = filename;
  12.  
  13.     if (this._slots[this._tag])
  14.         this._slots[this._tag].classList.add('selected');
  15.  
  16.     if (this.interfaced())
  17.         this.resetWidget();
  18.  
  19.     return this;
  20. };

selectTag sélectionne le fichier spécifié par id.

  1. Wall.prototype.unselectTag = function() {
  2.     if (!this._tag)
  3.         return this;
  4.  
  5.     if (this._slots[this._tag])
  6.         this._slots[this._tag].classList.remove('selected');
  7.  
  8.     this._tag = null;
  9.  
  10.     if (this.interfaced())
  11.         this.resetWidget();
  12.  
  13.     return this;
  14. };

unselectTag désélectionne le fichier qui est sélectionné.

  1. Wall.prototype._clickImage = function(e, filename) {
  2.     if (e.shiftKey) {
  3.         if (!this._fileURL)
  4.             return;
  5.  
  6.         const url = `${this._fileURL}/${encodeURI(filename)}`;
  7.  
  8.         window.open(url);
  9.     }
  10.     else {
  11.         if (this._tag == filename)
  12.             this.unselectTag();
  13.         else
  14.             this.selectTag(filename);
  15.  
  16.         this.respondTo('wallSelectionChanged', this);
  17.     }
  18.  
  19. };

_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 :

XSendFile On
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.

  1. require_once 'dirclear.php';

Inclut le code de la fonction dirclear.

function dirclear($dir='.') {
    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.

  1. define('WALL_DIR', ROOT_DIR . DIRECTORY_SEPARATOR . 'wall');
  2. define('WALL_DIR_MODE', 0775);
  3.  
  4. define('WALL_NFILES', 20);
  5. define('WALL_FILE_MAX_SIZE', 5*1000000);
  6.  
  7. define('WALL_FILE_TYPES', array(
  8.     'application/pdf',
  9.     'image/jpeg',
  10.     'image/png',
  11.     'image/gif'
  12. ));
  13.  
  14. define('WALL_TAG_WIDTH', 180);
  15.  
  16. 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.

  1. 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.

  1. function wall_directory($wall_id) {
  2.     return WALL_DIR . DIRECTORY_SEPARATOR . $wall_id;
  3. }
  4.  
  5. function wall_tags_directory($wall_id) {
  6.     return wall_directory($wall_id) . DIRECTORY_SEPARATOR . 'tags';
  7. }

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.

  1. function wall_create_directory($wall_id) {
  2.     $dir=wall_directory($wall_id);
  3.  
  4.     if (file_exists($dir)) {
  5.         return true;
  6.     }
  7.  
  8.     if (!@mkdir($dir, WALL_DIR_MODE)) {
  9.         return false;
  10.     }
  11.  
  12.     $subdir = wall_tags_directory($wall_id);
  13.  
  14.     if (!@mkdir($subdir, WALL_DIR_MODE)) {
  15.         return false;
  16.     }
  17.  
  18.     return true;
  19. }

wall_create_directory vérifie si le dossier $wall_id existe et si non, le crée avec le sous-dossier pour les vignettes.

  1. function wall_delete_directory($wall_id) {
  2.     $dir=wall_directory($wall_id);
  3.  
  4.     dirclear($dir);
  5.  
  6.     return @rmdir($dir);
  7. }

wall_delete_directory détruit tout le contenu du dossier $wall_id.

  1. function wall_contents($wall_id, $sort=true) {
  2.     $filelist=false;
  3.  
  4.     $dir=wall_directory($wall_id);
  5.     $files=glob($dir . DIRECTORY_SEPARATOR . '*.*', GLOB_NOSORT);
  6.  
  7.     if ($files) {
  8.         if ($sort) {
  9.             usort($files, function($f1, $f2) {
  10.                 $t1=filemtime($f1);
  11.                 $t2=filemtime($f2);
  12.  
  13.                 if ($t1 == $t2)  {
  14.                     return 0;
  15.                 }
  16.  
  17.                 return ($t1 < $t2) ? -1 : 1;
  18.             });
  19.         }
  20.  
  21.         $filelist=array();
  22.         foreach ($files as $f) {
  23.             $filelist[]=pathinfo($f, PATHINFO_BASENAME);
  24.         }
  25.     }
  26.  
  27.     return $filelist;
  28. }

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.

  1. function wall_file($wall_id, $fname) {
  2.     return wall_directory($wall_id) . DIRECTORY_SEPARATOR . $fname;
  3. }
  4.  
  5. function wall_file_tag($wall_id, $fname) {
  6.     return wall_tags_directory($wall_id) . DIRECTORY_SEPARATOR . str_replace('.', '_', $fname) . '.png';
  7. }

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.

  1. function wall_tag_pdf($wall_id, $fname, $pdffile) {
  2.     $tmpdir=ini_get('upload_tmp_dir') ?: ROOT_DIR . DIRECTORY_SEPARATOR . 'tmp';
  3.     $tmpfile=$tmpdir . DIRECTORY_SEPARATOR . uniqid('pdf') . '.png';
  4.  
  5.     $cmdline=sprintf(GS_COVER, $tmpfile, $pdffile);
  6.  
  7.     @exec($cmdline, $output, $ret);
  8.  
  9.     if ($ret != 0) {
  10.         return false;
  11.     }
  12.  
  13.     $r = wall_create_tag($wall_id, $fname, $tmpfile);
  14.  
  15.     @unlink($tmpfile);
  16.  
  17.     return $r;
  18. }

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.

  1. function wall_tag_img($wall_id, $fname, $imgfile) {
  2.     return  wall_create_tag($wall_id, $fname, $imgfile);
  3. }

wall_tag_img crée la vignette du fichier $fname dans $wall_id avec l'image dans $imgfile.

  1. function wall_create_tag($wall_id, $fname, $imgfile) {
  2.     $fileinfo=@getimagesize($imgfile);
  3.  
  4.     if (!$fileinfo) {
  5.         return false;
  6.     }
  7.  
  8.     $width=$fileinfo[0];
  9.     $height=$fileinfo[1];
  10.  
  11.     $imgtype=$fileinfo[2];
  12.  
  13.     $img=false;
  14.  
  15.     switch ($imgtype) {
  16.         case IMAGETYPE_JPEG:
  17.             $img=imagecreatefromjpeg($imgfile);
  18.             break;
  19.         case IMAGETYPE_PNG:
  20.             $img=imagecreatefrompng($imgfile);
  21.             break;
  22.         case IMAGETYPE_GIF:
  23.             $img=imagecreatefromgif($imgfile);
  24.             break;
  25.         default:
  26.             break;
  27.     }
  28.  
  29.     if (!$img) {
  30.         return false;
  31.     }
  32.  
  33.     if ($imgtype == IMAGETYPE_JPEG) {
  34.         $exif = @exif_read_data($imgfile);
  35.  
  36.         if (!empty($exif['Orientation'])) {
  37.             switch ($exif['Orientation']) {
  38.                 case 3:
  39.                     $deg=180;
  40.                     break;
  41.                 case 6:
  42.                     $deg=-90;
  43.                     break;
  44.                 case 8:
  45.                     $deg=90;
  46.                     break;
  47.                 default:
  48.                     $deg=0;
  49.                     break;
  50.             }
  51.  
  52.             if ($deg != 0) {
  53.                 $img = imagerotate($img, $deg, 0);
  54.  
  55.                 if ($deg=90 or $deg=-90) {
  56.                     $width=$fileinfo[1];
  57.                     $height=$fileinfo[0];
  58.                 }
  59.             }
  60.         }
  61.     }
  62.  
  63.     if ($width > WALL_TAG_WIDTH) {
  64.         $w=WALL_TAG_WIDTH;
  65.         $h=$height * (WALL_TAG_WIDTH / $width);
  66.     }
  67.     else {
  68.         $w=$width;
  69.         $h=$height;
  70.     }
  71.  
  72.     $imgcopy = imagecreatetruecolor($w, $h);
  73.  
  74.     if ($imgtype == IMAGETYPE_PNG or $imgtype == IMAGETYPE_GIF) {
  75.         $bg=imagecolorallocate($imgcopy, 255, 255, 255);
  76.         imagefill($imgcopy, 0, 0, $bg);
  77.     }
  78.  
  79.     if ($width > WALL_TAG_WIDTH) {
  80.         imagecopyresampled($imgcopy, $img, 0, 0, 0, 0, $w, $h, $width, $height);
  81.     }
  82.     else {
  83.         imagecopy($imgcopy, $img, 0, 0, 0, 0, $w, $h);
  84.     }
  85.  
  86.     $filecopy=wall_file_tag($wall_id, $fname);
  87.  
  88.     if (!@imagepng($imgcopy, $filecopy, PNG_QUALITY))  {
  89.         return false;
  90.     }
  91.  
  92.     clearstatcache(true, $filecopy);
  93.  
  94.     imagedestroy($imgcopy);
  95.  
  96.     return true;
  97. }

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.

  1. function wall_init_file($wall_id, $fname, $file, $mimetype) {
  2.     switch ($mimetype) {
  3.         case 'image/jpeg':
  4.         case 'image/png':
  5.         case 'image/gif':
  6.             return wall_tag_img($wall_id, $fname, $file);
  7.  
  8.         case 'application/pdf':
  9.             return wall_tag_pdf($wall_id, $fname, $file);
  10.  
  11.         default:
  12.             break;
  13.     }
  14.  
  15.     return false;
  16. }

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.

  1. require_once 'filemimetype.php';

Inclut le code de la fonction file_mime_type.

function file_mime_type($file, $encoding=true) {
    $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.

  1. require_once 'validatefilename.php';

Inclut le code de la fonction validate_filename.

function validate_filename($name) {
    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).

function validate_filename($name) {
    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.

  1. require_once 'models/wall.inc';

Inclut le code de la fonction wall_file.

  1. 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.

  1.     if (!isset($_SESSION['wall_id'])) {
  2.         goto badrequest;
  3.     }
  4.  
  5.     $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.

  1.     $name=false;
  2.  
  3.     if (is_array($arglist)) {
  4.         if (isset($arglist[0])) {
  5.             $name=$arglist[0];
  6.         }
  7.     }
  8.  
  9.     if (!$name or !validate_filename($name)) {
  10.         goto badrequest;
  11.     }
  12.  
  13.     $file=wall_file($wall_id, $name);
  14.  
  15.     if (!file_exists($file)) {
  16.         goto notfound;
  17.     }

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.

  1.     $filesize=filesize($file);
  2.     $filetype=file_mime_type($file);
  3.     if (!$filetype) {
  4.         $filetype = 'application/octet-stream';
  5.     }

Calcule la taille et le type du fichier demandé.

  1.     header("X-Sendfile: $file");
  2.     header("Content-Type: $filetype");
  3.     header("Content-Length: $filesize");
  4.  
  5.     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é.

  1. badrequest:
  2.     header('HTTP/1.1 400 Bad Request');
  3.     return false;

Retourne un simple en-tête 400 Bad Request.

  1.     header("Content-Type: $filetype");
  2.     header("Content-Length: $filesize");
  3.  
  4.     return false;

Retourne un simple en-tête 404 Not Found.

  1. 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.

  1. require_once 'validatefilename.php';
  2. 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.

  1. 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.

  1.     if (!isset($_SESSION['wall_id'])) {
  2.         goto badrequest;
  3.     }
  4.  
  5.     $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.

  1.     $maxfilesize=WALL_FILE_MAX_SIZE;
  2.     $filetypes=WALL_FILE_TYPES;

Initialise $maxfilesize à WALL_FILE_MAX_SIZE et filetypes à WAL_FILE_TYPES, la liste des types MIME supportés.

  1.     $name=$type=$data=false;
  2.     $size=$offset=0;

Initialise les variables des arguments du POST.

  1.     if (isset($_POST['file_name'])) {
  2.         $name=$_POST['file_name'];
  3.     }
  4.     if (isset($_POST['file_size'])) {
  5.         $size=$_POST['file_size'];
  6.     }
  7.     if (isset($_POST['file_type'])) {
  8.         $type=$_POST['file_type'];
  9.     }
  10.     if (isset($_POST['file_offset'])) {
  11.         $offset=$_POST['file_offset'];
  12.     }
  13.     if (isset($_POST['file_data'])) {
  14.         $data=explode(';base64,', $_POST['file_data']);
  15.         $data=is_array($data) && isset($data[1]) ? base64_decode($data[1]) : false;
  16.     }

Extrait les arguments du POST. Le bloc de données encodé en BASE64 est décodé.

  1.     if (!$name or !validate_filename($name)) {
  2.         goto badrequest;
  3.     }
  4.  
  5.     if (!$type or !in_array($type, $filetypes)) {
  6.         goto badrequest;
  7.     }
  8.  
  9.     if (!is_numeric($offset) or $offset < 0) {
  10.         goto badrequest;
  11.     }
  12.  
  13.     if (!is_numeric($size) or $size < 0 or ($maxfilesize and $size > $maxfilesize)) {
  14.         goto badrequest;
  15.     }

Vérifie les arguments du POST.

  1.     if (!$data) {
  2.         goto badrequest;
  3.     }
  4.  
  5.     $datasize=strlen($data);
  6.  
  7.     if ($offset + $datasize > $size) {
  8.         goto badrequest;
  9.     }

Vérifie la taille des données envoyées dans le POST.

Obtient le nom complet du fichier dans l'espace de l'utilisateur.

  1.     $fout = @fopen($file, $offset == 0 ? 'wb' : 'cb');
  2.  
  3.     if ($fout === false) {
  4.         goto internalerror;
  5.     }

Ouvre le fichier en mode binaire. Si le POST contient le premier bloc de données, le fichier est créé.

  1.     $r = fseek($fout, $offset);
  2.  
  3.     if ($r == -1) {
  4.         goto internalerror;
  5.     }

Déplace le pointeur du fichier à la position du bloc de données.

  1.     $r = fwrite($fout, $data);
  2.  
  3.     if ($r === false) {
  4.         goto internalerror;
  5.     }

Écrit le bloc de données.

  1.     if ($offset + $datasize < $size) {
  2.         return false;
  3.     }

Retourne un simple en-tête 200 Ok si plus de blocs de données sont attendus.

  1.     if (!wall_init_file($wall_id, $name, $file, $type)) {
  2.         goto internalerror;
  3.     }
  4.  
  5.     return false;

Quand tout le fichier est sauvegardé, appelle wall_init_file pour créer la vignette du fichier.

  1. badrequest:
  2.     header('HTTP/1.1 400 Bad Request');
  3.     return false;

Retourne un simple en-tête 400 Bad Request.

  1. internalerror:
  2.     @unlink($file);
  3.  
  4.     header('HTTP/1.1 500 Internal Error');

Retourne un simple en-tête 500 Internal Error.

  1. require_once 'validatefilename.php';
  2. require_once 'models/wall.inc';

Inclut le code des fonctions validate_filename, wall_file et wall_file_tag.

  1. 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.

  1.     if (!isset($_SESSION['wall_id'])) {
  2.         goto badrequest;
  3.     }
  4.  
  5.     $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.

  1.     $name=false;

Initialise le nom du fichier en argument du POST.

  1.     if (isset($_POST['file_name'])) {
  2.         $name=$_POST['file_name'];
  3.     }

Extrait le nom du fichier du POST.

  1.     if (!$name or !validate_filename($name)) {
  2.         goto badrequest;
  3.     }

Vérifie le nom du fichier.

  1.     $file=wall_file($wall_id, $name);
  2.  
  3.     if (!file_exists($file)) {
  4.         goto badrequest;
  5.     }

Cherche le fichier dans l'espace de l'utilisateur. Retourne une erreur HTTP 400 Bad Request le fichier spécifié n'est pas trouvé.

  1.     $r = @unlink($file);
  2.  
  3.     if (!$r) {
  4.         goto internalerror;
  5.     }

Détruit le fichier.

  1.     $file=wall_file_tag($wall_id, $name);
  2.  
  3.     $r = @unlink($file);
  4.  
  5.     if (!$r) {
  6.         goto internalerror;
  7.     }
  8.  
  9.     return false;

Détruit la vignette du fichier.

  1. badrequest:
  2.     header('HTTP/1.1 400 Bad Request');
  3.     return false;

Retourne un simple en-tête 400 Bad Request.

  1. internalerror:
  2.     header('HTTP/1.1 500 Internal Error');
  3.     return false;
  4. }

Retourne un simple en-tête 500 Internal Error.

Test
  1. <?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.

  1. require_once 'models/wall.inc';
  2.  
  3. if (!isset($_SESSION['wall_id'])) {
  4.     $wall_id = uniqid();
  5.  
  6.     $_SESSION['wall_id'] = $wall_id;
  7.  
  8. }
  9. else {
  10.     $wall_id = $_SESSION['wall_id'];
  11. }
  12.  
  13. wall_create_directory($wall_id);
  14.  
  15. $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.

  1. <?php $tag_url='/walltag'; ?>
  2. <?php $file_url='/wallfile'; ?>
  3. <?php $upload_url='/wallupload'; ?>
  4. <?php $delete_url='/walldelete'; ?>
  5. <?php $filetypes=array('image/jpeg', 'image/png', 'image/gif', 'application/pdf'); ?>
  6. <?php $maxfilesize=5*1000000; ?>
  7. <?php $chunksize=100000; ?>
  8. <?php $maxfiles=10; ?>

Configure les options du mur. Adaptez les URL à votre environnement.

  1. <?php $id=uniqid('id'); ?>

Définit l'identifiant de la <div> qui encadre le HTML du mur.

  1. <div id="<?php echo $id; ?>" class="noprint">
  2. <div class="ojs_wall">
  3. <div class="tags"></div>
  4. <div class="filecontrols">
  5. <span class="fileupload"><i class="fas fa-lg fa-fw fa-upload"></i></span>
  6. <span class="filedelete"><i class="fas fa-lg fa-fw fa-trash"></i></span>
  7. <span class="filedownload"><i class="fas fa-lg fa-fw fa-download"></i></span>
  8. <span class="filestatus"></span>
  9. </div>
  10. <input type="file" class="fileinput"/>
  11. </div>
  12. </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.

  1. <?php head('javascript', '/objectivejs/Objective.js'); ?>
  2. <?php head('javascript', '/objectivejs/Responder.js'); ?>
  3. <?php head('javascript', '/objectivejs/View.js'); ?>
  4. <?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.

  1. <?php if (!$debug): ?>
  2. (function() {
  3. <?php endif; ?>

Isole tout le code en JavaScript dans une fonction de fermeture si $debug vaut false.

  1.     const options = {
  2.         draganddrop: true,
  3.         maxfiles: <?php echo $maxfiles; ?>,
  4.         maxfilesize: <?php echo $maxfilesize; ?>,
  5.         filetypes: [<?php echo implode(',', array_map(function($s) { return "'$s'"; }, $filetypes)); ?>],
  6.         tagURL: '<?php echo $tag_url; ?>',
  7.         fileURL: '<?php echo $file_url; ?>',
  8.         deleteURL: '<?php echo $delete_url; ?>',
  9.         uploadURL: '<?php echo $upload_url; ?>',
  10.         chunksize: <?php echo $chunksize; ?>
  11.     }
  12.  
  13.     const wall = new Wall(options);

Crée une instance de Wall avec les options souhaitées.

  1.     const container = document.querySelector('#<?php echo $id; ?>');

Récupère la <div> qui encadre le HTML du programme.

  1.     wall.setManagedWidget(container.querySelector('.ojs_wall')).resetWidget();

Crée et affiche l'interface du mur.

  1.     const filelist = [<?php echo implode(',', array_map(function($s) { return "'$s'"; }, $filelist)); ?>];
  2.  
  3.     wall.files = filelist;

Configure et affiche les vignettes du mur à partir de la liste des fichiers de l'utilisateur.

  1. <?php if (!$debug): ?>
  2. })();
  3. <?php endif; ?>

Ferme la fonction qui isole le code en JavaScript si $debug vaut false.

VOIR AUSSI

View, AudioPlayer, Écrire des données sur un serveur

Commentaires

Votre commentaire :
[p] [b] [i] [u] [s] [quote] [pre] [br] [code] [url] [email] strip aide 2000

Entrez un maximum de 2000 caractères.
Améliorez la présentation de votre texte avec les balises de formatage suivantes :
[p]paragraphe[/p], [b]gras[/b], [i]italique[/i], [u]souligné[/u], [s]barré[/s], [quote]citation[/quote], [pre]tel quel[/pre], [br]à la ligne,
[url]http://www.izend.org[/url], [url=http://www.izend.org]site[/url], [email]izend@izend.org[/email], [email=izend@izend.org]izend[/email],
[code]commande[/code], [code=langage]code source en c, java, php, html, javascript, xml, css, sql, bash, dos, make, etc.[/code].