[ Index ] |
PHP Cross Reference of phpBB-3.2.11-deutsch |
[Summary view] [Print] [Text view]
1 /* global phpbb, plupload, attachInline */ 2 3 plupload.addI18n(phpbb.plupload.i18n); 4 phpbb.plupload.ids = []; 5 6 (function($) { // Avoid conflicts with other libraries 7 8 'use strict'; 9 10 /** 11 * Set up the uploader. 12 */ 13 phpbb.plupload.initialize = function() { 14 // Initialize the Plupload uploader. 15 phpbb.plupload.uploader.init(); 16 17 // Set attachment data. 18 phpbb.plupload.setData(phpbb.plupload.data); 19 phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData()); 20 21 // Only execute if Plupload initialized successfully. 22 phpbb.plupload.uploader.bind('Init', function() { 23 phpbb.plupload.form = $(phpbb.plupload.config.form_hook)[0]; 24 let $attachRowTemplate = $('#attach-row-tpl'); 25 $attachRowTemplate.removeClass('attach-row-tpl'); 26 phpbb.plupload.rowTpl = $attachRowTemplate[0].outerHTML; 27 28 // Hide the basic upload panel and remove the attach row template. 29 $('#attach-row-tpl, #attach-panel-basic').remove(); 30 // Show multi-file upload options. 31 $('#attach-panel-multi').show(); 32 }); 33 34 phpbb.plupload.uploader.bind('PostInit', function() { 35 // Point out the drag-and-drop zone if it's supported. 36 if (phpbb.plupload.uploader.features.dragdrop) { 37 $('#drag-n-drop-message').show(); 38 } 39 40 // Ensure "Add files" button position is correctly calculated. 41 if ($('#attach-panel-multi').is(':visible')) { 42 phpbb.plupload.uploader.refresh(); 43 } 44 $('[data-subpanel="attach-panel"]').one('click', function() { 45 phpbb.plupload.uploader.refresh(); 46 }); 47 }); 48 }; 49 50 /** 51 * Unsets all elements in the object uploader.settings.multipart_params whose keys 52 * begin with 'attachment_data[' 53 */ 54 phpbb.plupload.clearParams = function() { 55 var obj = phpbb.plupload.uploader.settings.multipart_params; 56 for (var key in obj) { 57 if (!obj.hasOwnProperty(key) || key.indexOf('attachment_data[') !== 0) { 58 continue; 59 } 60 61 delete phpbb.plupload.uploader.settings.multipart_params[key]; 62 } 63 }; 64 65 /** 66 * Update uploader.settings.multipart_params object with new data. 67 * 68 * @param {object} obj 69 */ 70 phpbb.plupload.updateMultipartParams = function(obj) { 71 var settings = phpbb.plupload.uploader.settings; 72 settings.multipart_params = $.extend(settings.multipart_params, obj); 73 }; 74 75 /** 76 * Convert the array of attachment objects into an object that PHP would expect as POST data. 77 * 78 * @returns {object} An object in the form 'attachment_data[i][key]': value as 79 * expected by the server 80 */ 81 phpbb.plupload.getSerializedData = function() { 82 var obj = {}; 83 for (var i = 0; i < phpbb.plupload.data.length; i++) { 84 var datum = phpbb.plupload.data[i]; 85 for (var key in datum) { 86 if (!datum.hasOwnProperty(key)) { 87 continue; 88 } 89 90 obj['attachment_data[' + i + '][' + key + ']'] = datum[key]; 91 } 92 } 93 94 // Insert form data 95 var $pluploadForm = $(phpbb.plupload.config.form_hook).first(); 96 obj.creation_time = $pluploadForm.find('input[type=hidden][name="creation_time"]').val(); 97 obj.form_token = $pluploadForm.find('input[type=hidden][name="form_token"]').val(); 98 99 return obj; 100 }; 101 102 /** 103 * Get the index from the phpbb.plupload.data array where the given 104 * attachment id appears. 105 * 106 * @param {int} attachId The attachment id of the file. 107 * @returns {bool|int} Index of the file if exists, otherwise false. 108 */ 109 phpbb.plupload.getIndex = function(attachId) { 110 var index = $.inArray(Number(attachId), phpbb.plupload.ids); 111 return (index !== -1) ? index : false; 112 }; 113 114 /** 115 * Set the data in phpbb.plupload.data and phpbb.plupload.ids arrays. 116 * 117 * @param {Array} data Array containing the new data to use. In the form of 118 * array(index => object(property: value). Requires attach_id to be one of the object properties. 119 */ 120 phpbb.plupload.setData = function(data) { 121 // Make sure that the array keys are reset. 122 phpbb.plupload.ids = phpbb.plupload.data = []; 123 phpbb.plupload.data = data; 124 125 for (var i = 0; i < data.length; i++) { 126 phpbb.plupload.ids.push(Number(data[i].attach_id)); 127 } 128 }; 129 130 /** 131 * Update the attachment data in the HTML and the phpbb & phpbb.plupload objects. 132 * 133 * @param {Array} data Array containing the new data to use. 134 * @param {string} action The action that required the update. Used to update the inline attachment bbcodes. 135 * @param {int} index The index from phpbb.plupload_ids that was affected by the action. 136 * @param {Array} downloadUrl Optional array of download urls to update. 137 */ 138 phpbb.plupload.update = function(data, action, index, downloadUrl) { 139 140 phpbb.plupload.updateBbcode(action, index); 141 phpbb.plupload.setData(data); 142 phpbb.plupload.updateRows(downloadUrl); 143 phpbb.plupload.clearParams(); 144 phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData()); 145 }; 146 147 /** 148 * Update the relevant elements and hidden data for all attachments. 149 * 150 * @param {Array} downloadUrl Optional array of download urls to update. 151 */ 152 phpbb.plupload.updateRows = function(downloadUrl) { 153 for (var i = 0; i < phpbb.plupload.ids.length; i++) { 154 phpbb.plupload.updateRow(i, downloadUrl); 155 } 156 }; 157 158 /** 159 * Insert a row for a new attachment. This expects an HTML snippet in the HTML 160 * using the id "attach-row-tpl" to be present. This snippet is cloned and the 161 * data for the file inserted into it. The row is then appended or prepended to 162 * #file-list based on the attach_order setting. 163 * 164 * @param {object} file Plupload file object for the new attachment. 165 */ 166 phpbb.plupload.insertRow = function(file) { 167 var row = $(phpbb.plupload.rowTpl); 168 169 row.attr('id', file.id); 170 row.find('.file-name').html(plupload.xmlEncode(file.name)); 171 row.find('.file-size').html(plupload.formatSize(file.size)); 172 173 if (phpbb.plupload.order === 'desc') { 174 $('#file-list').prepend(row); 175 } else { 176 $('#file-list').append(row); 177 } 178 }; 179 180 /** 181 * Update the relevant elements and hidden data for an attachment. 182 * 183 * @param {int} index The index from phpbb.plupload.ids of the attachment to edit. 184 * @param {Array} downloadUrl Optional array of download urls to update. 185 */ 186 phpbb.plupload.updateRow = function(index, downloadUrl) { 187 var attach = phpbb.plupload.data[index], 188 row = $('[data-attach-id="' + attach.attach_id + '"]'); 189 190 // Add the link to the file 191 if (typeof downloadUrl !== 'undefined' && typeof downloadUrl[index] !== 'undefined') { 192 var url = downloadUrl[index].replace('&', '&'), 193 link = $('<a></a>'); 194 195 link.attr('href', url).html(attach.real_filename); 196 row.find('.file-name').html(link); 197 } 198 199 row.find('textarea').attr('name', 'comment_list[' + index + ']'); 200 phpbb.plupload.updateHiddenData(row, attach, index); 201 }; 202 203 /** 204 * Update hidden input data for an attachment. 205 * 206 * @param {object} row jQuery object for the attachment row. 207 * @param {object} attach Attachment data object from phpbb.plupload.data 208 * @param {int} index Attachment index from phpbb.plupload.ids 209 */ 210 phpbb.plupload.updateHiddenData = function(row, attach, index) { 211 row.find('input[type="hidden"]').remove(); 212 213 for (var key in attach) { 214 if (!attach.hasOwnProperty(key)) { 215 return; 216 } 217 218 var input = $('<input />') 219 .attr('type', 'hidden') 220 .attr('name', 'attachment_data[' + index + '][' + key + ']') 221 .attr('value', attach[key]); 222 $(row).append(input); 223 } 224 }; 225 226 /** 227 * Deleting a file removes it from the queue and fires an AJAX event to the 228 * server to tell it to remove the temporary attachment. The server 229 * responds with the updated attachment data list so that any future 230 * uploads can maintain state with the server 231 * 232 * @param {object} row jQuery object for the attachment row. 233 * @param {int} attachId Attachment id of the file to be removed. 234 */ 235 phpbb.plupload.deleteFile = function(row, attachId) { 236 // If there's no attach id, then the file hasn't been uploaded. Simply delete the row. 237 if (typeof attachId === 'undefined') { 238 var file = phpbb.plupload.uploader.getFile(row.attr('id')); 239 phpbb.plupload.uploader.removeFile(file); 240 241 row.slideUp(100, function() { 242 row.remove(); 243 phpbb.plupload.hideEmptyList(); 244 }); 245 } 246 247 var index = phpbb.plupload.getIndex(attachId); 248 row.find('.file-status').toggleClass('file-uploaded file-working'); 249 250 if (index === false) { 251 return; 252 } 253 var fields = {}; 254 fields['delete_file[' + index + ']'] = 1; 255 256 var always = function() { 257 row.find('.file-status').removeClass('file-working'); 258 }; 259 260 var done = function(response) { 261 if (typeof response !== 'object') { 262 return; 263 } 264 265 // trigger_error() was called which likely means a permission error was encountered. 266 if (typeof response.title !== 'undefined') { 267 phpbb.plupload.uploader.trigger('Error', { message: response.message }); 268 // We will have to assume that the deletion failed. So leave the file status as uploaded. 269 row.find('.file-status').toggleClass('file-uploaded'); 270 271 return; 272 } 273 274 // Handle errors while deleting file 275 if (typeof response.error !== 'undefined') { 276 phpbb.alert(phpbb.plupload.lang.ERROR, response.error.message); 277 278 // We will have to assume that the deletion failed. So leave the file status as uploaded. 279 row.find('.file-status').toggleClass('file-uploaded'); 280 281 return; 282 } 283 284 phpbb.plupload.update(response, 'removal', index); 285 // Check if the user can upload files now if he had reached the max files limit. 286 phpbb.plupload.handleMaxFilesReached(); 287 288 if (row.attr('id')) { 289 var file = phpbb.plupload.uploader.getFile(row.attr('id')); 290 phpbb.plupload.uploader.removeFile(file); 291 } 292 row.slideUp(100, function() { 293 row.remove(); 294 // Hide the file list if it's empty now. 295 phpbb.plupload.hideEmptyList(); 296 }); 297 phpbb.plupload.uploader.trigger('FilesRemoved'); 298 }; 299 300 $.ajax(phpbb.plupload.config.url, { 301 type: 'POST', 302 data: $.extend(fields, phpbb.plupload.getSerializedData()), 303 headers: phpbb.plupload.config.headers 304 }) 305 .always(always) 306 .done(done); 307 }; 308 309 /** 310 * Check the attachment list and hide its container if it's empty. 311 */ 312 phpbb.plupload.hideEmptyList = function() { 313 if (!$('#file-list').children().length) { 314 $('#file-list-container').slideUp(100); 315 } 316 }; 317 318 /** 319 * Update the indices used in inline attachment bbcodes. This ensures that the 320 * bbcodes correspond to the correct file after a file is added or removed. 321 * This should be called before the phpbb.plupload,data and phpbb.plupload.ids 322 * arrays are updated, otherwise it will not work correctly. 323 * 324 * @param {string} action The action that occurred -- either "addition" or "removal" 325 * @param {int} index The index of the attachment from phpbb.plupload.ids that was affected. 326 */ 327 phpbb.plupload.updateBbcode = function(action, index) { 328 var textarea = $('#message', phpbb.plupload.form), 329 text = textarea.val(), 330 removal = (action === 'removal'); 331 332 // Return if the bbcode isn't used at all. 333 if (text.indexOf('[attachment=') === -1) { 334 return; 335 } 336 337 function runUpdate(i) { 338 var regex = new RegExp('\\[attachment=' + i + '\\](.*?)\\[\\/attachment\\]', 'g'); 339 text = text.replace(regex, function updateBbcode(_, fileName) { 340 // Remove the bbcode if the file was removed. 341 if (removal && index === i) { 342 return ''; 343 } 344 var newIndex = i + ((removal) ? -1 : 1); 345 return '[attachment=' + newIndex + ']' + fileName + '[/attachment]'; 346 }); 347 } 348 349 // Loop forwards when removing and backwards when adding ensures we don't 350 // corrupt the bbcode index. 351 var i; 352 if (removal) { 353 for (i = index; i < phpbb.plupload.ids.length; i++) { 354 runUpdate(i); 355 } 356 } else { 357 for (i = phpbb.plupload.ids.length - 1; i >= index; i--) { 358 runUpdate(i); 359 } 360 } 361 362 textarea.val(text); 363 }; 364 365 /** 366 * Get Plupload file objects based on their upload status. 367 * 368 * @param {int} status Plupload status - plupload.DONE, plupload.FAILED, 369 * plupload.QUEUED, plupload.STARTED, plupload.STOPPED 370 * 371 * @returns {Array} The Plupload file objects matching the status. 372 */ 373 phpbb.plupload.getFilesByStatus = function(status) { 374 var files = []; 375 376 $.each(phpbb.plupload.uploader.files, function(i, file) { 377 if (file.status === status) { 378 files.push(file); 379 } 380 }); 381 return files; 382 }; 383 384 /** 385 * Check whether the user has reached the maximun number of files that he's allowed 386 * to upload. If so, disables the uploader and marks the queued files as failed. Otherwise 387 * makes sure that the uploader is enabled. 388 * 389 * @returns {bool} True if the limit has been reached. False if otherwise. 390 */ 391 phpbb.plupload.handleMaxFilesReached = function() { 392 // If there is no limit, the user is an admin or moderator. 393 if (!phpbb.plupload.maxFiles) { 394 return false; 395 } 396 397 if (phpbb.plupload.maxFiles <= phpbb.plupload.ids.length) { 398 // Fail the rest of the queue. 399 phpbb.plupload.markQueuedFailed(phpbb.plupload.lang.TOO_MANY_ATTACHMENTS); 400 // Disable the uploader. 401 phpbb.plupload.disableUploader(); 402 phpbb.plupload.uploader.trigger('Error', { message: phpbb.plupload.lang.TOO_MANY_ATTACHMENTS }); 403 404 return true; 405 } else if (phpbb.plupload.maxFiles > phpbb.plupload.ids.length) { 406 // Enable the uploader if the user is under the limit 407 phpbb.plupload.enableUploader(); 408 } 409 return false; 410 }; 411 412 /** 413 * Disable the uploader 414 */ 415 phpbb.plupload.disableUploader = function() { 416 $('#add_files').addClass('disabled'); 417 phpbb.plupload.uploader.disableBrowse(); 418 }; 419 420 /** 421 * Enable the uploader 422 */ 423 phpbb.plupload.enableUploader = function() { 424 $('#add_files').removeClass('disabled'); 425 phpbb.plupload.uploader.disableBrowse(false); 426 }; 427 428 /** 429 * Mark all queued files as failed. 430 * 431 * @param {string} error Error message to present to the user. 432 */ 433 phpbb.plupload.markQueuedFailed = function(error) { 434 var files = phpbb.plupload.getFilesByStatus(plupload.QUEUED); 435 436 $.each(files, function(i, file) { 437 $('#' + file.id).find('.file-progress').hide(); 438 phpbb.plupload.fileError(file, error); 439 }); 440 }; 441 442 /** 443 * Marks a file as failed and sets the error message for it. 444 * 445 * @param {object} file Plupload file object that failed. 446 * @param {string} error Error message to present to the user. 447 */ 448 phpbb.plupload.fileError = function(file, error) { 449 file.status = plupload.FAILED; 450 file.error = error; 451 $('#' + file.id).find('.file-status') 452 .addClass('file-error') 453 .attr({ 454 'data-error-title': phpbb.plupload.lang.ERROR, 455 'data-error-message': error 456 }); 457 }; 458 459 460 /** 461 * Set up the Plupload object and get some basic data. 462 */ 463 phpbb.plupload.uploader = new plupload.Uploader(phpbb.plupload.config); 464 phpbb.plupload.initialize(); 465 466 /** 467 * Add a file filter to check for max file sizes per mime type. 468 */ 469 plupload.addFileFilter('mime_types_max_file_size', function(types, file, callback) { 470 if (file.size !== 'undefined') { 471 $(types).each(function(i, type) { 472 let extensions = [], 473 extsArray = type.extensions.split(','); 474 475 $(extsArray).each(function(i, extension) { 476 /^\s*\*\s*$/.test(extension) ? extensions.push("\\.*") : extensions.push("\\." + extension.replace(new RegExp("[" + "/^$.*+?|()[]{}\\".replace(/./g, "\\$&") + "]", "g"), "\\$&")); 477 }); 478 479 let regex = new RegExp("(" + extensions.join("|") + ")$", "i"); 480 481 if (regex.test(file.name)) { 482 if (type.max_file_size !== 'undefined' && type.max_file_size) { 483 if (file.size > type.max_file_size) { 484 phpbb.plupload.uploader.trigger('Error', { 485 code: plupload.FILE_SIZE_ERROR, 486 message: plupload.translate('File size error.'), 487 file: file 488 }); 489 490 callback(false); 491 } else { 492 callback(true); 493 } 494 } else { 495 callback(true); 496 } 497 498 return false; 499 } 500 }); 501 } 502 }); 503 504 var $fileList = $('#file-list'); 505 506 /** 507 * Insert inline attachment bbcode. 508 */ 509 $fileList.on('click', '.file-inline-bbcode', function(e) { 510 var attachId = $(this).parents('.attach-row').attr('data-attach-id'), 511 index = phpbb.plupload.getIndex(attachId); 512 513 attachInline(index, phpbb.plupload.data[index].real_filename); 514 e.preventDefault(); 515 }); 516 517 /** 518 * Delete a file. 519 */ 520 $fileList.on('click', '.file-delete', function(e) { 521 var row = $(this).parents('.attach-row'), 522 attachId = row.attr('data-attach-id'); 523 524 phpbb.plupload.deleteFile(row, attachId); 525 e.preventDefault(); 526 }); 527 528 /** 529 * Display the error message for a particular file when the error icon is clicked. 530 */ 531 $fileList.on('click', '.file-error', function(e) { 532 phpbb.alert($(this).attr('data-error-title'), $(this).attr('data-error-message')); 533 e.preventDefault(); 534 }); 535 536 /** 537 * Fires when an error occurs. 538 */ 539 phpbb.plupload.uploader.bind('Error', function(up, error) { 540 error.file.name = plupload.xmlEncode(error.file.name); 541 542 // The error message that Plupload provides for these is vague, so we'll be more specific. 543 if (error.code === plupload.FILE_EXTENSION_ERROR) { 544 error.message = plupload.translate('Invalid file extension:') + ' ' + error.file.name; 545 } else if (error.code === plupload.FILE_SIZE_ERROR) { 546 error.message = plupload.translate('File too large:') + ' ' + error.file.name; 547 } 548 phpbb.alert(phpbb.plupload.lang.ERROR, error.message); 549 }); 550 551 /** 552 * Fires before a given file is about to be uploaded. This allows us to 553 * send the real filename along with the chunk. This is necessary because 554 * for some reason the filename is set to 'blob' whenever a file is chunked 555 * 556 * @param {object} up The plupload.Uploader object 557 * @param {object} file The plupload.File object that is about to be uploaded 558 */ 559 phpbb.plupload.uploader.bind('BeforeUpload', function(up, file) { 560 if (phpbb.plupload.handleMaxFilesReached()) { 561 return; 562 } 563 564 phpbb.plupload.updateMultipartParams({ real_filename: file.name }); 565 }); 566 567 /** 568 * Fired when a single chunk of any given file is uploaded. This parses the 569 * response from the server and checks for an error. If an error occurs it 570 * is reported to the user and the upload of this particular file is halted 571 * 572 * @param {object} up The plupload.Uploader object 573 * @param {object} file The plupload.File object whose chunk has just 574 * been uploaded 575 * @param {object} response The response object from the server 576 */ 577 phpbb.plupload.uploader.bind('ChunkUploaded', function(up, file, response) { 578 if (response.chunk >= response.chunks - 1) { 579 return; 580 } 581 582 var json = {}; 583 try { 584 json = $.parseJSON(response.response); 585 } catch (e) { 586 file.status = plupload.FAILED; 587 up.trigger('FileUploaded', file, { 588 response: JSON.stringify({ 589 error: { 590 message: 'Error parsing server response.' 591 } 592 }) 593 }); 594 } 595 596 // If trigger_error() was called, then a permission error likely occurred. 597 if (typeof json.title !== 'undefined') { 598 json.error = { message: json.message }; 599 } 600 601 if (json.error) { 602 file.status = plupload.FAILED; 603 up.trigger('FileUploaded', file, { 604 response: JSON.stringify({ 605 error: { 606 message: json.error.message 607 } 608 }) 609 }); 610 } 611 }); 612 613 /** 614 * Fires when files are added to the queue. 615 */ 616 phpbb.plupload.uploader.bind('FilesAdded', function(up, files) { 617 // Prevent unnecessary requests to the server if the user already uploaded 618 // the maximum number of files allowed. 619 if (phpbb.plupload.handleMaxFilesReached()) { 620 return; 621 } 622 623 // Switch the active tab if the style supports it 624 if (typeof activateSubPanel === 'function') { 625 activateSubPanel('attach-panel'); // jshint ignore: line 626 } 627 628 // Show the file list if there aren't any files currently. 629 var $fileListContainer = $('#file-list-container'); 630 if (!$fileListContainer.is(':visible')) { 631 $fileListContainer.show(100); 632 } 633 634 $.each(files, function(i, file) { 635 phpbb.plupload.insertRow(file); 636 }); 637 638 up.bind('UploadProgress', function(up, file) { 639 $('.file-progress-bar', '#' + file.id).css('width', file.percent + '%'); 640 $('#file-total-progress-bar').css('width', up.total.percent + '%'); 641 }); 642 643 // Do not allow more files to be added to the running queue. 644 phpbb.plupload.disableUploader(); 645 646 // Start uploading the files once the user has selected them. 647 up.start(); 648 }); 649 650 651 /** 652 * Fires when an entire file has been uploaded. It checks for errors 653 * returned by the server otherwise parses the list of attachment data and 654 * appends it to the next file upload so that the server can maintain state 655 * with regards to the attachments in a given post 656 * 657 * @param {object} up The plupload.Uploader object 658 * @param {object} file The plupload.File object that has just been 659 * uploaded 660 * @param {string} response The response string from the server 661 */ 662 phpbb.plupload.uploader.bind('FileUploaded', function(up, file, response) { 663 var json = {}, 664 row = $('#' + file.id), 665 error; 666 667 // Hide the progress indicator. 668 row.find('.file-progress').hide(); 669 670 try { 671 json = JSON.parse(response.response); 672 } catch (e) { 673 error = 'Error parsing server response.'; 674 } 675 676 // If trigger_error() was called, then a permission error likely occurred. 677 if (typeof json.title !== 'undefined') { 678 error = json.message; 679 up.trigger('Error', { message: error }); 680 681 // The rest of the queue will fail. 682 phpbb.plupload.markQueuedFailed(error); 683 } else if (json.error) { 684 error = json.error.message; 685 } 686 687 if (typeof error !== 'undefined') { 688 phpbb.plupload.fileError(file, error); 689 } else if (file.status === plupload.DONE) { 690 file.attachment_data = json.data[0]; 691 692 row.attr('data-attach-id', file.attachment_data.attach_id); 693 row.find('.file-inline-bbcode').show(); 694 row.find('.file-status').addClass('file-uploaded'); 695 phpbb.plupload.update(json.data, 'addition', 0, [json.download_url]); 696 } 697 }); 698 699 /** 700 * Fires when the entire queue of files have been uploaded. 701 */ 702 phpbb.plupload.uploader.bind('UploadComplete', function() { 703 // Hide the progress bar 704 setTimeout(function() { 705 $('#file-total-progress-bar').fadeOut(500, function() { 706 $(this).css('width', 0).show(); 707 }); 708 }, 2000); 709 710 // Re-enable the uploader 711 phpbb.plupload.enableUploader(); 712 }); 713 714 })(jQuery); // Avoid conflicts with other libraries
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:33:01 2020 | Cross-referenced by PHPXref 0.7.1 |