Wall
An instance of Wall manages a deposit of JPG, PNG or GIF images and PDF documents shown as a wall of thumbnails. The user can select a file on the local disk or drag and drop a file on the wall to automatically upload it on the server. In one click, she can download a file saved on the server or delete it. The code of the server in PHP manages the copies of the files and the generation of the thumbnails displayed on the wall. Accessing the files on a wall is protected. Adding the management of other types of files like LibreOffice documents is a simple exercise.
To edit files directly in a navigator with CODE – Collabora Online Development Edition from a wall of documents, see Collaboractor.
REMINDER: The layout and the style of an interface are in the hands of the programmer. No graphical design is imposed. Coding the look of an instance of Wall is done instantly. The examples on this page use the icons from Font Awesome.
IMPORTANT: The files on a wall on this server are deleted automatically every 20 minutes.
- Responder
- View
- Wall
- View
Click on the load button to open a local file and upload it on the wall. Select a JPG, PNG or GIF file, a PDF document. NOTE: The maximum size of the file is configured to 5 MB. Open a folder containing images or documents with the explorer of your file system. Drag and drop a JPG, PNG, GIF or PDF file on the space to the left of the control buttons. The file is transferred in blocks of 100 kB. The progression is shown by a percentage. In case of error, the upload button turns red.
Move the pointer of the mouse over an image to display the name of the file.
Click on an image to select a file and click on the trash can to delete this file. If the server returns an error, the trash can turns red.
Click on an image and press the download button or hold down the Shift key and click on an image to open a file.
To start uploading an image or a document from a file with a button, a code can run the method uploadFile
of the wall when the button is clicked.
IMPORTANT: For security reasons, uploadFile
must be called in response to a user interaction.
<script>
document.getElementById('btn_upload_file').onclick = () => wall.uploadFile();
</script>
In the console of the navigator, display the list of files on the wall:
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 });
The Wall class inherits from the View class.
The parameter options
of the constructor is an object which configures the options maxfiles
, maxfilesize
, filetypes
, chunksize
, draganddrop
, tagURL
, uploadURL
, deleteURL
and fileURL
.
maxfiles
specifies the maximum number of files a user's space can contain, 10 by default. Triggers an error RangeError if < 1.
maxfilesize
specifies the maximum size of file, 1 MB by default. Triggers an error RangeError if < 100 kB.
filetypes
specifies the list of the MIME types of the files which the wall accepts to upload. By default, all types of files are accepted.
chunksize
specifies the size of the data blocks sent to the server, 100 kB by default. Triggers an error RangeError if < 10 kB.
If draganddrop
, false
by default, is true
, the instance accepts to upload a file that the user has dropped on the interface.
tagURL
is a character string which specifies the URL of the GET sent to the server to get the thumbnail of a file.
uploadURL
is a character string which specifies the URL of the POST sent to the server to upload a file.
deleteURL
is a character string which specifies the URL of the POST sent to the server to delete a file.
fileURL
is a character string which specifies the URL of the GET sent to the server to get a file.
tagURL
, uploadURL
, deleteURL
and fileURL
are optional.
- 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
is an accessor which returns or changes the list of files managed by this
.
filelist
is an array of character strings, e.g. ["1.pdf", "2.jpg"]
.
filelist
is normally built by the code on the server.
- Object.defineProperty(Wall.prototype, 'tag', {
- get: function() {
- return this._tag;
- }
- });
tag
is an accessor which returns the name of the file selected in 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
displays the thumbnails of the files managed by 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
enables or disables the different control buttons of the interface according the configuration and the state of 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
redefines the method inherited from the View class.
It initializes the elements of the interface of this
with the different graphical components in
, i. e. the properties w
_tagsWidget
, _uploadWidget
, _fileWidget
, _deleteWidget
, _downloadWidget
and _statusWidget
of this
.
- 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
redefines the method inherited from the View class.
It sets all the widgets of the interface to null
.
- Wall.prototype.uploadFile = function() {
- if (this._uploading)
- return this;
- if (this._fileWidget)
- this._fileWidget.click();
- return this;
- };
uploadFile
opens the file explorer of the navigator.
If a file is selected by the user, the internal method _uploadFile
is called with in argument the file descriptor.
For security reasons, uploadFile
must be called in response to a user interaction.
- 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
reads the file described by fd
and uploads it on the server.
_uploadFile
is an internal method called in response to a click on the widget to input a file name, i. e. _fileWidget
, or in response to the drag and drop of a file on the widget which shows the thumbnails, 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
asks the server to delete the file which has been selected on the wall.
- 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
asks the navigator to download the file which has been selected on the wall.
- 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
selects the file of this
specified by 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
deselects the file of this
which is being selected.
- 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
is an internal method which in response to a click on the thumbnail of a file, selects or deselects it, or if the Shift key is pressed, downloads the corresponding file.
Server
A file is copied in a disk space reserved to a user on the server in response to a POST by a function which extracts from it the name, the MIME type and the size of the file, the block of data which is transferred and its position in the file. A block of data is encoded in BASE64. When the last block of data is saved, the server generates the thumbnail of the file, a reduced image of an image or the first page of a document.
A file is deleted by the server in response to a POST by a function which extracts from it the name of the file. The server deletes also the thumbnail associated to the file.
Accessing a file or a thumbnail is done indirectly by a GET which triggers an action on the server which returns the requested file in the disk space reserved to the user.
IMPORTANT: A direct access to a file in a user's folder is best rejected by a Deny from all
directive for the entire directory managed by a wall.
The code of the server which responds to a POST to copy a file, to a POST to delete a file and to a GET to obtain a file or the thumbnail of a file is programmed for iZend. The file wall.inc is installed in the folder models. The files wallfile.php, wallfileupload.php and wallfiledelete.php are installed in the folder actions. To activates the URLs /wallfile, /wallupload and /walldelete, entries are added in the file aliases.inc. Adapt this code to your development environment.
Every user, identified by her session, has a reserved folder in the directory wall at the root of the site. The thumbnails are in the subfolder tags of a user's folder.
To send back a file or a thumbnail, the server uses the Xsendfile module by Apache. To install and enable this module:
$ sudo apt-get install libapache2-mod-xsendfile
$ sudo a2enmod xsendfile
To activate this module and configure it for a site, add the following lines in the configuration file of the site for Apache:
XsendFilePath /var/www/mysite.net/wall
<Directory /var/www/mysite.net/wall>
Deny from all
</Directory>
Replace /var/www/mysite.net by the root directory of the site. Notice that a direct access to the contents of this directory is rejected.
The code which manages the user folders is in the file wall.inc.
- require_once 'dirclear.php';
Includes the code of the function 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
deletes recursively all the files and all the subdirectories which are in the directory $dir
.
See dirclear in the documentation of the library of 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
defines the location of the users folders, i.e. the directory wall at the root of the site.
WALL_DIR_MODE
defines the creation mode of a folder, more precisely in write mode for the execution group of Apache.
WALL_NFILES
defines the maximum number of files in a user's folder and WALL_FILE_MAX_SIZE
the maximum size of a file.
WALL_FILE_TYPES
lists the file types managed by the wall.
WALL_TAG_WIDTH
gives the width of a thumbnail and PNG_QUALITY
the quality level of its 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');
Defines the command-line used to generate an image of the first page of a PDF document.
The first %s
is replaced by the name of the output file, a temporary PNG file.
The second %s
is replaced by the name of the input file, the PDF document in the user's folder.
- 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
returns the location of the folder $wall_id
in WALL_DIR
, i. e. the folder of a user.
wall_tags_directory
returns the location of the thumbnails of the folder $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
checks if the folder $wall_id
exists and if not, creates it with the subfolder for the thumbnails.
- function wall_delete_directory($wall_id) {
- $dir=wall_directory($wall_id);
- dirclear($dir);
- return @rmdir($dir);
- }
wall_delete_directory
deletes all the contents of the folder $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
returns the names of all the JPG, PNG, GIF or PDF files in the folder $wall_id
.
If $sort
is true
, the files are sorted by the date and the time they were last modified.
- 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
returns the full name of the file $fname
in $wall_id
.
wall_file_tag
returns the full name of the thumbnail of the file $fname
in $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
creates the thumbnail of the file $fname
in $wall_id
from the document in $pdffile
.
wall_tag_pdf
creates a temporary file containing the image of the first page of the document.
- function wall_tag_img($wall_id, $fname, $imgfile) {
- return wall_create_tag($wall_id, $fname, $imgfile);
- }
wall_tag_img
creates the thumbnail of the file $fname
in $wall_id
from the image in $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
creates the thumbnail of the file $fname
in $wall_id
from the image in the file $imgfile
.
If the image is larger than WALL_TAG_WIDTH
, the thumbnail has a width of WALL_TAG_WIDTH
pixels and a height preserving the aspect ratio of the image.
If the image is a PNG or a GIF, the thumbnail has a white background.
If the image is a JPG, the thumbnail is properly orientated.
- 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
creates the thumbnail of the file $fname
in $wall_id
from the file $file
whose content is of the MIME type $mimetype
.
wall_init_file
returns the result of the function wall_tag_img
or the function wall_tag_pdf
or false
if $mimetype
isn't supported.
NOTE: Managing more file types is fairly easy. To generate thumbnails for files edited with LibreOffice, look at unoconv - Universal Office Converter.
- require_once 'filemimetype.php';
Includes the code of the function 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
returns a character string giving the MIME type of the file $file
, e.g. image/jpeg, and if $encoding
is true
, the character set of the file preceded by a ; (SEMICOLON), e.g. image/jpeg; charset=binary.
See file_mime_type in the documentation of the library of iZend.
- require_once 'validatefilename.php';
Includes the code of the function validate_filename
.
return preg_match('/^[[:alnum:]]+[[:alnum:]_-]*(\.[[:alnum:]]+)?$/', $name);
}
validate_filename
returns true
if $name
starts with an alphanumeric character followed by a series of alphanumeric characters, underscores or dashes terminated in option by a dot and at least one alphanumeric character.
See validate_filename in the documentation of the library of iZend.
NOTE: Adapt this function to the syntax of the file names accepted by the website. IMPORTANT: Don't give access to files outside the user's folder. Don't allow in particular the character / (SLASH).
return preg_match('/^[0-9\pL][0-9\pL \._+-]*(\.[[:alnum:]]+)$/u', $name);
}
In this version, accented characters are accepted and a file name must start with a letter or a digit and end with an extension with in between in option, letters, digits, spaces, dots, underscores, plus signs and dashes.
- require_once 'models/wall.inc';
Includes the code of the function wall_file
.
- function wallfile($lang, $arglist=false) {
Defines the function wallfile
which is called by the URL /wallfile of the website.
The arguments $lang
and $arglist
are prepared by iZend.
Adapt the code to your development environment.
$lang
is false
. The content returned by this action is independent of the user's language.
$arglist
is an array which contains the rest of the URL of the request and its parameters , e.g. array(0 => '1.jpg')
for /wallfile/1.jpg and array(0 => '1.png', 'nocache' => '1600709132578')
for /wallfile/1.png?nocache=1600709132578.
- if (!isset($_SESSION['wall_id'])) {
- goto badrequest;
- }
- $wall_id=$_SESSION['wall_id'];
Extrracts the identifier of the user's wall saved in the session.
Returns an error HTTP 400 Bad Request
if no identifier is found in the 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;
- }
Checks that the URL contains a valid file name.
Builds the full name of the requested file with the user's directory and the file name passed in the URL.
EXAMPLE: The URL /wallfile/1.png has been deconstructed to extract from it the action, /wallfile corresponding to the function wallfile
, then the reset of the URL has been decomposed in the array passed in argument to the function, /1.png giving the array array(0 => '1.png')
.
If the user has the identifier 5f690ce2c930d
, the full name of the file is /wall/5f690ce2c930d/1.png in the root directory of the website.
Returns an error HTTP 404 Not Found
if the requested file cannot be found in the user's space..
- $filesize=filesize($file);
- $filetype=file_mime_type($file);
- if (!$filetype) {
- $filetype = 'application/octet-stream';
- }
Computes the size and the type of the requested file.
- header("X-Sendfile: $file");
- header("Content-Type: $filetype");
- header("Content-Length: $filesize");
- return false;
Returns a HTTP document with just in the header a directive X-Sendfile
so Apache directly returns the specified file.
- badrequest:
- header('HTTP/1.1 400 Bad Request');
- return false;
Returns a plain header 400 Bad Request
.
- notfound:
- header('HTTP/1.1 404 Not Found');
- return false;
- }
Returns a plain header 404 Not Found
.
- function walltag($lang, $arglist=false) {
Defines the function walltag
which is called by the URL /walltag of the website.
The function walltag
is nearly identical to the function wallfile
.
- require_once 'validatefilename.php';
- require_once 'models/wall.inc';
Includes the code of the functions validate_filename
, wall_file
and wall_init_file
, the constants WAL_FILE_MAX_SIZE
and WAL_FILE_TYPES
.
- function wallupload($lang, $arglist=false) {
Defines the function wallupload
which is called by the URL /wallupload of the site.
The arguments $lang
and $arglist
are not used.
The content returned by this action is independent of the user's language.
No argument or parameter is expected from the URL.
- if (!isset($_SESSION['wall_id'])) {
- goto badrequest;
- }
- $wall_id=$_SESSION['wall_id'];
Extrracts the identifier of the user's wall saved in the session.
Returns an error HTTP 400 Bad Request
if no identifier is found in the session.
- $maxfilesize=WALL_FILE_MAX_SIZE;
- $filetypes=WALL_FILE_TYPES;
Initializes $maxfilesize
to WALL_FILE_MAX_SIZE
and filetypes
to WAL_FILE_TYPES
, the list of supported MIME types.
- $name=$type=$data=false;
- $size=$offset=0;
Initializes the variables of the arguments of the 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;
- }
Extracts the arguments of the POST. The block of data encoded in BASE64 is decoded.
- 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;
- }
Checks the arguments of the POST.
- if (!$data) {
- goto badrequest;
- }
- $datasize=strlen($data);
- if ($offset + $datasize > $size) {
- goto badrequest;
- }
Checks the size of the data sent in the POST.
- $file=wall_file($wall_id, $name);
Gets the full name of the file in the user's space.
- $fout = @fopen($file, $offset == 0 ? 'wb' : 'cb');
- if ($fout === false) {
- goto internalerror;
- }
Opens the file in binary mode. If the POST contains the first block of data, the file is created.
- $r = fseek($fout, $offset);
- if ($r == -1) {
- goto internalerror;
- }
Moves the file pointer to the position of the block of data.
- $r = fwrite($fout, $data);
- if ($r === false) {
- goto internalerror;
- }
Writes the block of data.
- if ($offset + $datasize < $size) {
- return false;
- }
Returns a plain header 200 Ok
if more blocks of data are expected.
- if (!wall_init_file($wall_id, $name, $file, $type)) {
- goto internalerror;
- }
- return false;
When the entire file is saved, calls wall_init_file
to create the thumbnail of the file.
- badrequest:
- header('HTTP/1.1 400 Bad Request');
- return false;
Returns a plain header 400 Bad Request
.
- internalerror:
- @unlink($file);
- header('HTTP/1.1 500 Internal Error');
Returns a plain header 500 Internal Error
.
- require_once 'validatefilename.php';
- require_once 'models/wall.inc';
Includes the code of the functions validate_filename
, wall_file
and wall_file_tag
.
- function walldelete($lang, $arglist=false) {
Defines the function wallfiledelete
which is called by the URL /walldelete of the web site.
The arguments $lang
and $arglist
are not used.
The content returned by this action is independent of the user's language.
No argument or parameter is expected from the URL.
- if (!isset($_SESSION['wall_id'])) {
- goto badrequest;
- }
- $wall_id=$_SESSION['wall_id'];
Extrracts the identifier of the user's wall saved in the session.
Returns an error HTTP 400 Bad Request
if no identifier is found in the session.
- $name=false;
Initializes the name of the file in argument of the POST.
- if (isset($_POST['file_name'])) {
- $name=$_POST['file_name'];
- }
Extracts the name of the file from the POST.
- if (!$name or !validate_filename($name)) {
- goto badrequest;
- }
Checks the name of the file.
- $file=wall_file($wall_id, $name);
- if (!file_exists($file)) {
- goto badrequest;
- }
Looks for the file in the user's space.
Returns an error HTTP 400 Bad Request
if the specified file isn't found.
- $r = @unlink($file);
- if (!$r) {
- goto internalerror;
- }
Deletes the file.
- $file=wall_file_tag($wall_id, $name);
- $r = @unlink($file);
- if (!$r) {
- goto internalerror;
- }
- return false;
Deletes the thumbnail of the file.
- badrequest:
- header('HTTP/1.1 400 Bad Request');
- return false;
Returns a plain header 400 Bad Request
.
- internalerror:
- header('HTTP/1.1 500 Internal Error');
- return false;
- }
Returns a plain header 500 Internal Error
.
Test
- <?php $debug=true; ?>
Setting $debug
to true
gives access in the console of the navigator to the variable wall
, the instance of the Wall.
If $debug
is false
, all the code in JavaScript is protected by a closure function.
- 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);
Includes the code of the functions wall_create_directory
and wall_contents
.
Initializes the identifier of the user's wall and saves it in the session, if necessary, otherwise recovers it.
Creates the user's wall, i.e. the disk space for the user, if not already created.
Recovers the list of the files on the user's wall.
- <?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; ?>
Configures the options of the wall. Adapt the URLs to your environment.
- <?php $id=uniqid('id'); ?>
Defines the identifier of the <div>
which surrounds the HTML of the wall.
- <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>
Creates the widgets of the interface. Notice the names of the classes of the different components which are expected by the setWidget
method of a wall.
- <?php head('javascript', '/objectivejs/Objective.js'); ?>
- <?php head('javascript', '/objectivejs/Responder.js'); ?>
- <?php head('javascript', '/objectivejs/View.js'); ?>
- <?php head('javascript', '/objectivejs/Wall.js'); ?>
Includes the code of all the necessary classes.
REMINDER: The function head
of the iZend library adds a tag such as <script src="/objectivejs/Objective.js"></script>
to the <head>
section of the document in HTML. Adapt the code to your development environment.
- <?php if (!$debug): ?>
- (function() {
- <?php endif; ?>
Isolates all the code in JavaScript in a closure function if $debug
is 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);
Creates an instance of Wall with the desired options.
- const container = document.querySelector('#<?php echo $id; ?>');
Retrieves the <div>
which surrounds the HTML of the program.
- wall.setManagedWidget(container.querySelector('.ojs_wall')).resetWidget();
Creates and displays the interface of the wall.
- const filelist = [<?php echo implode(',', array_map(function($s) { return "'$s'"; }, $filelist)); ?>];
- wall.files = filelist;
Configures and displays the thumbnails of the wall from the list of the user's files.
- <?php if (!$debug): ?>
- })();
- <?php endif; ?>
Closes the function which isolates the code in JavaScript if $debug
is false
.
Comments