1
 19

Write data on a server

   

Click in the image or press the button to load a file from your disk space.

Select a JPG, PNG, GIF or SVG file. NOTE: The maximum size of the file is configured to 1 MB and the maximum width of the display to 320 px.

Open a folder containing images with the explorer of your file system. Drag and drop a JPG, PNG, GIF or SVG file over the image.

Edit the width or the height of the image.

Try the Undo and Redo buttons.

Reload the page. The modifications are saved.

For the code of the editor, consult the manual pages ImageSizeInspector and Editor and the article Architecture of an editor.

Click on the button to upload the image with its dimensions on the server.

The file is transferred in 100 kB blocks. The progression is displayed by a percentage. When the transfer is over, a check mark is displayed next to the button. An error is signaled by an exclamation point in red.

Code

The image is copied in the disk space of the server in response to a POST by a function which extracts from it the MIME type and the size of the file, the width and the height of the image, the block of data which is transferred, its size and its position in the file. A block of data is encoded in BASE64.

In iZend, the file uploadimage.php is installed directly in the folder actions. To activate the URL /uploadimage in a website, an entry is added in the file aliases.inc to the list of URLs common to all languages.

$aliases = array(
    array(
        'captcha' => 'captcha',
...
        'uploadimage' => 'uploadimage',
    ),
    'en'    => array(
...
    ),
);

Adapt the installation of this function to your development environment.

  1. define('TMP_DIR', ROOT_DIR . DIRECTORY_SEPARATOR . 'tmp');
  2.  
  3. define('IMAGE_MAX_SIZE', 1000000);

Defines TMP_DIR - the folder where the image file is saved - to the name of the subfolder tmp in the root folder of the site and IMAGE_MAX_SIZE - the maximum size of an image - to 1 MB.

  1. function uploadimage($lang, $arglist=false) {

Defines the function uploadimage which is called by the URL /uploadimage of the site. The arguments $lang - the language of the output, which will always be false - and $arglist - the parameters passed in the URL - are not used.

  1.     $maxfilesize=IMAGE_MAX_SIZE;
  2.  
  3.     $filetypes=array('image/jpeg', 'image/png', 'image/gif', 'image/svg+xml');

Initializes $maxfilesize to IMAGE_MAX_SIZE and $filetypes to the list of supported MIME types.

  1.     $type=$data=false;
  2.     $size=$offset=0;
  3.  
  4.     $width=$height=0;

Initializes the variables of the arguments of the POST.

  1.     if (isset($_POST['file_size'])) {
  2.         $size=$_POST['file_size'];
  3.     }
  4.     if (isset($_POST['file_type'])) {
  5.         $type=$_POST['file_type'];
  6.     }
  7.     if (isset($_POST['file_offset'])) {
  8.         $offset=$_POST['file_offset'];
  9.     }
  10.     if (isset($_POST['file_data'])) {
  11.         $data=base64_decode($_POST['file_data'], true);
  12.     }
  13.     if (isset($_POST['image_width'])) {
  14.         $width=$_POST['image_width'];
  15.     }
  16.     if (isset($_POST['image_height'])) {
  17.         $height=$_POST['image_height'];
  18.     }

Extracts the arguments of the POST. The block of data encoded in BASE64 is decoded.

  1.     if (($width = filter_var($width, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0)))) === false)
  2.         goto badrequest;
  3.  
  4.     if (($height = filter_var($height, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0)))) === false)
  5.         goto badrequest;
  6.  
  7.     if (($offset = filter_var($offset, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0)))) === false)
  8.         goto badrequest;
  9.  
  10.     if (($size = filter_var($size, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0, 'max_range' => $maxfilesize))))  === false)
  11.         goto badrequest;
  12.  
  13.     if (!$type or !in_array($type, $filetypes)) {
  14.         goto badrequest;
  15.     }

Checks the arguments of the POST.

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

Checks the size of the data sent in the POST.

  1.     goto trashfile;

On this server, writing the file is simulated. To check that the image has been copied properly by displaying it, comment out this instruction and adapt the rest of the code to save the file in a folder which is writable by the server.

  1.     $name='image';
  2.  
  3.     switch ($type) {
  4.         case 'image/jpeg':
  5.             $fname = $name . '.jpg';
  6.             break;
  7.         case 'image/png':
  8.             $fname = $name . '.png';
  9.             break;
  10.         case 'image/gif':
  11.             $fname = $name . '.gif';
  12.             break;
  13.         case 'image/svg+xml':
  14.             $fname = $name . '.svg';
  15.             break;
  16.         default:
  17.             goto badrequest;
  18.     }
  19.  
  20.     $file = TMP_DIR . DIRECTORY_SEPARATOR . $fname;

Defines the name of the image file. The extension of the file name depends on the MIME type of the image.

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

Opens the image file in binary mode. If the POST contains the first block of data, the file is created.

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

Moves the file pointer to the position of the block of data.

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

Writes the block of data.

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

Returns a plain header 200 Ok if more blocks of data are expected.

  1.     $file = TMP_DIR . DIRECTORY_SEPARATOR . $name . '.json';
  2.  
  3.     $data = array('width' => $width, 'height' => $height, 'name' => $fname);
  4.  
  5.     $r = @file_put_contents($file, json_encode($data));
  6.  
  7.     if ($r === false) {
  8.         goto internalerror;
  9.     }
  10.  
  11.     return false;

If all the image data is saved, creates a file image.json in the same directory giving the width and the height of the image and the name of the file. Returns a plain header 200 Ok.

  1. trashfile:
  2.     return false;

Returns a plain header 200 Ok without saving the data.

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

Returns a plain header 400 Bad Request if an element of the POST is invalid.

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

Returns a plain header 500 Internal Error if the image couldn't be saved properly.

  1. <?php $debug=false; ?>

Setting $debug to true gives access in the console of the navigator to all the components of the interface. If $debug is false, all the code in JavaScript is protected by a closure function.

  1. <?php $filetypes=array('image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'); ?>
  2. <?php $maxsize=1000000; ?>
  3. <?php $imgname='image'; ?>
  4. <?php $imgsrc='/files/images/loadimage.png'; ?>
  5. <?php $imgwidth=320; ?>
  6. <?php $imgheight=180; ?>
  7. <?php $uploadurl='/uploadimage'; ?>
  8. <?php $chunksize=100000; ?>

Defines the MIME types supported by the editor, the maximum size of an image, the name of the storage item in the navigator, the URL of the initial image, the width and the height of the display of the image, the URL of the save function of the server, the size of the blocks of data sent to the server.

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

Defines the identifier of the <div> which surrounds the editor.

  1. #<?php echo $id; ?> .ojs img {
  2.     max-width: <?php echo $imgwidth; ?>px;
  3. }
  4. #<?php echo $id; ?> #uploadstatus {
  5.     font-size: smaller;
  6. }

Configures the width of the image display. Shows the progression of a transfer in a smaller font.

  1. <div id="<?php echo $id; ?>" class="noprint">
  2. <div class="ojs">
  3. <div>
  4. <div class="ojs_undo">
  5. <button type="submit" class="ojs_button narrow control_undo" disabled><i class="fas fa-undo"></i></button>
  6. <button type="submit" class="ojs_button narrow control_redo" disabled><i class="fas fa-redo"></i></button>
  7. </div>
  8. <span class="ojs_dimension">
  9. <input class="ojs_width" type="number" min="0"/>&nbsp;<i class="fas fa-arrows-alt-h small"></i>
  10. <input class="ojs_height" type="number" min="0"/>&nbsp;<i class="fas fa-arrows-alt-v small"></i>
  11. </span>
  12. </div>
  13. <div><img src="<?php echo $imgsrc; ?>" alt="" width="<?php echo $imgwidth; ?>" height="<?php echo $imgheight; ?>"/></div>
  14. <div>
  15. <span><button id="loadimage" type="submit" class="ojs_button narrow"><i class="fas fa-file-image"></i></button></span>
  16. <span><button id="uploadimage" type="submit" class="ojs_button narrow"><i class="fas fa-file-export"></i></button></span>
  17. <span id="uploadstatus"></span>
  18. </div>
  19. </div>
  20. </div>

Creates the widgets for the instances of UndoPanel and ImageSizeInspector, the container of the image and the buttons to load a local file and upload it on the server.

  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/Model.js'); ?>
  5. <?php head('javascript', '/objectivejs/Validator.js'); ?>
  6. <?php head('javascript', '/objectivejs/Editor.js'); ?>
  7. <?php head('javascript', '/objectivejs/Inspector.js'); ?>
  8. <?php head('javascript', '/objectivejs/NumberInspector.js'); ?>
  9. <?php head('javascript', '/objectivejs/DimensionInspector.js'); ?>
  10. <?php head('javascript', '/objectivejs/ImageInspector.js') ?>
  11. <?php head('javascript', '/objectivejs/SequenceInspector.js') ?>
  12. <?php head('javascript', '/objectivejs/ImageSizeInspector.js') ?>
  13. <?php head('javascript', '/objectivejs/ImageModel.js'); ?>
  14. <?php head('javascript', '/objectivejs/Undo.js'); ?>
  15. <?php head('javascript', '/objectivejs/Panel.js'); ?>
  16. <?php head('javascript', '/objectivejs/UndoPanel.js'); ?>
  17. <?php head('javascript', '/objectivejs/ModelStorageDelegate.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.

  1. function ImageLoader(model, inspectors, panel, upload, status) {
  2.     Editor.call(this, model, null, inspectors, panel);
  3.  
  4.     if (upload)
  5.         upload.onclick = () => this.upload();
  6.  
  7.     this._upload = upload;
  8.     this._status = status;
  9. }
  10.  
  11. ImageLoader.prototype = Object.create(Editor.prototype);
  12.  
  13. Object.defineProperty(ImageLoader.prototype, 'constructor', { value: ImageLoader, enumerable: false, writable: true });
  14.  
  15. ImageLoader.uploadURL = '/uploadimage';
  16. ImageLoader.chunkSize = 100000;
  17.  
  18. ImageLoader.prototype.upload = function() {
  19.     const {image} = this._model.get();
  20.  
  21.     if (image.image === null)
  22.         return;
  23.  
  24.     const [url] = image.image;
  25.     const [width, height] = image.size;
  26.  
  27.     const imgtype = /^data:(image\/[-.+0-9a-zA-Z]+);base64,/.exec(url)[1];
  28.     const imgdata = atob(url.substring(url.indexOf(',')+1));
  29.     const imgsize = imgdata.length;
  30.  
  31.     const uploadurl = ImageLoader.uploadURL;
  32.     const chunksize = ImageLoader.chunkSize;
  33.  
  34.     let offset = 0;
  35.     let progress = 0;
  36.  
  37.     const uploadslice = () => {
  38.         if (this._status && imgsize > chunksize)
  39.             this._status.innerText = `${progress}%`;
  40.  
  41.         const data = imgdata.substring(offset, offset + chunksize);
  42.  
  43.         $.post(uploadurl, {file_size: imgsize, file_type: imgtype, file_offset: offset, file_data: btoa(data), image_width: width, image_height: height })
  44.             .done(() => {
  45.                 offset += data.length;
  46.                 progress = Math.floor((offset / imgsize) * 100);
  47.  
  48.                 if (progress < 100)
  49.                     uploadslice();
  50.                 else {
  51.                     if (this._status)
  52.                         this._status.innerHTML = '<i class="fas fa-check-circle"></i>';
  53.                     if (this._upload)
  54.                         this._upload.disabled = false;
  55.                 }
  56.             })
  57.             .fail(() => {
  58.                 if (this._status)
  59.                     this._status.innerHTML = '<i class="fas fa-exclamation-circle inerror"></i>';
  60.                 if (this._upload)
  61.                     this._upload.disabled = false;
  62.             });
  63.     };
  64.  
  65.     if (this._status)
  66.         this._status.innerText = '';
  67.  
  68.     if (this._upload && imgsize > chunksize)
  69.         this._upload.disabled = true;
  70.  
  71.     uploadslice();
  72. }

An instance of ImageLoader is an Editor which can upload an image with a size on a server. upload gets the data of the model, extracts the type of the image and the image data in BASE64, computes its size, sends to the server all this information and the image data in 100 kB sequential blocks, displays the status of the operation.

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

Isolates all the code in JavaScript in a closure function if $debug is false.

  1.     const model = new ImageModel('<?php echo $imgname; ?>');
  2.  
  3.     const container = document.querySelector('#<?php echo $id; ?>');
  4.  
  5.     const panel = new UndoPanel();
  6.  
  7.     panel.setManagedWidget(container.querySelector('.ojs_undo')).resetWidget();
  8.  
  9.     const filetypes = [<?php echo implode(',', array_map(function($s) { return "'$s'"; }, $filetypes)); ?>];
  10.  
  11.     const imageInspector = new ImageInspector(null, { filetypes: filetypes, maxsize: <?php echo $maxsize; ?> });
  12.  
  13.     imageInspector.setManagedWidget(container.querySelector('img'));
  14.  
  15.     const sizeInspector = new DimensionInspector(0, 0, {minWidth: 0, minHeight: 0});
  16.  
  17.     sizeInspector.setManagedWidget(container.querySelector('.ojs_dimension')).resetWidget();
  18.  
  19.     const imageSizeInspector = new ImageSizeInspector(imageInspector, sizeInspector);
  20.  
  21.     const loadimage = container.querySelector('#loadimage');
  22.  
  23.     loadimage.onclick = () => imageInspector.loadImage();
  24.  
  25.     const inspectors = {
  26.         image:      imageSizeInspector
  27.     };
  28.  
  29.     const uploadimage = container.querySelector('#uploadimage');
  30.     const uploadstatus = container.querySelector('#uploadstatus');
  31.  
  32.     const editor = new ImageLoader(model, inspectors, panel, uploadimage, uploadstatus);
  33.  
  34.     model.setDelegate(new ModelStorageDelegate());
  35.  
  36.     model.readIn();
  37.  
  38.     model.enableSync();

Creates the data model to be edited. Retrieves the <div> which surrounds the HTML of the program Creates the panel with the buttons to undo and redo a modification, creates the inspector for the image and its size, creates the independent button which allows to load an image, creates the editor. Associates the model to a delegate in charge of saving its data in the storage space of the navigator. Configures the model with the data already saved. Enables saving the data of the model whenever it is changed.

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

Closes the function which isolates the code in JavaScript if $debug is false.

SEE ALSO

ImageModel, ModelStorageDelegate, ImageSizeInspector, Editor, Wall, Architecture of an editor, Configure charts by Plotly

Comments

Your comment:
[p] [b] [i] [u] [s] [quote] [pre] [br] [code] [url] [email] strip help 2000

Enter a maximum of 2000 characters.
Improve the presentation of your text with the following formatting tags:
[p]paragraph[/p], [b]bold[/b], [i]italics[/i], [u]underline[/u], [s]strike[/s], [quote]citation[/quote], [pre]as is[/pre], [br]line break,
[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]command[/code], [code=language]source code in c, java, php, html, javascript, xml, css, sql, bash, dos, make, etc.[/code].