Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

plupload.js

Zuletzt modifiziert: 09.10.2024, 12:52 - Dateigröße: 19.54 KiB


 001  plupload.addI18n(phpbb.plupload.i18n);
 002  phpbb.plupload.ids = [];
 003   
 004  (function($) {  // Avoid conflicts with other libraries
 005   
 006  "use strict";
 007   
 008  /**
 009   * Set up the uploader.
 010   *
 011   * @return undefined
 012   */
 013  phpbb.plupload.initialize = function() {
 014      // Initialize the Plupload uploader.
 015      uploader.init();
 016   
 017      // Set attachment data.
 018      phpbb.plupload.setData(phpbb.plupload.data);
 019      phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData());
 020   
 021      // Only execute if Plupload initialized successfully.
 022      uploader.bind('Init', function() {
 023          phpbb.plupload.form = $(phpbb.plupload.config.form_hook)[0],
 024          phpbb.plupload.rowTpl = $('#attach-row-tpl')[0].outerHTML;
 025   
 026          // Hide the basic upload panel and remove the attach row template.
 027          $('#attach-row-tpl, #attach-panel-basic').remove();
 028          // Show multi-file upload options.
 029          $('#attach-panel-multi').show();
 030      });
 031   
 032      uploader.bind('PostInit', function() {
 033          // Point out the drag-and-drop zone if it's supported.
 034          if (uploader.features.dragdrop) {
 035              $('#drag-n-drop-message').show();
 036          }
 037      });
 038  };
 039   
 040  /**
 041   * Unsets all elements in the object uploader.settings.multipart_params whose keys
 042   * begin with 'attachment_data['
 043   *
 044   * @return undefined
 045   */
 046  phpbb.plupload.clearParams = function() {
 047      var obj = uploader.settings.multipart_params;
 048      for (var key in obj) {
 049          if (!obj.hasOwnProperty(key) || key.indexOf('attachment_data[') !== 0) {
 050              continue;
 051          }
 052   
 053          delete uploader.settings.multipart_params[key];
 054      }
 055  };
 056   
 057  /**
 058   * Update uploader.settings.multipart_params object with new data.
 059   *
 060   * @param object obj
 061   * @return undefined
 062   */
 063  phpbb.plupload.updateMultipartParams = function(obj) {
 064      uploader.settings.multipart_params = $.extend(
 065          uploader.settings.multipart_params,
 066          obj
 067      );
 068  };
 069   
 070  /**
 071   * Convert the array of attachment objects into an object that PHP would expect as POST data.
 072   *
 073   * @return object An object in the form 'attachment_data[i][key]': value as
 074   *     expected by the server
 075   */
 076  phpbb.plupload.getSerializedData = function() {
 077      var obj = {};
 078      for (var i = 0; i < phpbb.plupload.data.length; i++) {
 079          var datum = phpbb.plupload.data[i];
 080          for (var key in datum) {
 081              if (!datum.hasOwnProperty(key)) {
 082                  continue;
 083              }
 084   
 085              obj['attachment_data[' + i + '][' + key + ']'] = datum[key];
 086          }
 087      }
 088      return obj;
 089  };
 090   
 091  /**
 092   * Get the index from the phpbb.plupload.data array where the given
 093   * attachment id appears.
 094   *
 095   * @param int attach_id The attachment id of the file.
 096   * @return bool    Returns false if the id cannot be found.
 097   * @return int    Returns the index of the file if it exists.
 098   */
 099  phpbb.plupload.getIndex = function(attach_id) {
 100      var index = $.inArray(Number(attach_id), phpbb.plupload.ids);
 101      return (index !== -1) ? index : false;
 102  };
 103   
 104  /**
 105   * Set the data in phpbb.plupload.data and phpbb.plupload.ids arrays.
 106   * 
 107   * @param array data    Array containing the new data to use. In the form of 
 108   * array(index => object(property: value). Requires attach_id to be one of the object properties.
 109   *
 110   * @return undefined
 111   */
 112  phpbb.plupload.setData = function(data) {
 113      // Make sure that the array keys are reset.
 114      phpbb.plupload.ids = phpbb.plupload.data = [];
 115      phpbb.plupload.data = data;
 116   
 117      for (var i = 0; i < data.length; i++) {
 118          phpbb.plupload.ids.push(Number(data[i].attach_id));
 119      }
 120  };
 121   
 122  /**
 123   * Update the attachment data in the HTML and the phpbb & phpbb.plupload objects.
 124   * 
 125   * @param array data        Array containing the new data to use.
 126   * @param string action        The action that required the update. Used to update the inline attachment bbcodes.
 127   * @param int index            The index from phpbb.plupload_ids that was affected by the action.
 128   * @param array downloadUrl    Optional array of download urls to update.
 129   * @return undefined
 130   */
 131  phpbb.plupload.update = function(data, action, index, downloadUrl) {
 132   
 133      phpbb.plupload.updateBbcode(action, index);
 134      phpbb.plupload.setData(data);
 135      phpbb.plupload.updateRows(downloadUrl);
 136      phpbb.plupload.clearParams();
 137      phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData());
 138  };
 139   
 140  /**
 141   * Update the relevant elements and hidden data for all attachments.
 142   * 
 143   * @param array downloadUrl    Optional array of download urls to update.
 144   * @return undefined
 145   */
 146  phpbb.plupload.updateRows = function(downloadUrl) {
 147      for (var i = 0; i < phpbb.plupload.ids.length; i++) {
 148          phpbb.plupload.updateRow(i, downloadUrl);
 149      }
 150  };
 151   
 152  /**
 153   * Insert a row for a new attachment. This expects an HTML snippet in the HTML
 154   * using the id "attach-row-tpl" to be present. This snippet is cloned and the
 155   * data for the file inserted into it. The row is then appended or prepended to
 156   * #file-list based on the attach_order setting.
 157   * 
 158   * @param object file    Plupload file object for the new attachment.
 159   * @return undefined
 160   */
 161  phpbb.plupload.insertRow = function(file) {
 162      var row = $(phpbb.plupload.rowTpl);
 163   
 164      row.attr('id', file.id);
 165      row.find('.file-name').html(plupload.xmlEncode(file.name));
 166      row.find('.file-size').html(plupload.formatSize(file.size));
 167   
 168      if (phpbb.plupload.order == 'desc') {
 169          $('#file-list').prepend(row);
 170      } else {
 171          $('#file-list').append(row);
 172      }
 173  };
 174   
 175  /**
 176   * Update the relevant elements and hidden data for an attachment.
 177   * 
 178   * @param int index    The index from phpbb.plupload.ids of the attachment to edit.
 179   * @param array downloadUrl    Optional array of download urls to update. 
 180   * @return undefined
 181   */
 182  phpbb.plupload.updateRow = function(index, downloadUrl) {
 183      var attach = phpbb.plupload.data[index],
 184          row = $('[data-attach-id="' + attach.attach_id + '"]');
 185   
 186      // Add the link to the file
 187      if (typeof downloadUrl !== 'undefined' && typeof downloadUrl[index] !== 'undefined') {
 188          var url = downloadUrl[index].replace('&amp;', '&'),
 189              link = $('<a></a>');
 190   
 191          link.attr('href', url).html(attach.real_filename);
 192          row.find('.file-name').html(link)    
 193      }
 194   
 195      row.find('textarea').attr('name', 'comment_list[' + index + ']');
 196      phpbb.plupload.updateHiddenData(row, attach, index);
 197  };
 198   
 199  /**
 200   * Update hidden input data for an attachment.
 201   *
 202   * @param object row    jQuery object for the attachment row.
 203   * @param object attach    Attachment data object from phpbb.plupload.data
 204   * @param int index        Attachment index from phpbb.plupload.ids
 205   * @return undefined
 206   */
 207  phpbb.plupload.updateHiddenData = function(row, attach, index) {
 208      row.find('input[type="hidden"]').remove();
 209   
 210      for (var key in attach) {
 211          var input = $('<input />')
 212              .attr('type', 'hidden')
 213              .attr('name', 'attachment_data[' + index + '][' + key +']')
 214              .attr('value', attach[key]);
 215          $('textarea', row).after(input);
 216      }
 217  };
 218   
 219  /**
 220   * Deleting a file removes it from the queue and fires an AJAX event to the
 221   * server to tell it to remove the temporary attachment. The server
 222   * responds with the updated attachment data list so that any future
 223   * uploads can maintain state with the server
 224   *
 225   * @param object row    jQuery object for the attachment row.
 226   * @param int attachId    Attachment id of the file to be removed.
 227   *
 228   * @return undefined
 229   */
 230  phpbb.plupload.deleteFile = function(row, attachId) {
 231      // If there's no attach id, then the file hasn't been uploaded. Simply delete the row.
 232      if (typeof attachId === 'undefined') {
 233          var file = uploader.getFile(row.attr('id'));
 234          uploader.removeFile(file);
 235   
 236          row.slideUp(100, function() {
 237              row.remove();
 238              phpbb.plupload.hideEmptyList();
 239          });
 240      }
 241   
 242      var index = phpbb.plupload.getIndex(attachId);
 243      row.find('.file-status').toggleClass('file-uploaded file-working');
 244   
 245      if (index === false) {
 246          return;
 247      }
 248      var fields = {};
 249      fields['delete_file[' + index + ']'] = 1;
 250   
 251      var always = function() {
 252          row.find('.file-status').removeClass('file-working');
 253      };
 254   
 255      var done = function(response) {
 256          if (typeof response !== 'object') {
 257              return;
 258          }
 259   
 260          // trigger_error() was called which likely means a permission error was encountered.
 261          if (typeof response.title !== 'undefined') {
 262              uploader.trigger('Error', {message: response.message});
 263              // We will have to assume that the deletion failed. So leave the file status as uploaded.
 264              row.find('.file-status').toggleClass('file-uploaded');
 265   
 266              return;
 267          }
 268          phpbb.plupload.update(response, 'removal', index);
 269          // Check if the user can upload files now if he had reached the max files limit.
 270          phpbb.plupload.handleMaxFilesReached();
 271   
 272          if (row.attr('id')) {
 273              var file = uploader.getFile(row.attr('id'));
 274              uploader.removeFile(file);
 275          }
 276          row.slideUp(100, function() {
 277              row.remove();
 278              // Hide the file list if it's empty now.
 279              phpbb.plupload.hideEmptyList();
 280          });
 281          uploader.trigger('FilesRemoved');
 282      };
 283   
 284      $.ajax(phpbb.plupload.config.url, {
 285          type: 'POST',
 286          data: $.extend(fields, phpbb.plupload.getSerializedData()),
 287          headers: {'X-PHPBB-USING-PLUPLOAD': '1', 'X-Requested-With': 'XMLHttpRequest'}
 288      })
 289      .always(always)
 290      .done(done);
 291  };
 292   
 293  /**
 294   * Check the attachment list and hide its container if it's empty.
 295   *
 296   * @return undefined
 297   */
 298  phpbb.plupload.hideEmptyList = function() {
 299      if (!$('#file-list').children().length) {
 300          $('#file-list-container').slideUp(100);
 301      }
 302  }
 303   
 304  /**
 305   * Update the indices used in inline attachment bbcodes. This ensures that the bbcodes
 306   * correspond to the correct file after a file is added or removed. This should be called 
 307   * before the phpbb.plupload,data and phpbb.plupload.ids arrays are updated, otherwise it will
 308   * not work correctly.
 309   *
 310   * @param string action    The action that occurred -- either "addition" or "removal"
 311   * @param int index        The index of the attachment from phpbb.plupload.ids that was affected.
 312   *
 313   * @return undefined
 314   */
 315  phpbb.plupload.updateBbcode = function(action, index) {
 316      var    textarea = $('#message', phpbb.plupload.form),
 317          text = textarea.val(),
 318          removal = (action === 'removal');
 319   
 320      // Return if the bbcode isn't used at all.
 321      if (text.indexOf('[attachment=') === -1) {
 322          return;
 323      }
 324   
 325      // Private function used to replace the bbcode.
 326      var updateBbcode = function(match, fileName) {
 327          // Remove the bbcode if the file was removed.
 328          if (removal && index === i) {
 329              return '';
 330          }
 331          var newIndex = i + ((removal) ? -1 : 1);
 332          return '[attachment=' + newIndex +']' + fileName + '[/attachment]';
 333      };
 334   
 335      // Private function used to generate search regexp
 336      var searchRegexp = function(index) {
 337          return new RegExp('\\[attachment=' + index + '\\](.*?)\\[\\/attachment\\]', 'g');
 338      }
 339      // The update order of the indices is based on the action taken to ensure that we don't corrupt
 340      // the bbcode index by updating it several times as we move through the loop.
 341      // Removal loop starts at the removed index and moves to the end of the array.
 342      // Addition loop starts at the end of the array and moves to the added index at 0.
 343      var searchLoop = function() {
 344          if (typeof i === 'undefined') {
 345              i = (removal) ? index : phpbb.plupload.ids.length - 1;
 346          }
 347          return (removal) ? (i < phpbb.plupload.ids.length): (i >= index);
 348      }
 349      var i;
 350   
 351      while (searchLoop()) {
 352          text = text.replace(searchRegexp(i), updateBbcode);
 353          (removal) ? i++ : i--;
 354      }
 355      textarea.val(text);
 356  };
 357   
 358  /**
 359   * Get Plupload file objects based on their upload status.
 360   *
 361   * @param int status Plupload status - plupload.DONE, plupload.FAILED, plupload.QUEUED,
 362   * plupload.STARTED, plupload.STOPPED
 363   *
 364   * @return Returns an array of the Plupload file objects matching the status.
 365   */
 366  phpbb.plupload.getFilesByStatus = function(status) {
 367      var files = [];
 368   
 369      $.each(uploader.files, function(i, file) {
 370          if (file.status === status) {
 371              files.push(file);
 372          }
 373      });
 374      return files;
 375  }
 376   
 377  /**
 378   * Check whether the user has reached the maximun number of files that he's allowed
 379   * to upload. If so, disables the uploader and marks the queued files as failed. Otherwise
 380   * makes sure that the uploader is enabled.
 381   *
 382   * @return bool Returns true if the limit has been reached. False if otherwise.
 383   */
 384  phpbb.plupload.handleMaxFilesReached = function() {
 385      // If there is no limit, the user is an admin or moderator.
 386      if (!phpbb.plupload.maxFiles) {
 387          return false;
 388      }
 389   
 390      if (phpbb.plupload.maxFiles <= phpbb.plupload.ids.length) {
 391          // Fail the rest of the queue.
 392          phpbb.plupload.markQueuedFailed(phpbb.plupload.lang.TOO_MANY_ATTACHMENTS);
 393          // Disable the uploader.
 394          phpbb.plupload.disableUploader();
 395          uploader.trigger('Error', {message: phpbb.plupload.lang.TOO_MANY_ATTACHMENTS});
 396   
 397          return true;
 398      } else if(phpbb.plupload.maxFiles > phpbb.plupload.ids.length) {
 399          // Enable the uploader if the user is under the limit
 400          phpbb.plupload.enableUploader();
 401      }
 402      return false;
 403  }
 404   
 405  /**
 406   * Disable the uploader
 407   *
 408   * @return undefined
 409   */
 410  phpbb.plupload.disableUploader = function() {
 411      $('#add_files').addClass('disabled');
 412      uploader.disableBrowse();
 413  }
 414   
 415  /**
 416   * Enable the uploader
 417   *
 418   * @return undefined
 419   */
 420  phpbb.plupload.enableUploader = function() {
 421      $('#add_files').removeClass('disabled');
 422      uploader.disableBrowse(false);
 423  }
 424   
 425  /**
 426   * Mark all queued files as failed.
 427   *
 428   * @param string error Error message to present to the user.
 429   * @return undefined
 430   */
 431  phpbb.plupload.markQueuedFailed = function(error) {
 432      var files = phpbb.plupload.getFilesByStatus(plupload.QUEUED);
 433   
 434      $.each(files, function(i, file) {
 435          $('#' + file.id).find('.file-progress').hide();
 436          phpbb.plupload.fileError(file, error);
 437      });
 438  }
 439   
 440  /**
 441   * Marks a file as failed and sets the error message for it.
 442   *
 443   * @param object file    Plupload file object that failed.
 444   * @param string error    Error message to present to the user.
 445   * @return undefined
 446   */
 447  phpbb.plupload.fileError = function(file, error) {
 448      file.status = plupload.FAILED;
 449      file.error = error;
 450      $('#' + file.id).find('.file-status').addClass('file-error').attr({'data-error-title': phpbb.plupload.lang.ERROR, 'data-error-message': error});
 451  }
 452   
 453   
 454   
 455   
 456  /**
 457   * Set up the Plupload object and get some basic data.
 458   */
 459  var    uploader = new plupload.Uploader(phpbb.plupload.config);
 460  phpbb.plupload.initialize();
 461   
 462   
 463   
 464   
 465  /**
 466   * Insert inline attachment bbcode.
 467   */
 468   $('#file-list').on('click', '.file-inline-bbcode', function(e) {
 469      var attachId = $(this).parents('.attach-row').attr('data-attach-id'),
 470          index = phpbb.plupload.getIndex(attachId);
 471   
 472      attach_inline(index, phpbb.plupload.data[index].real_filename);    
 473      e.preventDefault();
 474  });
 475   
 476  /**
 477   * Delete a file.
 478   */
 479  $('#file-list').on('click', '.file-delete', function(e) {
 480      var row = $(this).parents('.attach-row'),
 481          attachId = row.attr('data-attach-id');
 482   
 483      phpbb.plupload.deleteFile(row, attachId);
 484      e.preventDefault();
 485  });
 486   
 487  /**
 488   * Display the error message for a particular file when the error icon is clicked.
 489   */
 490  $('#file-list').on('click', '.file-error', function(e) {
 491      phpbb.alert($(this).attr('data-error-title'), $(this).attr('data-error-message'));
 492      e.preventDefault();
 493  });
 494   
 495  /**
 496   * Fires when an error occurs.
 497   */
 498  uploader.bind('Error', function(up, error) {
 499      error.file.name = plupload.xmlEncode(error.file.name);
 500   
 501      // The error message that Plupload provides for these is vague, so we'll be more specific.
 502      if (error.code === plupload.FILE_EXTENSION_ERROR) {
 503          error.message = plupload.translate('Invalid file extension:') + ' ' + error.file.name;
 504      } else if (error.code === plupload.FILE_SIZE_ERROR) {
 505          error.message = plupload.translate('File too large:') + ' ' + error.file.name;
 506      }
 507      phpbb.alert(phpbb.plupload.lang.ERROR, error.message);
 508  });
 509   
 510  /**
 511   * Fires before a given file is about to be uploaded. This allows us to
 512   * send the real filename along with the chunk. This is necessary because
 513   * for some reason the filename is set to 'blob' whenever a file is chunked
 514   *
 515   * @param object up        The plupload.Uploader object
 516   * @param object file    The plupload.File object that is about to be
 517   *     uploaded
 518   *
 519   * @return undefined
 520   */
 521  uploader.bind('BeforeUpload', function(up, file) {
 522      if (phpbb.plupload.handleMaxFilesReached()) {
 523          return;
 524      }
 525   
 526      phpbb.plupload.updateMultipartParams({'real_filename': file.name});
 527  });
 528   
 529  /**
 530   * Fired when a single chunk of any given file is uploaded. This parses the
 531   * response from the server and checks for an error. If an error occurs it
 532   * is reported to the user and the upload of this particular file is halted
 533   *
 534   * @param object up            The plupload.Uploader object
 535   * @param object file        The plupload.File object whose chunk has just
 536   *     been uploaded
 537   * @param object response    The response object from the server
 538   *
 539   * @return undefined
 540   */
 541  uploader.bind('ChunkUploaded', function(up, file, response) {
 542      if (response.chunk >= response.chunks - 1) {
 543          return;
 544      }
 545   
 546      var json = {};
 547      try {
 548          json = $.parseJSON(response.response);
 549      } catch (e) {
 550          file.status = plupload.FAILED;
 551          up.trigger('FileUploaded', file, {
 552              response: JSON.stringify({
 553                  error: {
 554                      message: 'Error parsing server response.'
 555                  }
 556              })
 557          });
 558      }
 559   
 560      // If trigger_error() was called, then a permission error likely occurred.
 561      if (typeof json.title !== 'undefined') {
 562          json.error = {message: json.message};
 563      }
 564   
 565      if (json.error) {
 566          file.status = plupload.FAILED;
 567          up.trigger('FileUploaded', file, {
 568              response: JSON.stringify({
 569                  error: {
 570                      message: json.error.message
 571                  }
 572              })
 573          });
 574      }
 575  });
 576   
 577  /**
 578   * Fires when files are added to the queue.
 579   *
 580   * @return undefined
 581   */
 582  uploader.bind('FilesAdded', function(up, files) {
 583      // Prevent unnecessary requests to the server if the user already uploaded
 584      // the maximum number of files allowed.
 585      if (phpbb.plupload.handleMaxFilesReached()) {
 586          return;
 587      }
 588   
 589      // Switch the active tab if the style supports it
 590      if (typeof activateSubPanel == 'function') {
 591          activateSubPanel('attach-panel');
 592      }
 593   
 594      // Show the file list if there aren't any files currently.
 595      if (!$('#file-list-container').is(':visible')) {
 596          $('#file-list-container').show(100);
 597      }
 598   
 599      $.each(files, function(i, file) {
 600          phpbb.plupload.insertRow(file);
 601      });
 602   
 603      up.bind('UploadProgress', function(up, file) {
 604          $('#' + file.id + " .file-progress-bar").css('width', file.percent + '%');
 605          $('#file-total-progress-bar').css('width', up.total.percent + '%');
 606      });
 607   
 608      // Do not allow more files to be added to the running queue.
 609      phpbb.plupload.disableUploader();
 610   
 611      // Start uploading the files once the user has selected them.
 612      up.start();
 613  });
 614   
 615   
 616  /**
 617   * Fires when an entire file has been uploaded. It checks for errors
 618   * returned by the server otherwise parses the list of attachment data and
 619   * appends it to the next file upload so that the server can maintain state
 620   * with regards to the attachments in a given post
 621   *
 622   * @param object up            The plupload.Uploader object
 623   * @param object file        The plupload.File object that has just been
 624   *     uploaded
 625   * @param string response    The response string from the server
 626   *
 627   * @return undefined
 628   */
 629  uploader.bind('FileUploaded', function(up, file, response) {
 630      var json = {},
 631          row = $('#' + file.id),
 632          error;
 633   
 634      // Hide the progress indicator.
 635      row.find('.file-progress').hide();
 636   
 637      try {
 638          json = $.parseJSON(response.response);
 639      } catch (e) {
 640          error = 'Error parsing server response.';
 641      }
 642   
 643      // If trigger_error() was called, then a permission error likely occurred.
 644      if (typeof json.title !== 'undefined') {
 645          error = json.message;
 646          up.trigger('Error', {message: error});
 647   
 648          // The rest of the queue will fail.
 649          phpbb.plupload.markQueuedFailed(error);
 650      } else if (json.error) {
 651          error = json.error.message;
 652      }
 653   
 654      if (typeof error !== 'undefined') {
 655          phpbb.plupload.fileError(file, error);
 656      } else if (file.status === plupload.DONE) {
 657          file.attachment_data = json['data'][0];
 658   
 659          row.attr('data-attach-id', file.attachment_data.attach_id);
 660          row.find('.file-inline-bbcode').show();
 661          row.find('.file-status').addClass('file-uploaded');
 662          phpbb.plupload.update(json['data'], 'addition', 0, [json['download_url']]);
 663      }
 664  });
 665   
 666  /**
 667   * Fires when the entire queue of files have been uploaded. 
 668   *
 669   * @param object up        The plupload.Uploader object
 670   * @param array files    An array of plupload.File objects that have just
 671   *     been uploaded as part of a queue
 672   *
 673   * @return undefined
 674   */
 675  uploader.bind('UploadComplete', function(up, files) {
 676      // Hide the progress bar
 677      setTimeout(function() {
 678          $('#file-total-progress-bar').fadeOut(500, function() {
 679              $(this).css('width', 0).show();
 680          });
 681      }, 2000);
 682   
 683      // Re-enable the uploader
 684      phpbb.plupload.enableUploader();
 685  });
 686   
 687  })(jQuery); // Avoid conflicts with other libraries
 688