Commit 64393a57 authored by Nicolas Widart's avatar Nicolas Widart

Squashed 'Modules/Media/' content from commit 551ec60

git-subtree-dir: Modules/Media
git-subtree-split: 551ec6006744ecfc3bbe43b4f650113746e25aef
parents
.php_cs.cache
vendor/
composer.lock
Modules/
/.project
build/
<?php
$finder = Symfony\CS\Finder\DefaultFinder::create()
->exclude('Modules')
->exclude('vendor')
->in(__DIR__)
;
return Symfony\CS\Config\Config::create()
->setUsingCache(true)
->level(Symfony\CS\FixerInterface::PSR2_LEVEL)
->fixers(array(
// Concatenation should be used with at least one whitespace around.
'concat_with_spaces',
// Unused use statements must be removed.
'ordered_use',
// Removes extra empty lines.
'extra_empty_lines',
// Removes line breaks between use statements.
'remove_lines_between_uses',
// An empty line feed should precede a return statement.
'return',
// Unused use statements must be removed.
'unused_use',
// Remove trailing whitespace at the end of blank lines.
'whitespacy_lines',
// There MUST be one blank line after the namespace declaration.
'line_after_namespace',
// There should be exactly one blank line before a namespace declaration.
'single_blank_line_before_namespace',
// Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block.
'single_line_after_imports',
// Ensure there is no code on the same line as the PHP open tag and it is followed by a blankline.
'blankline_after_open_tag',
// Remove duplicated semicolons.
'duplicate_semicolon',
// PHP multi-line arrays should have a trailing comma.
'multiline_array_trailing_comma',
// There should be no empty lines after class opening brace.
'no_blank_lines_after_class_opening',
// There should not be blank lines between docblock and the documented element.
'no_empty_lines_after_phpdocs',
// Phpdocs should start and end with content, excluding the very first and last line of the docblocks.
'phpdoc_trim',
// Removes line breaks between use statements.
'remove_lines_between_uses',
))
->finder($finder);
rules:
php.interface_has_no_interface_suffix:
enabled: false
language: php
php:
- 7
- 5.6
cache:
directories:
- $HOME/.composer/cache
env:
- LARAVEL_VERSION="~5.2" TESTBENCH_VERSION="~3.2"
before_script:
- travis_retry composer self-update
- travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-dist
script: phpunit
sudo: false
notifications:
slack: asgardcms:rQqM7V2EoZOrsv6GgAemZyOS
email:
- n.widart@gmail.com
- josh@joshbrown.me
/* The MIT License */
.dropzone,
.dropzone *,
.dropzone-previews,
.dropzone-previews * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.dropzone {
position: relative;
border: 1px solid rgba(0,0,0,0.08);
background: rgba(0,0,0,0.02);
padding: 1em;
}
.dropzone.dz-clickable {
cursor: pointer;
}
.dropzone.dz-clickable .dz-message,
.dropzone.dz-clickable .dz-message span {
cursor: pointer;
}
.dropzone.dz-clickable * {
cursor: default;
}
.dropzone .dz-message {
opacity: 1;
-ms-filter: none;
filter: none;
}
.dropzone.dz-drag-hover {
border-color: rgba(0,0,0,0.15);
background: rgba(0,0,0,0.04);
}
.dropzone.dz-started .dz-message {
display: none;
}
.dropzone .dz-preview,
.dropzone-previews .dz-preview {
background: rgba(255,255,255,0.8);
position: relative;
display: inline-block;
margin: 17px;
vertical-align: top;
border: 1px solid #acacac;
padding: 6px 6px 6px 6px;
}
.dropzone .dz-preview.dz-file-preview [data-dz-thumbnail],
.dropzone-previews .dz-preview.dz-file-preview [data-dz-thumbnail] {
display: none;
}
.dropzone .dz-preview .dz-details,
.dropzone-previews .dz-preview .dz-details {
width: 100px;
height: 100px;
position: relative;
background: #ebebeb;
padding: 5px;
margin-bottom: 22px;
}
.dropzone .dz-preview .dz-details .dz-filename,
.dropzone-previews .dz-preview .dz-details .dz-filename {
overflow: hidden;
height: 100%;
}
.dropzone .dz-preview .dz-details img,
.dropzone-previews .dz-preview .dz-details img {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
}
.dropzone .dz-preview .dz-details .dz-size,
.dropzone-previews .dz-preview .dz-details .dz-size {
position: absolute;
bottom: -28px;
left: 3px;
height: 28px;
line-height: 28px;
}
.dropzone .dz-preview.dz-error .dz-error-mark,
.dropzone-previews .dz-preview.dz-error .dz-error-mark {
display: block;
}
.dropzone .dz-preview.dz-success .dz-success-mark,
.dropzone-previews .dz-preview.dz-success .dz-success-mark {
display: block;
}
.dropzone .dz-preview:hover .dz-details img,
.dropzone-previews .dz-preview:hover .dz-details img {
display: none;
}
.dropzone .dz-preview .dz-success-mark,
.dropzone-previews .dz-preview .dz-success-mark,
.dropzone .dz-preview .dz-error-mark,
.dropzone-previews .dz-preview .dz-error-mark {
display: none;
position: absolute;
width: 40px;
height: 40px;
font-size: 30px;
text-align: center;
right: -10px;
top: -10px;
}
.dropzone .dz-preview .dz-success-mark,
.dropzone-previews .dz-preview .dz-success-mark {
color: #8cc657;
}
.dropzone .dz-preview .dz-error-mark,
.dropzone-previews .dz-preview .dz-error-mark {
color: #ee162d;
}
.dropzone .dz-preview .dz-progress,
.dropzone-previews .dz-preview .dz-progress {
position: absolute;
top: 100px;
left: 6px;
right: 6px;
height: 6px;
background: #d7d7d7;
display: none;
}
.dropzone .dz-preview .dz-progress .dz-upload,
.dropzone-previews .dz-preview .dz-progress .dz-upload {
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 0%;
background-color: #8cc657;
}
.dropzone .dz-preview.dz-processing .dz-progress,
.dropzone-previews .dz-preview.dz-processing .dz-progress {
display: block;
}
.dropzone .dz-preview .dz-error-message,
.dropzone-previews .dz-preview .dz-error-message {
display: none;
position: absolute;
top: -5px;
left: -20px;
background: rgba(245,245,245,0.8);
padding: 8px 10px;
color: #800;
min-width: 140px;
max-width: 500px;
z-index: 500;
}
.dropzone .dz-preview:hover.dz-error .dz-error-message,
.dropzone-previews .dz-preview:hover.dz-error .dz-error-message {
display: block;
}
.dropzone {
border: 1px solid rgba(0,0,0,0.03);
min-height: 360px;
-webkit-border-radius: 3px;
border-radius: 3px;
background: rgba(0,0,0,0.03);
padding: 23px;
}
.dropzone .dz-default.dz-message {
opacity: 1;
-ms-filter: none;
filter: none;
-webkit-transition: opacity 0.3s ease-in-out;
-moz-transition: opacity 0.3s ease-in-out;
-o-transition: opacity 0.3s ease-in-out;
-ms-transition: opacity 0.3s ease-in-out;
transition: opacity 0.3s ease-in-out;
background-image: url("../images/spritemap.png");
background-repeat: no-repeat;
background-position: 0 0;
position: absolute;
width: 428px;
height: 123px;
margin-left: -214px;
margin-top: -61.5px;
top: 50%;
left: 50%;
}
@media all and (-webkit-min-device-pixel-ratio:1.5),(min--moz-device-pixel-ratio:1.5),(-o-min-device-pixel-ratio:1.5/1),(min-device-pixel-ratio:1.5),(min-resolution:138dpi),(min-resolution:1.5dppx) {
.dropzone .dz-default.dz-message {
background-image: url("../images/spritemap@2x.png");
-webkit-background-size: 428px 406px;
-moz-background-size: 428px 406px;
background-size: 428px 406px;
}
}
.dropzone .dz-default.dz-message span {
display: none;
}
.dropzone.dz-square .dz-default.dz-message {
background-position: 0 -123px;
width: 268px;
margin-left: -134px;
height: 174px;
margin-top: -87px;
}
.dropzone.dz-drag-hover .dz-message {
opacity: 0.15;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=15)";
filter: alpha(opacity=15);
}
.dropzone.dz-started .dz-message {
display: block;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
}
.dropzone .dz-preview,
.dropzone-previews .dz-preview {
-webkit-box-shadow: 1px 1px 4px rgba(0,0,0,0.16);
box-shadow: 1px 1px 4px rgba(0,0,0,0.16);
font-size: 14px;
}
.dropzone .dz-preview.dz-image-preview:hover .dz-details img,
.dropzone-previews .dz-preview.dz-image-preview:hover .dz-details img {
display: block;
opacity: 0.1;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=10)";
filter: alpha(opacity=10);
}
.dropzone .dz-preview.dz-success .dz-success-mark,
.dropzone-previews .dz-preview.dz-success .dz-success-mark {
opacity: 1;
-ms-filter: none;
filter: none;
}
.dropzone .dz-preview.dz-error .dz-error-mark,
.dropzone-previews .dz-preview.dz-error .dz-error-mark {
opacity: 1;
-ms-filter: none;
filter: none;
}
.dropzone .dz-preview.dz-error .dz-progress .dz-upload,
.dropzone-previews .dz-preview.dz-error .dz-progress .dz-upload {
background: #ee1e2d;
}
.dropzone .dz-preview .dz-error-mark,
.dropzone-previews .dz-preview .dz-error-mark,
.dropzone .dz-preview .dz-success-mark,
.dropzone-previews .dz-preview .dz-success-mark {
display: block;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
-webkit-transition: opacity 0.4s ease-in-out;
-moz-transition: opacity 0.4s ease-in-out;
-o-transition: opacity 0.4s ease-in-out;
-ms-transition: opacity 0.4s ease-in-out;
transition: opacity 0.4s ease-in-out;
background-image: url("../images/spritemap.png");
background-repeat: no-repeat;
}
@media all and (-webkit-min-device-pixel-ratio:1.5),(min--moz-device-pixel-ratio:1.5),(-o-min-device-pixel-ratio:1.5/1),(min-device-pixel-ratio:1.5),(min-resolution:138dpi),(min-resolution:1.5dppx) {
.dropzone .dz-preview .dz-error-mark,
.dropzone-previews .dz-preview .dz-error-mark,
.dropzone .dz-preview .dz-success-mark,
.dropzone-previews .dz-preview .dz-success-mark {
background-image: url("../images/spritemap@2x.png");
-webkit-background-size: 428px 406px;
-moz-background-size: 428px 406px;
background-size: 428px 406px;
}
}
.dropzone .dz-preview .dz-error-mark span,
.dropzone-previews .dz-preview .dz-error-mark span,
.dropzone .dz-preview .dz-success-mark span,
.dropzone-previews .dz-preview .dz-success-mark span {
display: none;
}
.dropzone .dz-preview .dz-error-mark,
.dropzone-previews .dz-preview .dz-error-mark {
background-position: -268px -123px;
}
.dropzone .dz-preview .dz-success-mark,
.dropzone-previews .dz-preview .dz-success-mark {
background-position: -268px -163px;
}
.dropzone .dz-preview .dz-progress .dz-upload,
.dropzone-previews .dz-preview .dz-progress .dz-upload {
-webkit-animation: loading 0.4s linear infinite;
-moz-animation: loading 0.4s linear infinite;
-o-animation: loading 0.4s linear infinite;
-ms-animation: loading 0.4s linear infinite;
animation: loading 0.4s linear infinite;
-webkit-transition: width 0.3s ease-in-out;
-moz-transition: width 0.3s ease-in-out;
-o-transition: width 0.3s ease-in-out;
-ms-transition: width 0.3s ease-in-out;
transition: width 0.3s ease-in-out;
-webkit-border-radius: 2px;
border-radius: 2px;
position: absolute;
top: 0;
left: 0;
width: 0%;
height: 100%;
background-image: url("../images/spritemap.png");
background-repeat: repeat-x;
background-position: 0px -400px;
}
@media all and (-webkit-min-device-pixel-ratio:1.5),(min--moz-device-pixel-ratio:1.5),(-o-min-device-pixel-ratio:1.5/1),(min-device-pixel-ratio:1.5),(min-resolution:138dpi),(min-resolution:1.5dppx) {
.dropzone .dz-preview .dz-progress .dz-upload,
.dropzone-previews .dz-preview .dz-progress .dz-upload {
background-image: url("../images/spritemap@2x.png");
-webkit-background-size: 428px 406px;
-moz-background-size: 428px 406px;
background-size: 428px 406px;
}
}
.dropzone .dz-preview.dz-success .dz-progress,
.dropzone-previews .dz-preview.dz-success .dz-progress {
display: block;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
-webkit-transition: opacity 0.4s ease-in-out;
-moz-transition: opacity 0.4s ease-in-out;
-o-transition: opacity 0.4s ease-in-out;
-ms-transition: opacity 0.4s ease-in-out;
transition: opacity 0.4s ease-in-out;
}
.dropzone .dz-preview .dz-error-message,
.dropzone-previews .dz-preview .dz-error-message {
display: block;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
-webkit-transition: opacity 0.3s ease-in-out;
-moz-transition: opacity 0.3s ease-in-out;
-o-transition: opacity 0.3s ease-in-out;
-ms-transition: opacity 0.3s ease-in-out;
transition: opacity 0.3s ease-in-out;
}
.dropzone .dz-preview:hover.dz-error .dz-error-message,
.dropzone-previews .dz-preview:hover.dz-error .dz-error-message {
opacity: 1;
-ms-filter: none;
filter: none;
}
.dropzone a.dz-remove,
.dropzone-previews a.dz-remove {
background-image: -webkit-linear-gradient(top, #fafafa, #eee);
background-image: -moz-linear-gradient(top, #fafafa, #eee);
background-image: -o-linear-gradient(top, #fafafa, #eee);
background-image: -ms-linear-gradient(top, #fafafa, #eee);
background-image: linear-gradient(to bottom, #fafafa, #eee);
-webkit-border-radius: 2px;
border-radius: 2px;
border: 1px solid #eee;
text-decoration: none;
display: block;
padding: 4px 5px;
text-align: center;
color: #aaa;
margin-top: 26px;
}
.dropzone a.dz-remove:hover,
.dropzone-previews a.dz-remove:hover {
color: #666;
}
@-moz-keyframes loading {
from {
background-position: 0 -400px;
}
to {
background-position: -7px -400px;
}
}
@-webkit-keyframes loading {
from {
background-position: 0 -400px;
}
to {
background-position: -7px -400px;
}
}
@-o-keyframes loading {
from {
background-position: 0 -400px;
}
to {
background-position: -7px -400px;
}
}
@keyframes loading {
from {
background-position: 0 -400px;
}
to {
background-position: -7px -400px;
}
}
;(function(){
/**
* Require the module at `name`.
*
* @param {String} name
* @return {Object} exports
* @api public
*/
function require(name) {
var module = require.modules[name];
if (!module) throw new Error('failed to require "' + name + '"');
if (!('exports' in module) && typeof module.definition === 'function') {
module.client = module.component = true;
module.definition.call(this, module.exports = {}, module);
delete module.definition;
}
return module.exports;
}
/**
* Registered modules.
*/
require.modules = {};
/**
* Register module at `name` with callback `definition`.
*
* @param {String} name
* @param {Function} definition
* @api private
*/
require.register = function (name, definition) {
require.modules[name] = {
definition: definition
};
};
/**
* Define a module's exports immediately with `exports`.
*
* @param {String} name
* @param {Generic} exports
* @api private
*/
require.define = function (name, exports) {
require.modules[name] = {
exports: exports
};
};
require.register("component~emitter@1.1.2", function (exports, module) {
/**
* Expose `Emitter`.
*/
module.exports = Emitter;
/**
* Initialize a new `Emitter`.
*
* @api public
*/
function Emitter(obj) {
if (obj) return mixin(obj);
};
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
function mixin(obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key];
}
return obj;
}
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
(this._callbacks[event] = this._callbacks[event] || [])
.push(fn);
return this;
};
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.once = function(event, fn){
var self = this;
this._callbacks = this._callbacks || {};
function on() {
self.off(event, on);
fn.apply(this, arguments);
}
on.fn = fn;
this.on(event, on);
return this;
};
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
// all
if (0 == arguments.length) {
this._callbacks = {};
return this;
}
// specific event
var callbacks = this._callbacks[event];
if (!callbacks) return this;
// remove all handlers
if (1 == arguments.length) {
delete this._callbacks[event];
return this;
}
// remove specific handler
var cb;
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i];
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1);
break;
}
}
return this;
};
/**
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
*/
Emitter.prototype.emit = function(event){
this._callbacks = this._callbacks || {};
var args = [].slice.call(arguments, 1)
, callbacks = this._callbacks[event];
if (callbacks) {
callbacks = callbacks.slice(0);
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
return this;
};
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
Emitter.prototype.listeners = function(event){
this._callbacks = this._callbacks || {};
return this._callbacks[event] || [];
};
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
Emitter.prototype.hasListeners = function(event){
return !! this.listeners(event).length;
};
});
require.register("dropzone", function (exports, module) {
/**
* Exposing dropzone
*/
module.exports = require("dropzone/lib/dropzone.js");
});
require.register("dropzone/lib/dropzone.js", function (exports, module) {
/*
*
* More info at [www.dropzonejs.com](http://www.dropzonejs.com)
*
* Copyright (c) 2012, Matias Meno
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
(function() {
var Dropzone, Em, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__slice = [].slice;
Em = typeof Emitter !== "undefined" && Emitter !== null ? Emitter : require("component~emitter@1.1.2");
noop = function() {};
Dropzone = (function(_super) {
var extend;
__extends(Dropzone, _super);
/*
This is a list of all available events you can register on a dropzone object.
You can register an event handler like this:
dropzone.on("dragEnter", function() { });
*/
Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached"];
Dropzone.prototype.defaultOptions = {
url: null,
method: "post",
withCredentials: false,
parallelUploads: 2,
uploadMultiple: false,
maxFilesize: 256,
paramName: "file",
createImageThumbnails: true,
maxThumbnailFilesize: 10,
thumbnailWidth: 100,
thumbnailHeight: 100,
maxFiles: null,
params: {},
clickable: true,
ignoreHiddenFiles: true,
acceptedFiles: null,
acceptedMimeTypes: null,
autoProcessQueue: true,
autoQueue: true,
addRemoveLinks: false,
previewsContainer: null,
dictDefaultMessage: "Drop files here to upload",
dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.",
dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.",
dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.",
dictInvalidFileType: "You can't upload files of this type.",
dictResponseError: "Server responded with {{statusCode}} code.",
dictCancelUpload: "Cancel upload",
dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?",
dictRemoveFile: "Remove file",
dictRemoveFileConfirmation: null,
dictMaxFilesExceeded: "You can not upload any more files.",
accept: function(file, done) {
return done();
},
init: function() {
return noop;
},
forceFallback: false,
fallback: function() {
var child, messageElement, span, _i, _len, _ref;
this.element.className = "" + this.element.className + " dz-browser-not-supported";
_ref = this.element.getElementsByTagName("div");
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
if (/(^| )dz-message($| )/.test(child.className)) {
messageElement = child;
child.className = "dz-message";
continue;
}
}
if (!messageElement) {
messageElement = Dropzone.createElement("<div class=\"dz-message\"><span></span></div>");
this.element.appendChild(messageElement);
}
span = messageElement.getElementsByTagName("span")[0];
if (span) {
span.textContent = this.options.dictFallbackMessage;
}
return this.element.appendChild(this.getFallbackForm());
},
resize: function(file) {
var info, srcRatio, trgRatio;
info = {
srcX: 0,
srcY: 0,
srcWidth: file.width,
srcHeight: file.height
};
srcRatio = file.width / file.height;
info.optWidth = this.options.thumbnailWidth;
info.optHeight = this.options.thumbnailHeight;
if ((info.optWidth == null) && (info.optHeight == null)) {
info.optWidth = info.srcWidth;
info.optHeight = info.srcHeight;
} else if (info.optWidth == null) {
info.optWidth = srcRatio * info.optHeight;
} else if (info.optHeight == null) {
info.optHeight = (1 / srcRatio) * info.optWidth;
}
trgRatio = info.optWidth / info.optHeight;
if (file.height < info.optHeight || file.width < info.optWidth) {
info.trgHeight = info.srcHeight;
info.trgWidth = info.srcWidth;
} else {
if (srcRatio > trgRatio) {
info.srcHeight = file.height;
info.srcWidth = info.srcHeight * trgRatio;
} else {
info.srcWidth = file.width;
info.srcHeight = info.srcWidth / trgRatio;
}
}
info.srcX = (file.width - info.srcWidth) / 2;
info.srcY = (file.height - info.srcHeight) / 2;
return info;
},
/*
Those functions register themselves to the events on init and handle all
the user interface specific stuff. Overwriting them won't break the upload
but can break the way it's displayed.
You can overwrite them if you don't like the default behavior. If you just
want to add an additional event handler, register it on the dropzone object
and don't overwrite those options.
*/
drop: function(e) {
return this.element.classList.remove("dz-drag-hover");
},
dragstart: noop,
dragend: function(e) {
return this.element.classList.remove("dz-drag-hover");
},
dragenter: function(e) {
return this.element.classList.add("dz-drag-hover");
},
dragover: function(e) {
return this.element.classList.add("dz-drag-hover");
},
dragleave: function(e) {
return this.element.classList.remove("dz-drag-hover");
},
paste: noop,
reset: function() {
return this.element.classList.remove("dz-started");
},
addedfile: function(file) {
var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results;
if (this.element === this.previewsContainer) {
this.element.classList.add("dz-started");
}
if (this.previewsContainer) {
file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim());
file.previewTemplate = file.previewElement;
this.previewsContainer.appendChild(file.previewElement);
_ref = file.previewElement.querySelectorAll("[data-dz-name]");
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
node.textContent = file.name;
}
_ref1 = file.previewElement.querySelectorAll("[data-dz-size]");
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
node = _ref1[_j];
node.innerHTML = this.filesize(file.size);
}
if (this.options.addRemoveLinks) {
file._removeLink = Dropzone.createElement("<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>" + this.options.dictRemoveFile + "</a>");
file.previewElement.appendChild(file._removeLink);
}
removeFileEvent = (function(_this) {
return function(e) {
e.preventDefault();
e.stopPropagation();
if (file.status === Dropzone.UPLOADING) {
return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() {
return _this.removeFile(file);
});
} else {
if (_this.options.dictRemoveFileConfirmation) {
return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() {
return _this.removeFile(file);
});
} else {
return _this.removeFile(file);
}
}
};
})(this);
_ref2 = file.previewElement.querySelectorAll("[data-dz-remove]");
_results = [];
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
removeLink = _ref2[_k];
_results.push(removeLink.addEventListener("click", removeFileEvent));
}
return _results;
}
},
removedfile: function(file) {
var _ref;
if (file.previewElement) {
if ((_ref = file.previewElement) != null) {
_ref.parentNode.removeChild(file.previewElement);
}
}
return this._updateMaxFilesReachedClass();
},
thumbnail: function(file, dataUrl) {
var thumbnailElement, _i, _len, _ref, _results;
if (file.previewElement) {
file.previewElement.classList.remove("dz-file-preview");
file.previewElement.classList.add("dz-image-preview");
_ref = file.previewElement.querySelectorAll("[data-dz-thumbnail]");
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
thumbnailElement = _ref[_i];
thumbnailElement.alt = file.name;
_results.push(thumbnailElement.src = dataUrl);
}
return _results;
}
},
error: function(file, message) {
var node, _i, _len, _ref, _results;
if (file.previewElement) {
file.previewElement.classList.add("dz-error");
if (typeof message !== "String" && message.error) {
message = message.error;
}
_ref = file.previewElement.querySelectorAll("[data-dz-errormessage]");
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
_results.push(node.textContent = message);
}
return _results;
}
},
errormultiple: noop,
processing: function(file) {
if (file.previewElement) {
file.previewElement.classList.add("dz-processing");
if (file._removeLink) {
return file._removeLink.textContent = this.options.dictCancelUpload;
}
}
},
processingmultiple: noop,
uploadprogress: function(file, progress, bytesSent) {
var node, _i, _len, _ref, _results;
if (file.previewElement) {
_ref = file.previewElement.querySelectorAll("[data-dz-uploadprogress]");
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
_results.push(node.style.width = "" + progress + "%");
}
return _results;
}
},
totaluploadprogress: noop,
sending: noop,
sendingmultiple: noop,
success: function(file) {
if (file.previewElement) {
return file.previewElement.classList.add("dz-success");
}
},
successmultiple: noop,
canceled: function(file) {
return this.emit("error", file, "Upload canceled.");
},
canceledmultiple: noop,
complete: function(file) {
if (file._removeLink) {
return file._removeLink.textContent = this.options.dictRemoveFile;
}
},
completemultiple: noop,
maxfilesexceeded: noop,
maxfilesreached: noop,
previewTemplate: "<div class=\"dz-preview dz-file-preview\">\n <div class=\"dz-details\">\n <div class=\"dz-filename\"><span data-dz-name></span></div>\n <div class=\"dz-size\" data-dz-size></div>\n <img data-dz-thumbnail />\n </div>\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress></span></div>\n <div class=\"dz-success-mark\"><span>✔</span></div>\n <div class=\"dz-error-mark\"><span>✘</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>"
};
extend = function() {
var key, object, objects, target, val, _i, _len;
target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
for (_i = 0, _len = objects.length; _i < _len; _i++) {
object = objects[_i];
for (key in object) {
val = object[key];
target[key] = val;
}
}
return target;
};
function Dropzone(element, options) {
var elementOptions, fallback, _ref;
this.element = element;
this.version = Dropzone.version;
this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, "");
this.clickableElements = [];
this.listeners = [];
this.files = [];
if (typeof this.element === "string") {
this.element = document.querySelector(this.element);
}
if (!(this.element && (this.element.nodeType != null))) {
throw new Error("Invalid dropzone element.");
}
if (this.element.dropzone) {
throw new Error("Dropzone already attached.");
}
Dropzone.instances.push(this);
this.element.dropzone = this;
elementOptions = (_ref = Dropzone.optionsForElement(this.element)) != null ? _ref : {};
this.options = extend({}, this.defaultOptions, elementOptions, options != null ? options : {});
if (this.options.forceFallback || !Dropzone.isBrowserSupported()) {
return this.options.fallback.call(this);
}
if (this.options.url == null) {
this.options.url = this.element.getAttribute("action");
}
if (!this.options.url) {
throw new Error("No URL provided.");
}
if (this.options.acceptedFiles && this.options.acceptedMimeTypes) {
throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated.");
}
if (this.options.acceptedMimeTypes) {
this.options.acceptedFiles = this.options.acceptedMimeTypes;
delete this.options.acceptedMimeTypes;
}
this.options.method = this.options.method.toUpperCase();
if ((fallback = this.getExistingFallback()) && fallback.parentNode) {
fallback.parentNode.removeChild(fallback);
}
if (this.options.previewsContainer !== false) {
if (this.options.previewsContainer) {
this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer");
} else {
this.previewsContainer = this.element;
}
}
if (this.options.clickable) {
if (this.options.clickable === true) {
this.clickableElements = [this.element];
} else {
this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable");
}
}
this.init();
}
Dropzone.prototype.getAcceptedFiles = function() {
var file, _i, _len, _ref, _results;
_ref = this.files;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
if (file.accepted) {
_results.push(file);
}
}
return _results;
};
Dropzone.prototype.getRejectedFiles = function() {
var file, _i, _len, _ref, _results;
_ref = this.files;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
if (!file.accepted) {
_results.push(file);
}
}
return _results;
};
Dropzone.prototype.getFilesWithStatus = function(status) {
var file, _i, _len, _ref, _results;
_ref = this.files;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
if (file.status === status) {
_results.push(file);
}
}
return _results;
};
Dropzone.prototype.getQueuedFiles = function() {
return this.getFilesWithStatus(Dropzone.QUEUED);
};
Dropzone.prototype.getUploadingFiles = function() {
return this.getFilesWithStatus(Dropzone.UPLOADING);
};
Dropzone.prototype.getActiveFiles = function() {
var file, _i, _len, _ref, _results;
_ref = this.files;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
if (file.status === Dropzone.UPLOADING || file.status === Dropzone.QUEUED) {
_results.push(file);
}
}
return _results;
};
Dropzone.prototype.init = function() {
var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1;
if (this.element.tagName === "form") {
this.element.setAttribute("enctype", "multipart/form-data");
}
if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) {
this.element.appendChild(Dropzone.createElement("<div class=\"dz-default dz-message\"><span>" + this.options.dictDefaultMessage + "</span></div>"));
}
if (this.clickableElements.length) {
setupHiddenFileInput = (function(_this) {
return function() {
if (_this.hiddenFileInput) {
document.body.removeChild(_this.hiddenFileInput);
}
_this.hiddenFileInput = document.createElement("input");
_this.hiddenFileInput.setAttribute("type", "file");
if ((_this.options.maxFiles == null) || _this.options.maxFiles > 1) {
_this.hiddenFileInput.setAttribute("multiple", "multiple");
}
_this.hiddenFileInput.className = "dz-hidden-input";
if (_this.options.acceptedFiles != null) {
_this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles);
}
_this.hiddenFileInput.style.visibility = "hidden";
_this.hiddenFileInput.style.position = "absolute";
_this.hiddenFileInput.style.top = "0";
_this.hiddenFileInput.style.left = "0";
_this.hiddenFileInput.style.height = "0";
_this.hiddenFileInput.style.width = "0";
document.body.appendChild(_this.hiddenFileInput);
return _this.hiddenFileInput.addEventListener("change", function() {
var file, files, _i, _len;
files = _this.hiddenFileInput.files;
if (files.length) {
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
_this.addFile(file);
}
}
return setupHiddenFileInput();
});
};
})(this);
setupHiddenFileInput();
}
this.URL = (_ref = window.URL) != null ? _ref : window.webkitURL;
_ref1 = this.events;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
eventName = _ref1[_i];
this.on(eventName, this.options[eventName]);
}
this.on("uploadprogress", (function(_this) {
return function() {
return _this.updateTotalUploadProgress();
};
})(this));
this.on("removedfile", (function(_this) {
return function() {
return _this.updateTotalUploadProgress();
};
})(this));
this.on("canceled", (function(_this) {
return function(file) {
return _this.emit("complete", file);
};
})(this));
this.on("complete", (function(_this) {
return function(file) {
if (_this.getUploadingFiles().length === 0 && _this.getQueuedFiles().length === 0) {
return setTimeout((function() {
return _this.emit("queuecomplete");
}), 0);
}
};
})(this));
noPropagation = function(e) {
e.stopPropagation();
if (e.preventDefault) {
return e.preventDefault();
} else {
return e.returnValue = false;
}
};
this.listeners = [
{
element: this.element,
events: {
"dragstart": (function(_this) {
return function(e) {
return _this.emit("dragstart", e);
};
})(this),
"dragenter": (function(_this) {
return function(e) {
noPropagation(e);
return _this.emit("dragenter", e);
};
})(this),
"dragover": (function(_this) {
return function(e) {
var efct;
try {
efct = e.dataTransfer.effectAllowed;
} catch (_error) {}
e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy';
noPropagation(e);
return _this.emit("dragover", e);
};
})(this),
"dragleave": (function(_this) {
return function(e) {
return _this.emit("dragleave", e);
};
})(this),
"drop": (function(_this) {
return function(e) {
noPropagation(e);
return _this.drop(e);
};
})(this),
"dragend": (function(_this) {
return function(e) {
return _this.emit("dragend", e);
};
})(this)
}
}
];
this.clickableElements.forEach((function(_this) {
return function(clickableElement) {
return _this.listeners.push({
element: clickableElement,
events: {
"click": function(evt) {
if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) {
return _this.hiddenFileInput.click();
}
}
}
});
};
})(this));
this.enable();
return this.options.init.call(this);
};
Dropzone.prototype.destroy = function() {
var _ref;
this.disable();
this.removeAllFiles(true);
if ((_ref = this.hiddenFileInput) != null ? _ref.parentNode : void 0) {
this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput);
this.hiddenFileInput = null;
}
delete this.element.dropzone;
return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1);
};
Dropzone.prototype.updateTotalUploadProgress = function() {
var activeFiles, file, totalBytes, totalBytesSent, totalUploadProgress, _i, _len, _ref;
totalBytesSent = 0;
totalBytes = 0;
activeFiles = this.getActiveFiles();
if (activeFiles.length) {
_ref = this.getActiveFiles();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
totalBytesSent += file.upload.bytesSent;
totalBytes += file.upload.total;
}
totalUploadProgress = 100 * totalBytesSent / totalBytes;
} else {
totalUploadProgress = 100;
}
return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent);
};
Dropzone.prototype._getParamName = function(n) {
if (typeof this.options.paramName === "function") {
return this.options.paramName(n);
} else {
return "" + this.options.paramName + (this.options.uploadMultiple ? "[" + n + "]" : "");
}
};
Dropzone.prototype.getFallbackForm = function() {
var existingFallback, fields, fieldsString, form;
if (existingFallback = this.getExistingFallback()) {
return existingFallback;
}
fieldsString = "<div class=\"dz-fallback\">";
if (this.options.dictFallbackText) {
fieldsString += "<p>" + this.options.dictFallbackText + "</p>";
}
fieldsString += "<input type=\"file\" name=\"" + (this._getParamName(0)) + "\" " + (this.options.uploadMultiple ? 'multiple="multiple"' : void 0) + " /><input type=\"submit\" value=\"Upload!\"></div>";
fields = Dropzone.createElement(fieldsString);
if (this.element.tagName !== "FORM") {
form = Dropzone.createElement("<form action=\"" + this.options.url + "\" enctype=\"multipart/form-data\" method=\"" + this.options.method + "\"></form>");
form.appendChild(fields);
} else {
this.element.setAttribute("enctype", "multipart/form-data");
this.element.setAttribute("method", this.options.method);
}
return form != null ? form : fields;
};
Dropzone.prototype.getExistingFallback = function() {
var fallback, getFallback, tagName, _i, _len, _ref;
getFallback = function(elements) {
var el, _i, _len;
for (_i = 0, _len = elements.length; _i < _len; _i++) {
el = elements[_i];
if (/(^| )fallback($| )/.test(el.className)) {
return el;
}
}
};
_ref = ["div", "form"];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
tagName = _ref[_i];
if (fallback = getFallback(this.element.getElementsByTagName(tagName))) {
return fallback;
}
}
};
Dropzone.prototype.setupEventListeners = function() {
var elementListeners, event, listener, _i, _len, _ref, _results;
_ref = this.listeners;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
elementListeners = _ref[_i];
_results.push((function() {
var _ref1, _results1;
_ref1 = elementListeners.events;
_results1 = [];
for (event in _ref1) {
listener = _ref1[event];
_results1.push(elementListeners.element.addEventListener(event, listener, false));
}
return _results1;
})());
}
return _results;
};
Dropzone.prototype.removeEventListeners = function() {
var elementListeners, event, listener, _i, _len, _ref, _results;
_ref = this.listeners;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
elementListeners = _ref[_i];
_results.push((function() {
var _ref1, _results1;
_ref1 = elementListeners.events;
_results1 = [];
for (event in _ref1) {
listener = _ref1[event];
_results1.push(elementListeners.element.removeEventListener(event, listener, false));
}
return _results1;
})());
}
return _results;
};
Dropzone.prototype.disable = function() {
var file, _i, _len, _ref, _results;
this.clickableElements.forEach(function(element) {
return element.classList.remove("dz-clickable");
});
this.removeEventListeners();
_ref = this.files;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
_results.push(this.cancelUpload(file));
}
return _results;
};
Dropzone.prototype.enable = function() {
this.clickableElements.forEach(function(element) {
return element.classList.add("dz-clickable");
});
return this.setupEventListeners();
};
Dropzone.prototype.filesize = function(size) {
var string;
if (size >= 1024 * 1024 * 1024 * 1024 / 10) {
size = size / (1024 * 1024 * 1024 * 1024 / 10);
string = "TiB";
} else if (size >= 1024 * 1024 * 1024 / 10) {
size = size / (1024 * 1024 * 1024 / 10);
string = "GiB";
} else if (size >= 1024 * 1024 / 10) {
size = size / (1024 * 1024 / 10);
string = "MiB";
} else if (size >= 1024 / 10) {
size = size / (1024 / 10);
string = "KiB";
} else {
size = size * 10;
string = "b";
}
return "<strong>" + (Math.round(size) / 10) + "</strong> " + string;
};
Dropzone.prototype._updateMaxFilesReachedClass = function() {
if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) {
if (this.getAcceptedFiles().length === this.options.maxFiles) {
this.emit('maxfilesreached', this.files);
}
return this.element.classList.add("dz-max-files-reached");
} else {
return this.element.classList.remove("dz-max-files-reached");
}
};
Dropzone.prototype.drop = function(e) {
var files, items;
if (!e.dataTransfer) {
return;
}
this.emit("drop", e);
files = e.dataTransfer.files;
if (files.length) {
items = e.dataTransfer.items;
if (items && items.length && (items[0].webkitGetAsEntry != null)) {
this._addFilesFromItems(items);
} else {
this.handleFiles(files);
}
}
};
Dropzone.prototype.paste = function(e) {
var items, _ref;
if ((e != null ? (_ref = e.clipboardData) != null ? _ref.items : void 0 : void 0) == null) {
return;
}
this.emit("paste", e);
items = e.clipboardData.items;
if (items.length) {
return this._addFilesFromItems(items);
}
};
Dropzone.prototype.handleFiles = function(files) {
var file, _i, _len, _results;
_results = [];
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
_results.push(this.addFile(file));
}
return _results;
};
Dropzone.prototype._addFilesFromItems = function(items) {
var entry, item, _i, _len, _results;
_results = [];
for (_i = 0, _len = items.length; _i < _len; _i++) {
item = items[_i];
if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) {
if (entry.isFile) {
_results.push(this.addFile(item.getAsFile()));
} else if (entry.isDirectory) {
_results.push(this._addFilesFromDirectory(entry, entry.name));
} else {
_results.push(void 0);
}
} else if (item.getAsFile != null) {
if ((item.kind == null) || item.kind === "file") {
_results.push(this.addFile(item.getAsFile()));
} else {
_results.push(void 0);
}
} else {
_results.push(void 0);
}
}
return _results;
};
Dropzone.prototype._addFilesFromDirectory = function(directory, path) {
var dirReader, entriesReader;
dirReader = directory.createReader();
entriesReader = (function(_this) {
return function(entries) {
var entry, _i, _len;
for (_i = 0, _len = entries.length; _i < _len; _i++) {
entry = entries[_i];
if (entry.isFile) {
entry.file(function(file) {
if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') {
return;
}
file.fullPath = "" + path + "/" + file.name;
return _this.addFile(file);
});
} else if (entry.isDirectory) {
_this._addFilesFromDirectory(entry, "" + path + "/" + entry.name);
}
}
};
})(this);
return dirReader.readEntries(entriesReader, function(error) {
return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0;
});
};
Dropzone.prototype.accept = function(file, done) {
if (file.size > this.options.maxFilesize * 1024 * 1024) {
return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize));
} else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) {
return done(this.options.dictInvalidFileType);
} else if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) {
done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles));
return this.emit("maxfilesexceeded", file);
} else {
return this.options.accept.call(this, file, done);
}
};
Dropzone.prototype.addFile = function(file) {
file.upload = {
progress: 0,
total: file.size,
bytesSent: 0
};
this.files.push(file);
file.status = Dropzone.ADDED;
this.emit("addedfile", file);
this._enqueueThumbnail(file);
return this.accept(file, (function(_this) {
return function(error) {
if (error) {
file.accepted = false;
_this._errorProcessing([file], error);
} else {
file.accepted = true;
if (_this.options.autoQueue) {
_this.enqueueFile(file);
}
}
return _this._updateMaxFilesReachedClass();
};
})(this));
};
Dropzone.prototype.enqueueFiles = function(files) {
var file, _i, _len;
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
this.enqueueFile(file);
}
return null;
};
Dropzone.prototype.enqueueFile = function(file) {
if (file.status === Dropzone.ADDED && file.accepted === true) {
file.status = Dropzone.QUEUED;
if (this.options.autoProcessQueue) {
return setTimeout(((function(_this) {
return function() {
return _this.processQueue();
};
})(this)), 0);
}
} else {
throw new Error("This file can't be queued because it has already been processed or was rejected.");
}
};
Dropzone.prototype._thumbnailQueue = [];
Dropzone.prototype._processingThumbnail = false;
Dropzone.prototype._enqueueThumbnail = function(file) {
if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) {
this._thumbnailQueue.push(file);
return setTimeout(((function(_this) {
return function() {
return _this._processThumbnailQueue();
};
})(this)), 0);
}
};
Dropzone.prototype._processThumbnailQueue = function() {
if (this._processingThumbnail || this._thumbnailQueue.length === 0) {
return;
}
this._processingThumbnail = true;
return this.createThumbnail(this._thumbnailQueue.shift(), (function(_this) {
return function() {
_this._processingThumbnail = false;
return _this._processThumbnailQueue();
};
})(this));
};
Dropzone.prototype.removeFile = function(file) {
if (file.status === Dropzone.UPLOADING) {
this.cancelUpload(file);
}
this.files = without(this.files, file);
this.emit("removedfile", file);
if (this.files.length === 0) {
return this.emit("reset");
}
};
Dropzone.prototype.removeAllFiles = function(cancelIfNecessary) {
var file, _i, _len, _ref;
if (cancelIfNecessary == null) {
cancelIfNecessary = false;
}
_ref = this.files.slice();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) {
this.removeFile(file);
}
}
return null;
};
Dropzone.prototype.createThumbnail = function(file, callback) {
var fileReader;
fileReader = new FileReader;
fileReader.onload = (function(_this) {
return function() {
var img;
img = document.createElement("img");
img.onload = function() {
var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3;
file.width = img.width;
file.height = img.height;
resizeInfo = _this.options.resize.call(_this, file);
if (resizeInfo.trgWidth == null) {
resizeInfo.trgWidth = resizeInfo.optWidth;
}
if (resizeInfo.trgHeight == null) {
resizeInfo.trgHeight = resizeInfo.optHeight;
}
canvas = document.createElement("canvas");
ctx = canvas.getContext("2d");
canvas.width = resizeInfo.trgWidth;
canvas.height = resizeInfo.trgHeight;
drawImageIOSFix(ctx, img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight);
thumbnail = canvas.toDataURL("image/png");
_this.emit("thumbnail", file, thumbnail);
if (callback != null) {
return callback();
}
};
return img.src = fileReader.result;
};
})(this);
return fileReader.readAsDataURL(file);
};
Dropzone.prototype.processQueue = function() {
var i, parallelUploads, processingLength, queuedFiles;
parallelUploads = this.options.parallelUploads;
processingLength = this.getUploadingFiles().length;
i = processingLength;
if (processingLength >= parallelUploads) {
return;
}
queuedFiles = this.getQueuedFiles();
if (!(queuedFiles.length > 0)) {
return;
}
if (this.options.uploadMultiple) {
return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength));
} else {
while (i < parallelUploads) {
if (!queuedFiles.length) {
return;
}
this.processFile(queuedFiles.shift());
i++;
}
}
};
Dropzone.prototype.processFile = function(file) {
return this.processFiles([file]);
};
Dropzone.prototype.processFiles = function(files) {
var file, _i, _len;
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
file.processing = true;
file.status = Dropzone.UPLOADING;
this.emit("processing", file);
}
if (this.options.uploadMultiple) {
this.emit("processingmultiple", files);
}
return this.uploadFiles(files);
};
Dropzone.prototype._getFilesWithXhr = function(xhr) {
var file, files;
return files = (function() {
var _i, _len, _ref, _results;
_ref = this.files;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
if (file.xhr === xhr) {
_results.push(file);
}
}
return _results;
}).call(this);
};
Dropzone.prototype.cancelUpload = function(file) {
var groupedFile, groupedFiles, _i, _j, _len, _len1, _ref;
if (file.status === Dropzone.UPLOADING) {
groupedFiles = this._getFilesWithXhr(file.xhr);
for (_i = 0, _len = groupedFiles.length; _i < _len; _i++) {
groupedFile = groupedFiles[_i];
groupedFile.status = Dropzone.CANCELED;
}
file.xhr.abort();
for (_j = 0, _len1 = groupedFiles.length; _j < _len1; _j++) {
groupedFile = groupedFiles[_j];
this.emit("canceled", groupedFile);
}
if (this.options.uploadMultiple) {
this.emit("canceledmultiple", groupedFiles);
}
} else if ((_ref = file.status) === Dropzone.ADDED || _ref === Dropzone.QUEUED) {
file.status = Dropzone.CANCELED;
this.emit("canceled", file);
if (this.options.uploadMultiple) {
this.emit("canceledmultiple", [file]);
}
}
if (this.options.autoProcessQueue) {
return this.processQueue();
}
};
Dropzone.prototype.uploadFile = function(file) {
return this.uploadFiles([file]);
};
Dropzone.prototype.uploadFiles = function(files) {
var file, formData, handleError, headerName, headerValue, headers, i, input, inputName, inputType, key, option, progressObj, response, updateProgress, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _m, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
xhr = new XMLHttpRequest();
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
file.xhr = xhr;
}
xhr.open(this.options.method, this.options.url, true);
xhr.withCredentials = !!this.options.withCredentials;
response = null;
handleError = (function(_this) {
return function() {
var _j, _len1, _results;
_results = [];
for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
file = files[_j];
_results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr));
}
return _results;
};
})(this);
updateProgress = (function(_this) {
return function(e) {
var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results;
if (e != null) {
progress = 100 * e.loaded / e.total;
for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
file = files[_j];
file.upload = {
progress: progress,
total: e.total,
bytesSent: e.loaded
};
}
} else {
allFilesFinished = true;
progress = 100;
for (_k = 0, _len2 = files.length; _k < _len2; _k++) {
file = files[_k];
if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) {
allFilesFinished = false;
}
file.upload.progress = progress;
file.upload.bytesSent = file.upload.total;
}
if (allFilesFinished) {
return;
}
}
_results = [];
for (_l = 0, _len3 = files.length; _l < _len3; _l++) {
file = files[_l];
_results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent));
}
return _results;
};
})(this);
xhr.onload = (function(_this) {
return function(e) {
var _ref;
if (files[0].status === Dropzone.CANCELED) {
return;
}
if (xhr.readyState !== 4) {
return;
}
response = xhr.responseText;
if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) {
try {
response = JSON.parse(response);
} catch (_error) {
e = _error;
response = "Invalid JSON response from server.";
}
}
updateProgress();
if (!((200 <= (_ref = xhr.status) && _ref < 300))) {
return handleError();
} else {
return _this._finished(files, response, e);
}
};
})(this);
xhr.onerror = (function(_this) {
return function() {
if (files[0].status === Dropzone.CANCELED) {
return;
}
return handleError();
};
})(this);
progressObj = (_ref = xhr.upload) != null ? _ref : xhr;
progressObj.onprogress = updateProgress;
headers = {
"Accept": "application/json",
"Cache-Control": "no-cache",
"X-Requested-With": "XMLHttpRequest"
};
if (this.options.headers) {
extend(headers, this.options.headers);
}
for (headerName in headers) {
headerValue = headers[headerName];
xhr.setRequestHeader(headerName, headerValue);
}
formData = new FormData();
if (this.options.params) {
_ref1 = this.options.params;
for (key in _ref1) {
value = _ref1[key];
formData.append(key, value);
}
}
for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
file = files[_j];
this.emit("sending", file, xhr, formData);
}
if (this.options.uploadMultiple) {
this.emit("sendingmultiple", files, xhr, formData);
}
if (this.element.tagName === "FORM") {
_ref2 = this.element.querySelectorAll("input, textarea, select, button");
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
input = _ref2[_k];
inputName = input.getAttribute("name");
inputType = input.getAttribute("type");
if (input.tagName === "SELECT" && input.hasAttribute("multiple")) {
_ref3 = input.options;
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
option = _ref3[_l];
if (option.selected) {
formData.append(inputName, option.value);
}
}
} else if (!inputType || ((_ref4 = inputType.toLowerCase()) !== "checkbox" && _ref4 !== "radio") || input.checked) {
formData.append(inputName, input.value);
}
}
}
for (i = _m = 0, _ref5 = files.length - 1; 0 <= _ref5 ? _m <= _ref5 : _m >= _ref5; i = 0 <= _ref5 ? ++_m : --_m) {
formData.append(this._getParamName(i), files[i], files[i].name);
}
return xhr.send(formData);
};
Dropzone.prototype._finished = function(files, responseText, e) {
var file, _i, _len;
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
file.status = Dropzone.SUCCESS;
this.emit("success", file, responseText, e);
this.emit("complete", file);
}
if (this.options.uploadMultiple) {
this.emit("successmultiple", files, responseText, e);
this.emit("completemultiple", files);
}
if (this.options.autoProcessQueue) {
return this.processQueue();
}
};
Dropzone.prototype._errorProcessing = function(files, message, xhr) {
var file, _i, _len;
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
file.status = Dropzone.ERROR;
this.emit("error", file, message, xhr);
this.emit("complete", file);
}
if (this.options.uploadMultiple) {
this.emit("errormultiple", files, message, xhr);
this.emit("completemultiple", files);
}
if (this.options.autoProcessQueue) {
return this.processQueue();
}
};
return Dropzone;
})(Em);
Dropzone.version = "3.10.2";
Dropzone.options = {};
Dropzone.optionsForElement = function(element) {
if (element.getAttribute("id")) {
return Dropzone.options[camelize(element.getAttribute("id"))];
} else {
return void 0;
}
};
Dropzone.instances = [];
Dropzone.forElement = function(element) {
if (typeof element === "string") {
element = document.querySelector(element);
}
if ((element != null ? element.dropzone : void 0) == null) {
throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone.");
}
return element.dropzone;
};
Dropzone.autoDiscover = true;
Dropzone.discover = function() {
var checkElements, dropzone, dropzones, _i, _len, _results;
if (document.querySelectorAll) {
dropzones = document.querySelectorAll(".dropzone");
} else {
dropzones = [];
checkElements = function(elements) {
var el, _i, _len, _results;
_results = [];
for (_i = 0, _len = elements.length; _i < _len; _i++) {
el = elements[_i];
if (/(^| )dropzone($| )/.test(el.className)) {
_results.push(dropzones.push(el));
} else {
_results.push(void 0);
}
}
return _results;
};
checkElements(document.getElementsByTagName("div"));
checkElements(document.getElementsByTagName("form"));
}
_results = [];
for (_i = 0, _len = dropzones.length; _i < _len; _i++) {
dropzone = dropzones[_i];
if (Dropzone.optionsForElement(dropzone) !== false) {
_results.push(new Dropzone(dropzone));
} else {
_results.push(void 0);
}
}
return _results;
};
Dropzone.blacklistedBrowsers = [/opera.*Macintosh.*version\/12/i];
Dropzone.isBrowserSupported = function() {
var capableBrowser, regex, _i, _len, _ref;
capableBrowser = true;
if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) {
if (!("classList" in document.createElement("a"))) {
capableBrowser = false;
} else {
_ref = Dropzone.blacklistedBrowsers;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
regex = _ref[_i];
if (regex.test(navigator.userAgent)) {
capableBrowser = false;
continue;
}
}
}
} else {
capableBrowser = false;
}
return capableBrowser;
};
without = function(list, rejectedItem) {
var item, _i, _len, _results;
_results = [];
for (_i = 0, _len = list.length; _i < _len; _i++) {
item = list[_i];
if (item !== rejectedItem) {
_results.push(item);
}
}
return _results;
};
camelize = function(str) {
return str.replace(/[\-_](\w)/g, function(match) {
return match.charAt(1).toUpperCase();
});
};
Dropzone.createElement = function(string) {
var div;
div = document.createElement("div");
div.innerHTML = string;
return div.childNodes[0];
};
Dropzone.elementInside = function(element, container) {
if (element === container) {
return true;
}
while (element = element.parentNode) {
if (element === container) {
return true;
}
}
return false;
};
Dropzone.getElement = function(el, name) {
var element;
if (typeof el === "string") {
element = document.querySelector(el);
} else if (el.nodeType != null) {
element = el;
}
if (element == null) {
throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector or a plain HTML element.");
}
return element;
};
Dropzone.getElements = function(els, name) {
var e, el, elements, _i, _j, _len, _len1, _ref;
if (els instanceof Array) {
elements = [];
try {
for (_i = 0, _len = els.length; _i < _len; _i++) {
el = els[_i];
elements.push(this.getElement(el, name));
}
} catch (_error) {
e = _error;
elements = null;
}
} else if (typeof els === "string") {
elements = [];
_ref = document.querySelectorAll(els);
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
el = _ref[_j];
elements.push(el);
}
} else if (els.nodeType != null) {
elements = [els];
}
if (!((elements != null) && elements.length)) {
throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector, a plain HTML element or a list of those.");
}
return elements;
};
Dropzone.confirm = function(question, accepted, rejected) {
if (window.confirm(question)) {
return accepted();
} else if (rejected != null) {
return rejected();
}
};
Dropzone.isValidFile = function(file, acceptedFiles) {
var baseMimeType, mimeType, validType, _i, _len;
if (!acceptedFiles) {
return true;
}
acceptedFiles = acceptedFiles.split(",");
mimeType = file.type;
baseMimeType = mimeType.replace(/\/.*$/, "");
for (_i = 0, _len = acceptedFiles.length; _i < _len; _i++) {
validType = acceptedFiles[_i];
validType = validType.trim();
if (validType.charAt(0) === ".") {
if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) {
return true;
}
} else if (/\/\*$/.test(validType)) {
if (baseMimeType === validType.replace(/\/.*$/, "")) {
return true;
}
} else {
if (mimeType === validType) {
return true;
}
}
}
return false;
};
if (typeof jQuery !== "undefined" && jQuery !== null) {
jQuery.fn.dropzone = function(options) {
return this.each(function() {
return new Dropzone(this, options);
});
};
}
if (typeof module !== "undefined" && module !== null) {
module.exports = Dropzone;
} else {
window.Dropzone = Dropzone;
}
Dropzone.ADDED = "added";
Dropzone.QUEUED = "queued";
Dropzone.ACCEPTED = Dropzone.QUEUED;
Dropzone.UPLOADING = "uploading";
Dropzone.PROCESSING = Dropzone.UPLOADING;
Dropzone.CANCELED = "canceled";
Dropzone.ERROR = "error";
Dropzone.SUCCESS = "success";
/*
Bugfix for iOS 6 and 7
Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios
based on the work of https://github.com/stomita/ios-imagefile-megapixel
*/
detectVerticalSquash = function(img) {
var alpha, canvas, ctx, data, ey, ih, iw, py, ratio, sy;
iw = img.naturalWidth;
ih = img.naturalHeight;
canvas = document.createElement("canvas");
canvas.width = 1;
canvas.height = ih;
ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
data = ctx.getImageData(0, 0, 1, ih).data;
sy = 0;
ey = ih;
py = ih;
while (py > sy) {
alpha = data[(py - 1) * 4 + 3];
if (alpha === 0) {
ey = py;
} else {
sy = py;
}
py = (ey + sy) >> 1;
}
ratio = py / ih;
if (ratio === 0) {
return 1;
} else {
return ratio;
}
};
drawImageIOSFix = function(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) {
var vertSquashRatio;
vertSquashRatio = detectVerticalSquash(img);
return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio);
};
/*
* contentloaded.js
*
* Author: Diego Perini (diego.perini at gmail.com)
* Summary: cross-browser wrapper for DOMContentLoaded
* Updated: 20101020
* License: MIT
* Version: 1.2
*
* URL:
* http://javascript.nwbox.com/ContentLoaded/
* http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
*/
contentLoaded = function(win, fn) {
var add, doc, done, init, poll, pre, rem, root, top;
done = false;
top = true;
doc = win.document;
root = doc.documentElement;
add = (doc.addEventListener ? "addEventListener" : "attachEvent");
rem = (doc.addEventListener ? "removeEventListener" : "detachEvent");
pre = (doc.addEventListener ? "" : "on");
init = function(e) {
if (e.type === "readystatechange" && doc.readyState !== "complete") {
return;
}
(e.type === "load" ? win : doc)[rem](pre + e.type, init, false);
if (!done && (done = true)) {
return fn.call(win, e.type || e);
}
};
poll = function() {
var e;
try {
root.doScroll("left");
} catch (_error) {
e = _error;
setTimeout(poll, 50);
return;
}
return init("poll");
};
if (doc.readyState !== "complete") {
if (doc.createEventObject && root.doScroll) {
try {
top = !win.frameElement;
} catch (_error) {}
if (top) {
poll();
}
}
doc[add](pre + "DOMContentLoaded", init, false);
doc[add](pre + "readystatechange", init, false);
return win[add](pre + "load", init, false);
}
};
Dropzone._autoDiscoverFunction = function() {
if (Dropzone.autoDiscover) {
return Dropzone.discover();
}
};
contentLoaded(window, Dropzone._autoDiscoverFunction);
}).call(this);
});
if (typeof exports == "object") {
module.exports = require("dropzone");
} else if (typeof define == "function" && define.amd) {
define([], function(){ return require("dropzone"); });
} else {
this["Dropzone"] = require("dropzone");
}
})()
$( document ).ready(function() {
Dropzone.autoDiscover = false;
var myDropzone = new Dropzone(".dropzone", {
url: Asgard.dropzonePostUrl,
autoProcessQueue: true,
maxFilesize: maxFilesize,
acceptedFiles : acceptedFiles
});
myDropzone.on("queuecomplete", function(file, http) {
window.setTimeout(function(){
location.reload();
}, 1000);
});
myDropzone.on("sending", function(file, xhr, fromData) {
xhr.setRequestHeader("Authorization", AuthorizationHeaderValue);
if ($('.alert-danger').length > 0) {
$('.alert-danger').remove();
}
});
myDropzone.on("error", function(file, errorMessage) {
var html = '<div class="alert alert-danger" role="alert">' + errorMessage + '</div>';
$('.col-md-12').first().prepend(html);
setTimeout(function() {
myDropzone.removeFile(file);
}, 2000);
});
});
$( document ).ready(function() {
var $fileCount = $('.jsFileCount');
var sortableWrapper = $(".jsThumbnailImageWrapper");
var sortableSelection = sortableWrapper.not(".jsSingleThumbnailWrapper");
// This comes from new-file-link-single
if (typeof window.openMediaWindowSingle === 'undefined') {
window.mediaZone = '';
window.openMediaWindowSingle = function (event, zone) {
window.single = true;
window.old = false;
window.mediaZone = zone;
window.zoneWrapper = $(event.currentTarget).siblings('.jsThumbnailImageWrapper');
window.open(Asgard.mediaGridSelectUrl, '_blank', 'menubar=no,status=no,toolbar=no,scrollbars=yes,height=500,width=1000');
};
}
if (typeof window.includeMediaSingle === 'undefined') {
window.includeMediaSingle = function (mediaId, filePath, mediaType, mimetype) {
var mediaPlaceholder;
if (mediaType === 'image') {
mediaPlaceholder = '<img src="' + filePath + '" alt=""/>';
} else if (mediaType == 'video') {
mediaPlaceholder = '<video src="' + filePath + '" controls width="320"></video>';
} else if (mediaType == 'audio') {
mediaPlaceholder = '<audio controls><source src="' + filePath + '" type="' + mimetype + '"></audio>'
} else {
mediaPlaceholder = '<i class="fa fa-file" style="font-size: 50px;"></i>';
}
var html = '<figure data-id="'+ mediaId +'">' + mediaPlaceholder +
'<a class="jsRemoveSimpleLink" href="#" data-id="' + mediaId + '">' +
'<i class="fa fa-times-circle removeIcon"></i></a>' +
'</figure>';
window.zoneWrapper.append(html).fadeIn('slow', function() {
toggleButton($(this));
});
window.zoneWrapper.children('input').val(mediaId);
};
}
// This comes from new-file-link-multiple
if (typeof window.openMediaWindowMultiple === 'undefined') {
window.mediaZone = '';
window.openMediaWindowMultiple = function (event, zone) {
window.single = false;
window.old = false;
window.mediaZone = zone;
window.zoneWrapper = $(event.currentTarget).siblings('.jsThumbnailImageWrapper');
window.open(Asgard.mediaGridSelectUrl, '_blank', 'menubar=no,status=no,toolbar=no,scrollbars=yes,height=500,width=1000');
};
}
if (typeof window.includeMediaMultiple === 'undefined') {
window.includeMediaMultiple = function (mediaId, filePath, mediaType, mimetype) {
var mediaPlaceholder;
if (mediaType === 'image') {
mediaPlaceholder = '<img src="' + filePath + '" alt=""/>';
} else if (mediaType == 'video') {
mediaPlaceholder = '<video src="' + filePath + '" controls width="320"></video>';
} else if (mediaType == 'audio') {
mediaPlaceholder = '<audio controls><source src="' + filePath + '" type="' + mimetype + '"></audio>'
} else {
mediaPlaceholder = '<i class="fa fa-file" style="font-size: 50px;"></i>';
}
var html = '<figure data-id="' + mediaId + '">' + mediaPlaceholder +
'<a class="jsRemoveLink" href="#" data-id="' + mediaId + '">' +
'<i class="fa fa-times-circle removeIcon"></i>' +
'</a>' +
'<input type="hidden" name="medias_multi[' + window.mediaZone + '][files][]" value="' + mediaId + '">' +
'</figure>';
window.zoneWrapper.append(html).fadeIn();
window.zoneWrapper.trigger('sortupdate');
if ($fileCount.length > 0) {
var count = parseInt($fileCount.text());
$fileCount.text(count + 1);
}
};
}
// This comes from new-file-link-multiple
sortableWrapper.on('click', '.jsRemoveLink', function (e) {
e.preventDefault();
var pictureWrapper = $(this).parent();
var pictureSortable = pictureWrapper.parent();
pictureWrapper.fadeOut().remove();
pictureSortable.trigger('sortupdate');
if ($fileCount.length > 0) {
var count = parseInt($fileCount.text());
$fileCount.text(count - 1);
}
});
// This comes from new-file-link-multiple
sortableSelection.sortable({
items: "figure",
placeholder: 'ui-state-highlight',
cursor:'move',
helper: 'clone',
containment: 'parent',
forcePlaceholderSize: false,
forceHelperSize: true
});
sortableSelection.on('sortupdate', function(e, ui) {
var dataSortable = $(this).sortable('toArray', {attribute: 'data-id'});
$(this).find($('.orders')).val(dataSortable);
});
// This comes from new-file-link-single
sortableWrapper.off('click', '.jsRemoveSimpleLink');
sortableWrapper.on('click', '.jsRemoveSimpleLink', function (e) {
e.preventDefault();
$(e.delegateTarget).fadeOut('slow', function() {
toggleButton($(this));
}).children('figure').remove();
$(e.delegateTarget).children('input').val('');
});
function toggleButton(el) {
var browseButton = el.parent().find('.btn-browse');
browseButton.toggle();
}
});
<?php
namespace Modules\Media\Blade\Facades;
use Illuminate\Support\Facades\Facade;
class MediaMultipleDirective extends Facade
{
protected static function getFacadeAccessor()
{
return 'media.multiple.directive';
}
}
<?php
namespace Modules\Media\Blade\Facades;
use Illuminate\Support\Facades\Facade;
class MediaSingleDirective extends Facade
{
protected static function getFacadeAccessor()
{
return 'media.single.directive';
}
}
<?php
namespace Modules\Media\Blade;
class MediaMultipleDirective
{
/**
* @var string
*/
private $zone;
/**
* @var
*/
private $entity;
/**
* @var string|null
*/
private $view;
public function show($arguments)
{
$this->extractArguments($arguments);
$view = $this->view ?: 'media::admin.fields.new-file-link-multiple';
$zone = $this->zone;
if ($this->entity !== null) {
$media = $this->entity->filesByZone($this->zone)->get();
}
return view($view, compact('media', 'zone'));
}
/**
* Extract the possible arguments as class properties
* @param array $arguments
*/
private function extractArguments(array $arguments)
{
$this->zone = array_get($arguments, 0);
$this->entity = array_get($arguments, 1);
$this->view = array_get($arguments, 2);
}
}
<?php
namespace Modules\Media\Blade;
class MediaSingleDirective
{
/**
* @var string
*/
private $zone;
/**
* @var
*/
private $entity;
/**
* @var string|null
*/
private $view;
public function show($arguments)
{
$this->extractArguments($arguments);
$view = $this->view ?: 'media::admin.fields.new-file-link-single';
$zone = $this->zone;
if ($this->entity !== null) {
$media = $this->entity->filesByZone($this->zone)->first();
}
return view($view, compact('media', 'zone'));
}
/**
* Extract the possible arguments as class properties
* @param array $arguments
*/
private function extractArguments(array $arguments)
{
$this->zone = array_get($arguments, 0);
$this->entity = array_get($arguments, 1);
$this->view = array_get($arguments, 2);
}
}
<?php
namespace Modules\Media\Composers\Backend;
use Modules\Core\Foundation\Asset\Manager\AssetManager;
use Modules\Core\Foundation\Asset\Pipeline\AssetPipeline;
use Modules\Core\Foundation\Asset\Types\AssetTypeFactory;
class PartialAssetComposer
{
/**
* @var AssetManager
*/
private $assetManager;
/**
* @var AssetPipeline
*/
private $assetPipeline;
/**
* @var AssetTypeFactory
*/
private $assetFactory;
public function __construct()
{
$this->assetManager = app(AssetManager::class);
$this->assetPipeline = app(AssetPipeline::class);
$this->assetFactory = app(AssetTypeFactory::class);
}
public function compose()
{
$this->addAssets();
$this->requireAssets();
}
/**
* Add the assets from the config file on the asset manager
*/
private function addAssets()
{
foreach (config('asgard.media.assets.media-partial-assets', []) as $assetName => $path) {
$path = $this->assetFactory->make($path)->url();
$this->assetManager->addAsset($assetName, $path);
}
}
/**
* Require assets from asset manager
*/
private function requireAssets()
{
$css = config('asgard.media.assets.media-partial-required-assets.css');
$js = config('asgard.media.assets.media-partial-required-assets.js');
if (!empty($css)) {
$this->assetPipeline->requireCss($css);
}
if (!empty($js)) {
$this->assetPipeline->requireJs($js);
}
}
}
<?php
return [
/*
|--------------------------------------------------------------------------
| Define which assets will be available through the asset manager
|--------------------------------------------------------------------------
| These assets are registered on the asset manager
*/
'media-partial-assets' => [
'jquery-ui.js' => ['module' => 'dashboard:vendor/jquery-ui/jquery-ui.min.js'],
'media-partial.js' => ['module' => 'media:js/media-partial.js'],
],
/*
|--------------------------------------------------------------------------
| Define which default assets will always be included in your pages
| through the asset pipeline
|--------------------------------------------------------------------------
*/
'media-partial-required-assets' => [
'js' => [
'jquery-ui.js',
'media-partial.js',
],
],
];
<?php
return [
/*
|--------------------------------------------------------------------------
| Choose which filesystem you wish to use to store the media
|--------------------------------------------------------------------------
| Choose one or more of the filesystems you configured
| in app/config/filesystems.php
| Supported: "local", "s3"
*/
'filesystem' => 'local',
/*
|--------------------------------------------------------------------------
| The path where the media files will be uploaded
|--------------------------------------------------------------------------
| Note: Trailing slash is required
*/
'files-path' => '/assets/media/',
/*
|--------------------------------------------------------------------------
| Specify all the allowed file extensions a user can upload on the server
|--------------------------------------------------------------------------
*/
'allowed-types' => '.jpg,.png',
/*
|--------------------------------------------------------------------------
| Determine the max file size upload rate
| Defined in MB
|--------------------------------------------------------------------------
*/
'max-file-size' => '5',
/*
|--------------------------------------------------------------------------
| Determine the max total media folder size
|--------------------------------------------------------------------------
| Expressed in bytes
*/
'max-total-size' => 1000000000,
/*
|--------------------------------------------------------------------------
| Custom Sidebar Class
|--------------------------------------------------------------------------
| If you want to customise the admin sidebar ordering or grouping
| You can define your own sidebar class for this module.
| No custom sidebar: null
*/
'custom-sidebar' => null,
];
<?php
return [
'media.medias' => [
'index' => 'media::media.list resource',
'create' => 'media::media.create resource',
'edit' => 'media::media.edit resource',
'destroy' => 'media::media.destroy resource',
],
];
<?php
namespace Modules\Media\Console;
use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Modules\Media\Jobs\RebuildThumbnails;
use Modules\Media\Repositories\FileRepository;
class RefreshThumbnailCommand extends Command
{
use DispatchesJobs;
protected $name = 'asgard:media:refresh';
protected $description = 'Create and or refresh the thumbnails';
/**
* @var FileRepository
*/
private $file;
public function __construct(FileRepository $file)
{
parent::__construct();
$this->file = $file;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function fire()
{
$this->line('Preparing to regenerate all thumbnails...');
$this->dispatch(new RebuildThumbnails($this->file->all()->pluck('path')));
$this->info('All thumbnails refreshed');
}
}
<?php
namespace Modules\Media\Contracts;
interface DeletingMedia
{
/**
* Get the entity ID
* @return int
*/
public function getEntityId();
/**
* Get the class name the imageables
* @return string
*/
public function getClassName();
}
<?php
namespace Modules\Media\Contracts;
interface StoringMedia
{
/**
* Return the entity
* @return \Illuminate\Database\Eloquent\Model
*/
public function getEntity();
/**
* Return the ALL data sent
* @return array
*/
public function getSubmissionData();
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateFilesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('media__files', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('filename');
$table->string('path');
$table->string('extension');
$table->string('mimetype');
$table->string('filesize');
$table->integer('folder_id')->unsigned();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('media__files');
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateFileTranslationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create(
'media__file_translations',
function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('file_id')->unsigned();
$table->string('locale')->index();
$table->string('description');
$table->string('alt_attribute');
$table->string('keywords');
$table->unique(['file_id', 'locale']);
$table->foreign('file_id')->references('id')->on('media__files')->onDelete('cascade');
}
);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('media__file_translations');
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateImageLinksTable extends Migration
{
/**
* Run the migrations.
* @return void
*/
public function up()
{
Schema::create('media__imageables', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('file_id');
$table->integer('imageable_id');
$table->string('imageable_type');
$table->string('zone');
$table->timestamps();
});
}
/**
* Reverse the migrations.
* @return void
*/
public function down()
{
Schema::drop('media__imageables');
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddSortable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('media__imageables', function (Blueprint $table) {
$table->integer('order')->nullable()->after('zone');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('media__imageables', function (Blueprint $table) {
$table->dropColumn('order');
});
}
}
<?php
namespace Modules\Media\Database\Seeders;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Seeder;
class MediaDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
}
}
<?php
namespace Modules\Media\Entities;
use Dimsav\Translatable\Translatable;
use Illuminate\Database\Eloquent\Model;
use Modules\Core\Traits\NamespacedEntity;
use Modules\Media\Helpers\FileHelper;
use Modules\Media\Image\Facade\Imagy;
use Modules\Media\ValueObjects\MediaPath;
use Modules\Tag\Contracts\TaggableInterface;
use Modules\Tag\Traits\TaggableTrait;
/**
* Class File
* @package Modules\Media\Entities
* @property \Modules\Media\ValueObjects\MediaPath path
*/
class File extends Model implements TaggableInterface
{
use Translatable, NamespacedEntity, TaggableTrait;
/**
* All the different images types where thumbnails should be created
* @var array
*/
private $imageExtensions = ['jpg', 'png', 'jpeg', 'gif'];
protected $table = 'media__files';
public $translatedAttributes = ['description', 'alt_attribute', 'keywords'];
protected $fillable = [
'description',
'alt_attribute',
'keywords',
'filename',
'path',
'extension',
'mimetype',
'width',
'height',
'filesize',
'folder_id',
];
protected $appends = ['path_string', 'media_type'];
protected static $entityNamespace = 'asgardcms/media';
public function getPathAttribute($value)
{
return new MediaPath($value);
}
public function getPathStringAttribute()
{
return (string) $this->path;
}
public function getMediaTypeAttribute()
{
return FileHelper::getTypeByMimetype($this->mimetype);
}
public function isImage()
{
return in_array(pathinfo($this->path, PATHINFO_EXTENSION), $this->imageExtensions);
}
public function getThumbnail($type)
{
if ($this->isImage() && $this->getKey()) {
return Imagy::getThumbnail($this->path, $type);
}
return false;
}
}
<?php
namespace Modules\Media\Entities;
use Illuminate\Database\Eloquent\Model;
class FileTranslation extends Model
{
public $timestamps = false;
protected $fillable = ['description', 'alt_attribute', 'keywords'];
protected $table = 'media__file_translations';
}
<?php
namespace Modules\Media\Events;
use Modules\Media\Entities\File;
class FileWasLinked
{
/**
* @var File
*/
public $file;
/**
* The entity that was linked to a file
* @var object
*/
public $entity;
/**
* @param File $file
* @param object $entity
*/
public function __construct(File $file, $entity)
{
$this->file = $file;
$this->entity = $entity;
}
}
<?php
namespace Modules\Media\Events;
class FileWasUnlinked
{
/**
* The imageable id
* @var int
*/
public $imageableId;
/**
* @param int $imageableId
*/
public function __construct($imageableId)
{
$this->imageableId = $imageableId;
}
}
<?php
namespace Modules\Media\Events;
use Modules\Media\Entities\File;
class FileWasUploaded
{
/**
* @var File
*/
public $file;
/**
* @param File $file
*/
public function __construct(File $file)
{
$this->file = $file;
}
}
<?php
namespace Modules\Media\Events\Handlers;
use Modules\Media\Contracts\StoringMedia;
class HandleMediaStorage
{
public function handle($event = null)
{
if ($event instanceof StoringMedia) {
$this->handleMultiMedia($event);
$this->handleSingleMedia($event);
}
}
/**
* Handle the request for the multi media partial
* @param StoringMedia $event
*/
private function handleMultiMedia(StoringMedia $event)
{
$entity = $event->getEntity();
$postMedias = array_get($event->getSubmissionData(), 'medias_multi', []);
foreach ($postMedias as $zone => $attributes) {
$syncList = [];
$orders = $this->getOrdersFrom($attributes);
foreach (array_get($attributes, 'files', []) as $fileId) {
$syncList[$fileId] = [];
$syncList[$fileId]['imageable_type'] = get_class($entity);
$syncList[$fileId]['zone'] = $zone;
$syncList[$fileId]['order'] = (int) array_search($fileId, $orders);
}
$entity->filesByZone($zone)->sync($syncList);
}
}
/**
* Handle the request to parse single media partials
* @param StoringMedia $event
*/
private function handleSingleMedia(StoringMedia $event)
{
$entity = $event->getEntity();
$postMedia = array_get($event->getSubmissionData(), 'medias_single', []);
foreach ($postMedia as $zone => $fileId) {
if (!empty($fileId)) {
$entity->filesByZone($zone)->sync([$fileId => ['imageable_type' => get_class($entity), 'zone' => $zone, 'order' => null]]);
} else {
$entity->filesByZone($zone)->sync([]);
}
}
}
/**
* Parse the orders input and return an array of file ids, in order
* @param array $attributes
* @return array
*/
private function getOrdersFrom(array $attributes)
{
$orderString = array_get($attributes, 'orders');
if ($orderString === null) {
return [];
}
$orders = explode(',', $orderString);
return array_filter($orders);
}
}
<?php
namespace Modules\Media\Events\Handlers;
use Illuminate\Support\Facades\DB;
use Modules\Media\Contracts\DeletingMedia;
class RemovePolymorphicLink
{
public function handle($event = null)
{
if ($event instanceof DeletingMedia) {
DB::table('media__imageables')->where('imageable_id', $event->getEntityId())
->where('imageable_type', $event->getClassName())->delete();
}
}
}
<?php
namespace Modules\Media\Helpers;
use Illuminate\Support\Str;
class FileHelper
{
/**
* Get first token of string before delimiter
* @param $mimetype
* @return string
*/
public static function getTypeByMimetype($mimetype)
{
return strtok($mimetype, '/');
}
/**
* Get Font Awesome icon for various files
* @param string $mediaType
* @return string
*/
public static function getFaIcon($mediaType)
{
switch ($mediaType) {
case 'video':
return 'fa-file-video-o';
case 'audio':
return 'fa-file-audio-o';
default:
return 'fa-file';
}
}
public static function slug($name)
{
$extension = self::getExtension($name);
$name = str_replace($extension, '', $name);
$name = Str::slug($name);
return $name . strtolower($extension);
}
/**
* Get the extension from the given name
* @param $name
* @return string
*/
private static function getExtension($name)
{
return substr($name, strrpos($name, '.'));
}
}
<?php
namespace Modules\Media\Http\Controllers\Admin;
use Illuminate\Contracts\Config\Repository;
use Modules\Core\Http\Controllers\Admin\AdminBaseController;
use Modules\Media\Entities\File;
use Modules\Media\Http\Requests\UpdateMediaRequest;
use Modules\Media\Image\Imagy;
use Modules\Media\Image\ThumbnailManager;
use Modules\Media\Repositories\FileRepository;
class MediaController extends AdminBaseController
{
/**
* @var FileRepository
*/
private $file;
/**
* @var Repository
*/
private $config;
/**
* @var Imagy
*/
private $imagy;
/**
* @var ThumbnailManager
*/
private $thumbnailsManager;
public function __construct(FileRepository $file, Repository $config, Imagy $imagy, ThumbnailManager $thumbnailsManager)
{
parent::__construct();
$this->file = $file;
$this->config = $config;
$this->imagy = $imagy;
$this->thumbnailsManager = $thumbnailsManager;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
$files = $this->file->all();
$config = $this->config->get('asgard.media.config');
return view('media::admin.index', compact('files', 'config'));
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
return view('media.create');
}
/**
* Show the form for editing the specified resource.
*
* @param File $file
* @return Response
*/
public function edit(File $file)
{
$thumbnails = $this->thumbnailsManager->all();
return view('media::admin.edit', compact('file', 'thumbnails'));
}
/**
* Update the specified resource in storage.
*
* @param File $file
* @param UpdateMediaRequest $request
* @return Response
*/
public function update(File $file, UpdateMediaRequest $request)
{
$this->file->update($file, $request->all());
return redirect()->route('admin.media.media.index')
->withSuccess(trans('media::messages.file updated'));
}
/**
* Remove the specified resource from storage.
*
* @param File $file
* @internal param int $id
* @return Response
*/
public function destroy(File $file)
{
$this->imagy->deleteAllFor($file);
$this->file->destroy($file);
return redirect()->route('admin.media.media.index')
->withSuccess(trans('media::messages.file deleted'));
}
}
<?php
namespace Modules\Media\Http\Controllers\Admin;
use Modules\Core\Http\Controllers\Admin\AdminBaseController;
use Modules\Media\Image\ThumbnailManager;
use Modules\Media\Repositories\FileRepository;
class MediaGridController extends AdminBaseController
{
/**
* @var FileRepository
*/
private $file;
/**
* @var ThumbnailManager
*/
private $thumbnailsManager;
public function __construct(FileRepository $file, ThumbnailManager $thumbnailsManager)
{
parent::__construct();
$this->file = $file;
$this->thumbnailsManager = $thumbnailsManager;
}
/**
* A grid view for the upload button
* @return \Illuminate\View\View
*/
public function index()
{
$files = $this->file->all();
$thumbnails = $this->thumbnailsManager->all();
return view('media::admin.grid.general', compact('files', 'thumbnails'));
}
/**
* A grid view of uploaded files used for the wysiwyg editor
* @return \Illuminate\View\View
*/
public function ckIndex()
{
$files = $this->file->all();
$thumbnails = $this->thumbnailsManager->all();
return view('media::admin.grid.ckeditor', compact('files', 'thumbnails'));
}
}
<?php
namespace Modules\Media\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Response;
use Modules\Media\Entities\File;
use Modules\Media\Events\FileWasLinked;
use Modules\Media\Events\FileWasUnlinked;
use Modules\Media\Events\FileWasUploaded;
use Modules\Media\Helpers\FileHelper;
use Modules\Media\Http\Requests\UploadMediaRequest;
use Modules\Media\Image\Imagy;
use Modules\Media\Repositories\FileRepository;
use Modules\Media\Services\FileService;
class MediaController extends Controller
{
/**
* @var FileService
*/
private $fileService;
/**
* @var FileRepository
*/
private $file;
/**
* @var Imagy
*/
private $imagy;
public function __construct(FileService $fileService, FileRepository $file, Imagy $imagy)
{
$this->fileService = $fileService;
$this->file = $file;
$this->imagy = $imagy;
}
public function all()
{
$files = $this->file->all();
return [
'count' => $files->count(),
'data' => $files,
];
}
/**
* Store a newly created resource in storage.
*
* @param UploadMediaRequest $request
* @return Response
*/
public function store(UploadMediaRequest $request)
{
$savedFile = $this->fileService->store($request->file('file'));
if (is_string($savedFile)) {
return Response::json([
'error' => $savedFile,
], 409);
}
event(new FileWasUploaded($savedFile));
return Response::json($savedFile->toArray());
}
/**
* Link the given entity with a media file
*
* @param Request $request
*/
public function linkMedia(Request $request)
{
$mediaId = $request->get('mediaId');
$entityClass = $request->get('entityClass');
$entityId = $request->get('entityId');
$order = $request->get('order');
$entity = $entityClass::find($entityId);
$zone = $request->get('zone');
$entity->files()->attach($mediaId, [
'imageable_type' => $entityClass,
'zone' => $zone,
'order' => $order,
]);
$imageable = DB::table('media__imageables')->whereFileId($mediaId)
->whereZone($zone)
->whereImageableType($entityClass)
->first();
$file = $this->file->find($imageable->file_id);
$mediaType = FileHelper::getTypeByMimetype($file->mimetype);
$thumbnailPath = $this->getThumbnailPathFor($mediaType, $file);
event(new FileWasLinked($file, $entity));
return Response::json([
'error' => false,
'message' => 'The link has been added.',
'result' => [
'path' => $thumbnailPath,
'imageableId' => $imageable->id,
'mediaType' => $mediaType,
'mimetype' => $file->mimetype,
],
]);
}
/**
* Remove the record in the media__imageables table for the given id
*
* @param Request $request
*/
public function unlinkMedia(Request $request)
{
$imageableId = $request->get('imageableId');
$deleted = DB::table('media__imageables')->whereId($imageableId)->delete();
if (! $deleted) {
return Response::json([
'error' => true,
'message' => 'The file was not found.',
]);
}
event(new FileWasUnlinked($imageableId));
return Response::json([
'error' => false,
'message' => 'The link has been removed.',
]);
}
/**
* Sort the record in the media__imageables table for the given array
* @param Request $request
*/
public function sortMedia(Request $request)
{
$imageableIdArray = $request->get('sortable');
$order = 1;
foreach ($imageableIdArray as $id) {
DB::table('media__imageables')->whereId($id)->update(['order' => $order]);
$order++;
}
return Response::json(['error' => false, 'message' => 'The items have been reorder.']);
}
/**
* Get the path for the given file and type
* @param string $mediaType
* @param File $file
* @return string
*/
private function getThumbnailPathFor($mediaType, File $file)
{
if ($mediaType === 'image') {
return $this->imagy->getThumbnail($file->path, 'mediumThumb');
}
return $file->path->getRelativeUrl();
}
}
<?php
namespace Modules\Media\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateMediaRequest extends FormRequest
{
public function rules()
{
return [];
}
public function authorize()
{
return true;
}
public function messages()
{
return [];
}
}
<?php
namespace Modules\Media\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UploadMediaRequest extends FormRequest
{
public function rules()
{
return [
'file' => ['required', 'max_size'],
];
}
public function authorize()
{
return true;
}
public function messages()
{
$bytes = config('asgard.media.config.max-total-size');
$size = $this->formatBytes($bytes);
return [
'file.max_size' => trans('media::media.validation.max_size', ['size' => $size]),
];
}
public function formatBytes($bytes, $precision = 2)
{
$units = [
trans('media::media.file-sizes.B'),
trans('media::media.file-sizes.KB'),
trans('media::media.file-sizes.MB'),
trans('media::media.file-sizes.GB'),
trans('media::media.file-sizes.TB'),
];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
}
<?php
use Illuminate\Routing\Router;
/** @var Router $router */
$router->group(['middleware' => 'api.token'], function (Router $router) {
$router->post('file', [
'uses' => 'MediaController@store',
'as' => 'api.media.store',
'middleware' => 'token-can:media.medias.create',
]);
$router->post('media/link', [
'uses' => 'MediaController@linkMedia',
'as' => 'api.media.link',
]);
$router->post('media/unlink', [
'uses' => 'MediaController@unlinkMedia',
'as' => 'api.media.unlink',
]);
$router->get('media/all', [
'uses' => 'MediaController@all',
'as' => 'api.media.all',
'middleware' => 'token-can:media.medias.index',
]);
$router->post('media/sort', [
'uses' => 'MediaController@sortMedia',
'as' => 'api.media.sort',
]);
});
<?php
use Illuminate\Routing\Router;
/** @var Router $router */
$router->bind('media', function ($id) {
return app(\Modules\Media\Repositories\FileRepository::class)->find($id);
});
$router->group(['prefix' => '/media'], function (Router $router) {
$router->get('media', [
'as' => 'admin.media.media.index',
'uses' => 'MediaController@index',
'middleware' => 'can:media.medias.index',
]);
$router->get('media/create', [
'as' => 'admin.media.media.create',
'uses' => 'MediaController@create',
'middleware' => 'can:media.medias.create',
]);
$router->post('media', [
'as' => 'admin.media.media.store',
'uses' => 'MediaController@store',
'middleware' => 'can:media.medias.create',
]);
$router->get('media/{media}/edit', [
'as' => 'admin.media.media.edit',
'uses' => 'MediaController@edit',
'middleware' => 'can:media.medias.edit',
]);
$router->put('media/{media}', [
'as' => 'admin.media.media.update',
'uses' => 'MediaController@update',
'middleware' => 'can:media.medias.edit',
]);
$router->delete('media/{media}', [
'as' => 'admin.media.media.destroy',
'uses' => 'MediaController@destroy',
'middleware' => 'can:media.medias.destroy',
]);
$router->get('media-grid/index', [
'uses' => 'MediaGridController@index',
'as' => 'media.grid.select',
'middleware' => 'can:media.medias.index',
]);
$router->get('media-grid/ckIndex', [
'uses' => 'MediaGridController@ckIndex',
'as' => 'media.grid.ckeditor',
'middleware' => 'can:media.medias.index',
]);
});
<?php
namespace Modules\Media\Image\Facade;
use Illuminate\Support\Facades\Facade;
class Imagy extends Facade
{
protected static function getFacadeAccessor()
{
return 'imagy';
}
}
<?php
namespace Modules\Media\Image;
interface ImageFactoryInterface
{
/**
* Return a new Manipulation class
* @param string $manipulation
* @return \Modules\Media\Image\ImageHandlerInterface
*/
public function make($manipulation);
}
<?php
namespace Modules\Media\Image;
interface ImageHandlerInterface
{
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options);
}
<?php
namespace Modules\Media\Image;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\ServiceProvider;
use Modules\Media\Image\Intervention\InterventionFactory;
class ImageServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->bind(ImageFactoryInterface::class, InterventionFactory::class);
$this->app->singleton(ThumbnailManager::class, function () {
return new ThumbnailManagerRepository();
});
$this->app['imagy'] = $this->app->share(function ($app) {
$factory = new InterventionFactory();
return new Imagy($factory, $app[ThumbnailManager::class], $app['config']);
});
$this->app->booting(function () {
$loader = AliasLoader::getInstance();
$loader->alias('Imagy', Facade\Imagy::class);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['imagy'];
}
}
<?php
namespace Modules\Media\Image;
use GuzzleHttp\Psr7\Stream;
use Illuminate\Contracts\Filesystem\Factory;
use Intervention\Image\ImageManager;
use Modules\Media\Entities\File;
use Modules\Media\ValueObjects\MediaPath;
class Imagy
{
/**
* @var \Intervention\Image\Image
*/
private $image;
/**
* @var ImageFactoryInterface
*/
private $imageFactory;
/**
* @var ThumbnailManager
*/
private $manager;
/**
* All the different images types where thumbnails should be created
* @var array
*/
private $imageExtensions = ['jpg', 'png', 'jpeg', 'gif'];
/**
* @var Factory
*/
private $filesystem;
/**
* @param ImageFactoryInterface $imageFactory
* @param ThumbnailManager $manager
*/
public function __construct(ImageFactoryInterface $imageFactory, ThumbnailManager $manager)
{
$this->image = app(ImageManager::class);
$this->filesystem = app(Factory::class);
$this->imageFactory = $imageFactory;
$this->manager = $manager;
}
/**
* Get an image in the given thumbnail options
* @param string $path
* @param string $thumbnail
* @param bool $forceCreate
* @return string
*/
public function get($path, $thumbnail, $forceCreate = false)
{
if (!$this->isImage($path)) {
return;
}
$filename = config('asgard.media.config.files-path') . $this->newFilename($path, $thumbnail);
if ($this->returnCreatedFile($filename, $forceCreate)) {
return $filename;
}
if ($this->fileExists($filename) === true) {
$this->filesystem->disk($this->getConfiguredFilesystem())->delete($filename);
}
$mediaPath = (new MediaPath($filename))->getUrl();
$this->makeNew($path, $mediaPath, $thumbnail);
return (new MediaPath($filename))->getUrl();
}
/**
* Return the thumbnail path
* @param string|File $originalImage
* @param string $thumbnail
* @return string
*/
public function getThumbnail($originalImage, $thumbnail)
{
if ($originalImage instanceof File) {
$originalImage = $originalImage->path;
}
if (!$this->isImage($originalImage)) {
if ($originalImage instanceof MediaPath) {
return $originalImage->getUrl();
}
return (new MediaPath($originalImage))->getRelativeUrl();
}
$path = config('asgard.media.config.files-path') . $this->newFilename($originalImage, $thumbnail);
return (new MediaPath($path))->getUrl();
}
/**
* Create all thumbnails for the given image path
* @param MediaPath $path
*/
public function createAll(MediaPath $path)
{
if (!$this->isImage($path)) {
return;
}
foreach ($this->manager->all() as $thumbnail) {
$image = $this->image->make($this->filesystem->disk($this->getConfiguredFilesystem())->get($this->getDestinationPath($path->getRelativeUrl())));
$filename = config('asgard.media.config.files-path') . $this->newFilename($path, $thumbnail->name());
foreach ($thumbnail->filters() as $manipulation => $options) {
$image = $this->imageFactory->make($manipulation)->handle($image, $options);
}
$image = $image->stream(pathinfo($path, PATHINFO_EXTENSION), array_get($thumbnail->filters(), 'quality', 90));
$this->writeImage($filename, $image);
}
}
/**
* Prepend the thumbnail name to filename
* @param $path
* @param $thumbnail
* @return mixed|string
*/
private function newFilename($path, $thumbnail)
{
$filename = pathinfo($path, PATHINFO_FILENAME);
return $filename . '_' . $thumbnail . '.' . pathinfo($path, PATHINFO_EXTENSION);
}
/**
* Return the already created file if it exists and force create is false
* @param string $filename
* @param bool $forceCreate
* @return bool
*/
private function returnCreatedFile($filename, $forceCreate)
{
return $this->fileExists($filename) && $forceCreate === false;
}
/**
* Write the given image
* @param string $filename
* @param Stream $image
*/
private function writeImage($filename, Stream $image)
{
$filename = $this->getDestinationPath($filename);
$resource = $image->detach();
$config = [
'visibility' => 'public',
'mimetype' => \GuzzleHttp\Psr7\mimetype_from_filename($filename),
];
if ($this->fileExists($filename)) {
return $this->filesystem->disk($this->getConfiguredFilesystem())->updateStream($filename, $resource, $config);
}
$this->filesystem->disk($this->getConfiguredFilesystem())->writeStream($filename, $resource, $config);
}
/**
* Make a new image
* @param MediaPath $path
* @param string $filename
* @param string null $thumbnail
*/
private function makeNew(MediaPath $path, $filename, $thumbnail)
{
$image = $this->image->make($path->getUrl());
foreach ($this->manager->find($thumbnail) as $manipulation => $options) {
$image = $this->imageFactory->make($manipulation)->handle($image, $options);
}
$image = $image->stream(pathinfo($path, PATHINFO_EXTENSION));
$this->writeImage($filename, $image);
}
/**
* Check if the given path is en image
* @param string $path
* @return bool
*/
public function isImage($path)
{
return in_array(pathinfo($path, PATHINFO_EXTENSION), $this->imageExtensions);
}
/**
* Delete all files on disk for the given file in storage
* This means the original and the thumbnails
* @param $file
* @return bool
*/
public function deleteAllFor(File $file)
{
if (!$this->isImage($file->path)) {
return $this->filesystem->disk($this->getConfiguredFilesystem())->delete($this->getDestinationPath($file->path->getRelativeUrl()));
}
$paths[] = $this->getDestinationPath($file->path->getRelativeUrl());
$fileName = pathinfo($file->path, PATHINFO_FILENAME);
$extension = pathinfo($file->path, PATHINFO_EXTENSION);
foreach ($this->manager->all() as $thumbnail) {
$path = config('asgard.media.config.files-path') . "{$fileName}_{$thumbnail->name()}.{$extension}";
if ($this->fileExists($this->getDestinationPath($path))) {
$paths[] = (new MediaPath($this->getDestinationPath($path)))->getRelativeUrl();
}
}
return $this->filesystem->disk($this->getConfiguredFilesystem())->delete($paths);
}
private function getConfiguredFilesystem()
{
return config('asgard.media.config.filesystem');
}
/**
* @param $filename
* @return bool
*/
private function fileExists($filename)
{
return $this->filesystem->disk($this->getConfiguredFilesystem())->exists($filename);
}
/**
* @param string $path
* @return string
*/
private function getDestinationPath($path)
{
if ($this->getConfiguredFilesystem() === 'local') {
return basename(public_path()) . $path;
}
return $path;
}
}
<?php
namespace Modules\Media\Image\Intervention;
use Modules\Media\Image\ImageFactoryInterface;
class InterventionFactory implements ImageFactoryInterface
{
/**
* @param string $manipulation
* @return \Modules\Media\Image\ImageHandlerInterface
*/
public function make($manipulation)
{
$class = 'Modules\\Media\\Image\\Intervention\\Manipulations\\' . ucfirst($manipulation);
return new $class();
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Blur implements ImageHandlerInterface
{
private $defaults = [
'amount' => 1,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->blur($options['amount']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Brightness implements ImageHandlerInterface
{
private $defaults = [
'level' => 1,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->brightness($options['level']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class CanvasResize implements ImageHandlerInterface
{
private $defaults = [
'width' => 100,
'height' => 100,
'anchor' => 'center',
'relative' => false,
'bgcolor' => 'rgba(255, 255, 255, 0)',
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->resizeCanvas($options['width'], $options['height'], $options['anchor'], $options['relative'], $options['bgcolor']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Colorize implements ImageHandlerInterface
{
private $defaults = [
'red' => 100,
'green' => 100,
'blue' => 100,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->colorize($options['red'], $options['green'], $options['blue']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Contrast implements ImageHandlerInterface
{
private $defaults = [
'level' => 0,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->contrast($options['level']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Crop implements ImageHandlerInterface
{
private $defaults = [
'width' => '100',
'height' => '100',
'x' => null,
'y' => null,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return mixed
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->crop($options['width'], $options['height'], $options['x'], $options['y']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Fit implements ImageHandlerInterface
{
private $defaults = [
'width' => 100,
'height' => null,
'position' => 'center',
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
$callback = isset($options['callback']) ? $options['callback'] : null;
return $image->fit($options['width'], $options['height'], $callback, $options['position']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Flip implements ImageHandlerInterface
{
private $defaults = [
'mode' => 'h',
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->flip($options['mode']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Gamma implements ImageHandlerInterface
{
private $defaults = [
'correction' => 0,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->gamma($options['correction']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Greyscale implements ImageHandlerInterface
{
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
return $image->greyscale();
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Heighten implements ImageHandlerInterface
{
private $defaults = [
'height' => 0,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
$callback = isset($options['callback']) ? $options['callback'] : null;
return $image->heighten($options['height'], $callback);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Invert implements ImageHandlerInterface
{
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
return $image->invert();
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class LimitColors implements ImageHandlerInterface
{
private $defaults = [
'count' => 255,
'matte' => null,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->limitColors($options['count'], $options['matte']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Opacity implements ImageHandlerInterface
{
private $defaults = [
'transparency' => 50,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->opacity($options['transparency']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Orientate implements ImageHandlerInterface
{
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
return $image->orientate();
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Pixelate implements ImageHandlerInterface
{
private $defaults = [
'size' => 0,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->pixelate($options['size']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Quality implements ImageHandlerInterface
{
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
return $image;
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Resize implements ImageHandlerInterface
{
private $defaults = [
'width' => 200,
'height' => 200,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
$callback = isset($options['callback']) ? $options['callback'] : null;
return $image->resize($options['width'], $options['height'], $callback);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Rotate implements ImageHandlerInterface
{
private $defaults = [
'angle' => 45,
'bgcolor' => '#000000',
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->rotate($options['angle'], $options['bgcolor']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Sharpen implements ImageHandlerInterface
{
private $defaults = [
'amount' => 10,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->sharpen($options['amount']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Trim implements ImageHandlerInterface
{
private $defaults = [
'base' => 'top-left',
'away' => ['top', 'bottom', 'left', 'right'],
'tolerance' => 0,
'feather' => 0,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->trim($options['base'], $options['away'], $options['tolerance'], $options['feather']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Watermark implements ImageHandlerInterface
{
private $defaults = [
'source' => 'public/assets/watermark.png',
'position' => 'bottom-right',
'x' => null,
'y' => null,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
return $image->insert($options['source'], $options['position'], $options['x'], $options['y']);
}
}
<?php
namespace Modules\Media\Image\Intervention\Manipulations;
use Modules\Media\Image\ImageHandlerInterface;
class Widen implements ImageHandlerInterface
{
private $defaults = [
'width' => 0,
];
/**
* Handle the image manipulation request
* @param \Intervention\Image\Image $image
* @param array $options
* @return \Intervention\Image\Image
*/
public function handle($image, $options)
{
$options = array_merge($this->defaults, $options);
$callback = isset($options['callback']) ? $options['callback'] : null;
return $image->widen($options['width'], $callback);
}
}
<?php
namespace Modules\Media\Image;
class Thumbnail
{
/**
* @var array
*/
private $filters;
/**
* @var string
*/
private $name;
/**
* @param $name
* @param $filters
*/
private function __construct($name, $filters)
{
$this->filters = $filters;
$this->name = $name;
}
/**
* @param $thumbnailDefinition
* @return static
*/
public static function make($thumbnailDefinition)
{
$name = key($thumbnailDefinition);
return new static($name, $thumbnailDefinition[$name]);
}
/**
* Make multiple thumbnail classes with the given array
* @param array $thumbnailDefinitions
* @return array
*/
public static function makeMultiple(array $thumbnailDefinitions)
{
$thumbnails = [];
foreach ($thumbnailDefinitions as $name => $thumbnail) {
$thumbnails[] = self::make([$name => $thumbnail]);
}
return $thumbnails;
}
/**
* Return the thumbnail name
* @return string
*/
public function name()
{
return $this->name;
}
/**
* @return array
*/
public function filters()
{
return $this->filters;
}
/**
* Return the first width option found in the filters
* @return int
*/
public function width()
{
return $this->getFirst('width');
}
/**
* Return the first height option found in the filters
* @return int
*/
public function height()
{
return $this->getFirst('height');
}
/**
* Get the thumbnail size in format: width x height
* @return string
*/
public function size()
{
return $this->width() . 'x' . $this->height();
}
/**
* Get the first found key in filters
* @param string $key
* @return int
*/
private function getFirst($key)
{
foreach ($this->filters as $filter) {
if (isset($filter[$key])) {
return (int) $filter[$key];
}
}
}
}
<?php
namespace Modules\Media\Image;
interface ThumbnailManager
{
/**
* Register a thumbnail
* @param string $name
* @param array $filters
* @return void
*/
public function registerThumbnail($name, array $filters);
/**
* Return all registered thumbnails
* @return array
*/
public function all();
/**
* Find the filters for the given thumbnail
* @param string $thumbnail
* @return array
*/
public function find($thumbnail);
}
<?php
namespace Modules\Media\Image;
class ThumbnailManagerRepository implements ThumbnailManager
{
/**
* @var array
*/
private $thumbnails = [];
public function registerThumbnail($name, array $filters)
{
$this->thumbnails[$name] = Thumbnail::make([$name => $filters]);
}
/**
* Return all registered thumbnails
* @return array
*/
public function all()
{
return $this->thumbnails;
}
/**
* Find the filters for the given thumbnail
* @param $thumbnail
* @return array
*/
public function find($thumbnail)
{
foreach ($this->all() as $thumb) {
if ($thumb->name() === $thumbnail) {
return $thumb->filters();
}
}
return [];
}
}
<?php
namespace Modules\Media\Jobs;
use App\Jobs\Job;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Modules\Media\ValueObjects\MediaPath;
class CreateThumbnails extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/**
* @var MediaPath
*/
private $path;
public function __construct(MediaPath $path)
{
$this->path = $path;
}
public function handle()
{
$imagy = app('imagy');
app('log')->info('Generating thumbnails for path: ' . $this->path);
$imagy->createAll($this->path);
}
}
<?php
namespace Modules\Media\Jobs;
use App\Jobs\Job;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
class RebuildThumbnails extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/**
* @var Collection
*/
private $paths;
public function __construct(Collection $paths)
{
$this->paths = $paths;
}
public function handle()
{
$imagy = app('imagy');
foreach ($this->paths as $path) {
try {
$imagy->createAll($path);
app('log')->info('Generating thumbnails for path: ' . $path);
} catch (\Exception $e) {
app('log')->warning('File not found: ' . $path);
}
}
}
}
# License (MIT)
Copyright (c) 2016 Nicolas Widart , n.widart@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
<?php
namespace Modules\Media\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
use Modules\Core\Traits\CanPublishConfiguration;
use Modules\Media\Blade\MediaMultipleDirective;
use Modules\Media\Blade\MediaSingleDirective;
use Modules\Media\Console\RefreshThumbnailCommand;
use Modules\Media\Entities\File;
use Modules\Media\Events\Handlers\HandleMediaStorage;
use Modules\Media\Events\Handlers\RemovePolymorphicLink;
use Modules\Media\Image\ThumbnailManager;
use Modules\Media\Repositories\Eloquent\EloquentFileRepository;
use Modules\Media\Repositories\FileRepository;
use Modules\Tag\Repositories\TagManager;
class MediaServiceProvider extends ServiceProvider
{
use CanPublishConfiguration;
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerBindings();
$this->registerCommands();
$this->app->bind('media.single.directive', function () {
return new MediaSingleDirective();
});
$this->app->bind('media.multiple.directive', function () {
return new MediaMultipleDirective();
});
}
public function boot(DispatcherContract $events)
{
$this->registerMaxFolderSizeValidator();
$this->publishConfig('media', 'config');
$this->publishConfig('media', 'permissions');
$this->publishConfig('media', 'assets');
$events->listen('*', HandleMediaStorage::class);
$events->listen('*', RemovePolymorphicLink::class);
$this->app[TagManager::class]->registerNamespace(new File());
$this->registerThumbnails();
$this->registerBladeTags();
$this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array();
}
private function registerBindings()
{
$this->app->bind(FileRepository::class, function ($app) {
return new EloquentFileRepository(new File(), $app['filesystem.disk']);
});
}
/**
* Register all commands for this module
*/
private function registerCommands()
{
$this->registerRefreshCommand();
}
/**
* Register the refresh thumbnails command
*/
private function registerRefreshCommand()
{
$this->app->singleton('command.media.refresh', function ($app) {
return new RefreshThumbnailCommand($app['Modules\Media\Repositories\FileRepository']);
});
$this->commands('command.media.refresh');
}
private function registerMaxFolderSizeValidator()
{
Validator::extend('max_size', '\Modules\Media\Validators\MaxFolderSizeValidator@validateMaxSize');
}
private function registerThumbnails()
{
$this->app[ThumbnailManager::class]->registerThumbnail('smallThumb', [
'resize' => [
'width' => 50,
'height' => null,
'callback' => function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
},
],
]);
$this->app[ThumbnailManager::class]->registerThumbnail('mediumThumb', [
'resize' => [
'width' => 180,
'height' => null,
'callback' => function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
},
],
]);
}
private function registerBladeTags()
{
if (app()->environment() === 'testing') {
return;
}
$this->app['blade.compiler']->directive('mediaSingle', function ($value) {
return "<?php echo MediaSingleDirective::show([$value]); ?>";
});
$this->app['blade.compiler']->directive('mediaMultiple', function ($value) {
return "<?php echo MediaMultipleDirective::show([$value]); ?>";
});
}
}
<?php
namespace Modules\Media\Providers;
use Modules\Core\Providers\RoutingServiceProvider as CoreRoutingServiceProvider;
class RouteServiceProvider extends CoreRoutingServiceProvider
{
/**
* The root namespace to assume when generating URLs to actions.
* @var string
*/
protected $namespace = 'Modules\Media\Http\Controllers';
/**
* @return string
*/
protected function getFrontendRoute()
{
return false;
}
/**
* @return string
*/
protected function getBackendRoute()
{
return __DIR__ . '/../Http/backendRoutes.php';
}
/**
* @return string
*/
protected function getApiRoute()
{
return __DIR__ . '/../Http/apiRoutes.php';
}
}
<?php
namespace Modules\Media\Repositories\Eloquent;
use Illuminate\Database\Eloquent\Collection;
use Modules\Core\Repositories\Eloquent\EloquentBaseRepository;
use Modules\Media\Entities\File;
use Modules\Media\Helpers\FileHelper;
use Modules\Media\Repositories\FileRepository;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class EloquentFileRepository extends EloquentBaseRepository implements FileRepository
{
/**
* Update a resource
* @param File $file
* @param $data
* @return mixed
*/
public function update($file, $data)
{
$file->update($data);
$file->setTags(array_get($data, 'tags', []));
return $file;
}
/**
* Create a file row from the given file
* @param UploadedFile $file
* @return mixed
*/
public function createFromFile(UploadedFile $file)
{
$fileName = FileHelper::slug($file->getClientOriginalName());
$exists = $this->model->whereFilename($fileName)->first();
if ($exists) {
$fileName = $this->getNewUniqueFilename($fileName);
}
return $this->model->create([
'filename' => $fileName,
'path' => config('asgard.media.config.files-path') . "{$fileName}",
'extension' => substr(strrchr($fileName, "."), 1),
'mimetype' => $file->getClientMimeType(),
'filesize' => $file->getFileInfo()->getSize(),
'folder_id' => 0,
]);
}
public function destroy($file)
{
$file->delete();
}
/**
* Find a file for the entity by zone
* @param $zone
* @param object $entity
* @return object
*/
public function findFileByZoneForEntity($zone, $entity)
{
foreach ($entity->files as $file) {
if ($file->pivot->zone == $zone) {
return $file;
}
}
return '';
}
/**
* Find multiple files for the given zone and entity
* @param zone $zone
* @param object $entity
* @return object
*/
public function findMultipleFilesByZoneForEntity($zone, $entity)
{
$files = [];
foreach ($entity->files as $file) {
if ($file->pivot->zone == $zone) {
$files[] = $file;
}
}
return new Collection($files);
}
/**
* @param $fileName
* @return string
*/
private function getNewUniqueFilename($fileName)
{
$fileNameOnly = pathinfo($fileName, PATHINFO_FILENAME);
$model = $this->model->where('filename', 'LIKE', "$fileNameOnly%")->orderBy('created_at', 'desc')->first();
$latestFilename = pathinfo($model->filename, PATHINFO_FILENAME);
$extension = pathinfo($model->filename, PATHINFO_EXTENSION);
$version = substr($latestFilename, -1, strpos($latestFilename, '_'));
$version++;
return $fileNameOnly . '_' . $version . '.' . $extension;
}
}
<?php
namespace Modules\Media\Repositories;
use Modules\Core\Repositories\BaseRepository;
use Symfony\Component\HttpFoundation\File\UploadedFile;
interface FileRepository extends BaseRepository
{
/**
* Create a file row from the given file
* @param UploadedFile $file
* @return mixed
*/
public function createFromFile(UploadedFile $file);
/**
* Find a file for the entity by zone
* @param string $zone
* @param object $entity
* @return object
*/
public function findFileByZoneForEntity($zone, $entity);
/**
* Find multiple files for the given zone and entity
* @param string $zone
* @param object $entity
* @return object
*/
public function findMultipleFilesByZoneForEntity($zone, $entity);
}
@extends('layouts.master')
@section('content-header')
<h1>
{{ trans('media::media.title.edit media') }} <small>{{ $file->filename }}</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ URL::route('dashboard.index') }}"><i class="fa fa-dashboard"></i> {{ trans('core::core.breadcrumb.home') }}</a></li>
<li><a href="{{ URL::route('admin.media.media.index') }}">{{ trans('media::media.title.media') }}</a></li>
<li class="active">{{ trans('media::media.title.edit media') }}</li>
</ol>
@stop
@section('content')
{!! Form::open(['route' => ['admin.media.media.update', $file->id], 'method' => 'put']) !!}
<div class="row">
<div class="col-md-8">
<div class="nav-tabs-custom">
@include('partials.form-tab-headers')
<div class="tab-content">
<?php $i = 0; ?>
<?php foreach (LaravelLocalization::getSupportedLocales() as $locale => $language): ?>
<?php ++$i; ?>
<div class="tab-pane {{ App::getLocale() == $locale ? 'active' : '' }}" id="tab_{{ $i }}">
@include('media::admin.partials.edit-fields', ['lang' => $locale])
</div>
<?php endforeach; ?>
<hr>
@tags('asgardcms/media', $file)
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-flat">{{ trans('core::core.button.update') }}</button>
<button class="btn btn-default btn-flat" name="button" type="reset">{{ trans('core::core.button.reset') }}</button>
<a class="btn btn-danger pull-right btn-flat" href="{{ URL::route('admin.media.media.index')}}"><i class="fa fa-times"></i> {{ trans('core::core.button.cancel') }}</a>
</div>
</div>
</div> {{-- end nav-tabs-custom --}}
</div>
<div class="col-md-4">
<?php if ($file->isImage()): ?>
<img src="{{ $file->path }}" alt="" style="width: 100%;"/>
<?php else: ?>
<i class="fa fa-file" style="font-size: 50px;"></i>
<?php endif; ?>
</div>
</div>
<?php if ($file->isImage()): ?>
<div class="row">
<div class="col-md-12">
<h3>Thumbnails</h3>
<ul class="list-unstyled">
<?php foreach ($thumbnails as $thumbnail): ?>
<li style="float:left; margin-right: 10px">
<img src="{{ Imagy::getThumbnail($file->path, $thumbnail->name()) }}" alt=""/>
<p class="text-muted" style="text-align: center">{{ $thumbnail->name() }} ({{ $thumbnail->size() }})</p>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
<?php endif; ?>
{!! Form::close() !!}
@stop
@section('footer')
<a data-toggle="modal" data-target="#keyboardShortcutsModal"><i class="fa fa-keyboard-o"></i></a> &nbsp;
@stop
@section('shortcuts')
<dl class="dl-horizontal">
<dt><code>b</code></dt>
<dd>{{ trans('core::core.back to index', ['name' => 'media']) }}</dd>
</dl>
@stop
@section('scripts')
<script>
$( document ).ready(function() {
$(document).keypressAction({
actions: [
{ key: 'b', route: "<?= route('admin.media.media.index') ?>" }
]
});
});
</script>
@stop
<script src="{{ Module::asset('dashboard:vendor/jquery-ui/jquery-ui.min.js') }}"></script>
<script>
var $fileCount = $('.jsFileCount');
if (typeof window.openMediaWindowMultipleOld === 'undefined') {
window.mediaZone = '';
window.openMediaWindowMultipleOld = function (event, zone) {
window.single = false;
window.old = true;
window.mediaZone = zone;
window.zoneWrapper = $(event.currentTarget).siblings('.jsThumbnailImageWrapper');
window.open(Asgard.mediaGridSelectUrl, '_blank', 'menubar=no,status=no,toolbar=no,scrollbars=yes,height=500,width=1000');
};
}
if (typeof window.includeMediaMultipleOld === 'undefined') {
window.includeMediaMultipleOld = function (mediaId) {
$.ajax({
type: 'POST',
url: Asgard.mediaLinkUrl,
data: {
'mediaId': mediaId,
'_token': '{{ csrf_token() }}',
'entityClass': '{{ $entityClass }}',
'entityId': '{{ $entityId }}',
'zone': window.mediaZone,
'order': $('.jsThumbnailImageWrapper figure').size() + 1
},
success: function (data) {
var mediaPlaceholder;
if (data.result.mediaType === 'image') {
mediaPlaceholder = '<img src="' + data.result.path + '" alt=""/>';
} else if (data.result.mediaType == 'video') {
mediaPlaceholder = '<video src="' + data.result.path + '" controls width="320"></video>';
} else if (data.result.mediaType == 'audio') {
mediaPlaceholder = '<audio controls><source src="' + data.result.path + '" type="' + data.result.mimetype + '"></audio>'
} else {
mediaPlaceholder = '<i class="fa fa-file" style="font-size: 50px;"></i>';
}
var html = '<figure data-id="'+ data.result.imageableId +'">' + mediaPlaceholder +
'<a class="jsRemoveLink" href="#" data-id="' + data.result.imageableId + '">' +
'<i class="fa fa-times-circle removeIcon"></i>' +
'</a></figure>';
window.zoneWrapper.append(html).fadeIn();
if ($fileCount.length > 0) {
var count = parseInt($fileCount.text());
$fileCount.text(count + 1);
}
}
});
};
}
</script>
<div class="form-group">
{!! Form::label($zone, ucwords(str_replace('_', ' ', $zone)) . ':') !!}
<div class="clearfix"></div>
<a class="btn btn-primary btn-upload" onclick="openMediaWindowMultipleOld(event, '{{ $zone }}')"><i class="fa fa-upload"></i>
{{ trans('media::media.Browse') }}
</a>
<div class="clearfix"></div>
<div class="jsThumbnailImageWrapper">
<?php $zoneVar = "{$zone}Files" ?>
<?php if (isset($$zoneVar) && !$$zoneVar->isEmpty()): ?>
<?php foreach ($$zoneVar as $file): ?>
<figure data-id="{{ $file->pivot->id }}">
<?php if ($file->media_type == 'image'): ?>
<img src="{{ Imagy::getThumbnail($file->path, (isset($thumbnailSize) ? $thumbnailSize : 'mediumThumb')) }}" alt="{{ $file->alt_attribute }}"/>
<?php elseif ($file->media_type == 'video'): ?>
<video src="{{ $file->path }}" controls width="320"></video>
<?php elseif ($file->media_type == 'audio'): ?>
<audio controls><source src="{{ $file->path }}" type="{{ $file->mimetype }}"></audio>
<?php else: ?>
<i class="fa fa-file" style="font-size: 50px;"></i>
<?php endif; ?>
<a class="jsRemoveLink" href="#" data-id="{{ $file->pivot->id }}">
<i class="fa fa-times-circle removeIcon"></i>
</a>
</figure>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<script>
$( document ).ready(function() {
$('.jsThumbnailImageWrapper').on('click', '.jsRemoveLink', function (e) {
e.preventDefault();
var imageableId = $(this).data('id'),
pictureWrapper = $(this).parent();
$.ajax({
type: 'POST',
url: Asgard.mediaUnlinkUrl,
data: {
'imageableId': imageableId,
'_token': '{{ csrf_token() }}'
},
success: function(data) {
if (data.error === false) {
pictureWrapper.fadeOut().remove();
if ($fileCount.length > 0) {
var count = parseInt($fileCount.text());
$fileCount.text(count - 1);
}
} else {
pictureWrapper.append(data.message);
}
}
});
});
$(".jsThumbnailImageWrapper").not(".jsSingleThumbnailWrapper").sortable({
item: 'figure',
placeholder: 'ui-state-highlight',
cursor:'move',
helper: 'clone',
containment: 'parent',
forcePlaceholderSize: false,
forceHelperSize: true,
update:function(event, ui) {
var dataSortable = $(this).sortable('toArray', {attribute: 'data-id'});
$.ajax({
global: false, /* leave it to false */
type: 'POST',
url: Asgard.mediaSortUrl,
data: {
'entityClass': '{{ $entityClass }}',
'zone': '{{ $zone }}',
'sortable': dataSortable,
'_token': '{{ csrf_token() }}'
}
});
}
});
});
</script>
<script>
if (typeof window.openMediaWindowSingleOld === 'undefined') {
window.mediaZone = '';
window.openMediaWindowSingleOld = function (event, zone) {
window.mediaZone = zone;
window.single = true;
window.old = true;
window.zoneWrapper = $(event.currentTarget).siblings('.jsThumbnailImageWrapper');
window.open(Asgard.mediaGridSelectUrl, '_blank', 'menubar=no,status=no,toolbar=no,scrollbars=yes,height=500,width=1000');
};
}
if (typeof window.includeMediaSingleOld === 'undefined') {
window.includeMediaSingleOld = function (mediaId) {
$.ajax({
type: 'POST',
url: Asgard.mediaLinkUrl,
data: {
'mediaId': mediaId,
'_token': '{{ csrf_token() }}',
'entityClass': '{{ $entityClass }}',
'entityId': '{{ $entityId }}',
'zone': window.mediaZone
},
success: function (data) {
var mediaPlaceholder;
if (data.result.mediaType === 'image') {
mediaPlaceholder = '<img src="' + data.result.path + '" alt=""/>';
} else if (data.result.mediaType == 'video') {
mediaPlaceholder = '<video src="' + data.result.path + '" controls width="320"></video>';
} else if (data.result.mediaType == 'audio') {
mediaPlaceholder = '<audio controls><source src="' + data.result.path + '" type="' + data.result.mimetype + '"></audio>'
} else {
mediaPlaceholder = '<i class="fa fa-file" style="font-size: 50px;"></i>';
}
var html = '<figure data-id="' + data.result.imageableId + '">' + mediaPlaceholder +
'<a class="jsRemoveSimpleLink" href="#" data-id="' + data.result.imageableId + '">' +
'<i class="fa fa-times-circle removeIcon"></i>' +
'</a></figure>';
window.zoneWrapper.append(html).fadeIn('slow', function() {
toggleButton($(this));
});
}
});
};
}
</script>
<div class="form-group">
{!! Form::label($zone, ucwords(str_replace('_', ' ', $zone)) . ':') !!}
<div class="clearfix"></div>
<a class="btn btn-primary btn-browse" onclick="openMediaWindowSingleOld(event, '{{ $zone }}');" <?php echo (isset(${$zone}->path))?'style="display:none;"':'' ?>><i class="fa fa-upload"></i>
{{ trans('media::media.Browse') }}
</a>
<div class="clearfix"></div>
<div class="jsThumbnailImageWrapper jsSingleThumbnailWrapper">
<?php if (isset(${$zone}->path)): ?>
<figure data-id="{{ ${$zone}->pivot->id }}">
<?php if (${$zone}->media_type == 'image'): ?>
<img src="{{ Imagy::getThumbnail(${$zone}->path, (isset($thumbnailSize) ? $thumbnailSize : 'mediumThumb')) }}" alt="{{ ${$zone}->alt_attribute }}"/>
<?php elseif (${$zone}->media_type == 'video'): ?>
<video src="{{ ${$zone}->path }}" controls width="320"></video>
<?php elseif (${$zone}->media_type == 'audio'): ?>
<audio controls><source src="{{ ${$zone}->path }}" type="{{ ${$zone}->mimetype }}"></audio>
<?php else: ?>
<i class="fa fa-file" style="font-size: 50px;"></i>
<?php endif; ?>
<a class="jsRemoveSimpleLink" href="#" data-id="{{ ${$zone}->pivot->id }}">
<i class="fa fa-times-circle removeIcon"></i>
</a>
</figure>
<?php endif; ?>
</div>
</div>
<script>
$( document ).ready(function() {
$('.jsThumbnailImageWrapper').off('click', '.jsRemoveSimpleLink');
$('.jsThumbnailImageWrapper').on('click', '.jsRemoveSimpleLink', function (e) {
e.preventDefault();
var imageableId = $(this).data('id');
$.ajax({
type: 'POST',
url: Asgard.mediaUnlinkUrl,
data: {
'imageableId': imageableId,
'_token': '{{ csrf_token() }}'
},
success: function(data) {
if (data.error === false) {
$(e.delegateTarget).fadeOut('slow', function() {
toggleButton($(this));
}).html('');
} else {
$(e.delegateTarget).append(data.message);
}
}
});
});
});
function toggleButton(el) {
var browseButton = el.parent().find('.btn-browse');
browseButton.toggle();
}
</script>
<div class="form-group">
{!! Form::label($zone, ucwords(str_replace('_', ' ', $zone)) . ':') !!}
<div class="clearfix"></div>
<a class="btn btn-primary btn-upload" onclick="openMediaWindowMultiple(event, '{{ $zone }}')"><i class="fa fa-upload"></i>
{{ trans('media::media.Browse') }}
</a>
<div class="clearfix"></div>
<div class="jsThumbnailImageWrapper">
<?php if (isset($media) && !$media->isEmpty()): ?>
<?php $order_list = [] ?>
<?php foreach ($media as $file): ?>
<?php $order_list[$zone][] = $file->id; ?>
<figure data-id="{{ $file->id }}">
<?php if ($file->media_type === 'image'): ?>
<img src="{{ Imagy::getThumbnail($file->path, (isset($thumbnailSize) ? $thumbnailSize : 'mediumThumb')) }}" alt="{{ $file->alt_attribute }}"/>
<?php elseif ($file->media_type === 'video'): ?>
<video src="{{ $file->path }}" controls width="320"></video>
<?php elseif ($file->media_type === 'audio'): ?>
<audio controls><source src="{{ $file->path }}" type="{{ $file->mimetype }}"></audio>
<?php else: ?>
<i class="fa fa-file" style="font-size: 50px;"></i>
<?php endif; ?>
<a class="jsRemoveLink" href="#" data-id="{{ $file->pivot->id }}">
<i class="fa fa-times-circle removeIcon"></i>
</a>
<input type="hidden" name="medias_multi[{{ $zone }}][files][]" value="{{ $file->id }}">
</figure>
<?php endforeach; ?>
<input type="hidden" name="medias_multi[{{ $zone }}][orders]" value="{{ implode(',', $order_list[$zone]) }}" class="orders">
<?php else: ?>
<input type="hidden" name="medias_multi[{{ $zone }}][orders]" value="" class="orders">
<?php endif; ?>
</div>
</div>
<div class="form-group">
{!! Form::label($zone, ucwords(str_replace('_', ' ', $zone)) . ':') !!}
<div class="clearfix"></div>
<a class="btn btn-primary btn-browse" onclick="openMediaWindowSingle(event, '{{ $zone }}');" <?php echo (isset($media->path))?'style="display:none;"':'' ?>><i class="fa fa-upload"></i>
{{ trans('media::media.Browse') }}
</a>
<div class="clearfix"></div>
<div class="jsThumbnailImageWrapper jsSingleThumbnailWrapper">
<?php if (isset($media->path)): ?>
<figure data-id="{{ $media->id }}">
<?php if ($media->media_type === 'image'): ?>
<img src="{{ Imagy::getThumbnail($media->path, (isset($thumbnailSize) ? $thumbnailSize : 'mediumThumb')) }}" alt="{{ $media->alt_attribute }}"/>
<?php elseif ($media->media_type === 'video'): ?>
<video src="{{ $media->path }}" controls width="320"></video>
<?php elseif ($media->media_type === 'audio'): ?>
<audio controls><source src="{{ $media->path }}" type="{{ $media->mimetype }}"></audio>
<?php else: ?>
<i class="fa fa-file" style="font-size: 50px;"></i>
<?php endif; ?>
<a class="jsRemoveSimpleLink" href="#" data-id="{{ $media->pivot->id }}">
<i class="fa fa-times-circle removeIcon"></i>
</a>
</figure>
<input type="hidden" name="medias_single[{{ $zone }}]" value="{{ $media->id }}">
<?php else: ?>
<input type="hidden" name="medias_single[{{ $zone }}]" value="">
<?php endif; ?>
</div>
</div>
@include('media::admin.grid.partials.content', ['isWysiwyg' => true])
<script>
$(document).ready(function () {
$('.jsInsertImage').on('click', function (e) {
e.preventDefault();
function getUrlParam(paramName) {
var reParam = new RegExp('(?:[\?&]|&)' + paramName + '=([^&]+)', 'i');
var match = window.location.search.match(reParam);
return ( match && match.length > 1 ) ? match[1] : null;
}
var funcNum = getUrlParam('CKEditorFuncNum');
window.opener.CKEDITOR.tools.callFunction(funcNum, $(this).data('file-path'));
window.close();
});
});
</script>
</body>
</html>
@include('media::admin.grid.partials.content', ['isWysiwyg' => false])
<script>
$(document).ready(function () {
$('.jsInsertImage').on('click', function (e) {
e.preventDefault();
var mediaId = $(this).data('id'),
filePath = $(this).data('file-path'),
mediaType = $(this).data('mediaType'),
mimetype = $(this).data('mimetype');
if(window.opener.old) {
if(window.opener.single) {
window.opener.includeMediaSingleOld(mediaId, filePath);
window.close();
} else {
window.opener.includeMediaMultipleOld(mediaId, filePath);
}
} else {
if(window.opener.single) {
window.opener.includeMediaSingle(mediaId, filePath, mediaType, mimetype);
window.close();
} else {
window.opener.includeMediaMultiple(mediaId, filePath, mediaType, mimetype);
}
}
});
});
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ trans('media::media.file picker') }}</title>
{!! Theme::style('vendor/bootstrap/dist/css/bootstrap.min.css') !!}
{!! Theme::style('vendor/admin-lte/dist/css/AdminLTE.css') !!}
{!! Theme::style('vendor/datatables.net-bs/css/dataTables.bootstrap.min.css') !!}
{!! Theme::style('vendor/font-awesome/css/font-awesome.min.css') !!}
<link href="{!! Module::asset('media:css/dropzone.css') !!}" rel="stylesheet" type="text/css" />
<style>
body {
background: #ecf0f5;
margin-top: 20px;
}
.dropzone {
border: 1px dashed #CCC;
min-height: 227px;
margin-bottom: 20px;
display: none;
}
</style>
<script>
AuthorizationHeaderValue = 'Bearer {{ $currentUser->getFirstApiKey() }}';
</script>
@include('partials.asgard-globals')
</head>
<body>
<div class="container">
<div class="row">
<form method="POST" class="dropzone">
{!! Form::token() !!}
</form>
</div>
<div class="clearfix"></div>
<div class="row">
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">{{ trans('media::media.choose file') }}</h3>
<div class="box-tools pull-right">
<button class="btn btn-box-tool jsShowUploadForm" data-toggle="tooltip" title="" data-original-title="Upload new">
<i class="fa fa-cloud-upload"></i>
Upload new
</button>
</div>
</div>
<div class="box-body">
<table class="data-table table table-bordered table-hover jsFileList data-table">
<thead>
<tr>
<th>id</th>
<th>{{ trans('core::core.table.thumbnail') }}</th>
<th>{{ trans('media::media.table.filename') }}</th>
<th data-sortable="false">{{ trans('core::core.table.actions') }}</th>
</tr>
</thead>
<tbody>
<?php if ($files): ?>
<?php foreach ($files as $file): ?>
<tr>
<td>{{ $file->id }}</td>
<td>
<?php if ($file->isImage()): ?>
<img src="{{ Imagy::getThumbnail($file->path, 'smallThumb') }}" alt=""/>
<?php else: ?>
<i class="fa {{ FileHelper::getFaIcon($file->media_type) }}" style="font-size: 20px;"></i>
<?php endif; ?>
</td>
<td>{{ $file->filename }}</td>
<td>
<div class="btn-group">
<?php if ($isWysiwyg === true): ?>
<button type="button" class="btn btn-primary btn-flat dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
{{ trans('media::media.insert') }} <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<?php foreach ($thumbnails as $thumbnail): ?>
<li data-file-path="{{ Imagy::getThumbnail($file->path, $thumbnail->name()) }}"
data-id="{{ $file->id }}" data-media-type="{{ $file->media_type }}"
data-mimetype="{{ $file->mimetype }}" class="jsInsertImage">
<a href="">{{ $thumbnail->name() }} ({{ $thumbnail->size() }})</a>
</li>
<?php endforeach; ?>
<li class="divider"></li>
<li data-file-path="{{ $file->path }}" data-id="{{ $file->id }}"
data-media-type="{{ $file->media_type }}" data-mimetype="{{ $file->mimetype }}" class="jsInsertImage">
<a href="">Original</a>
</li>
</ul>
<?php else: ?>
<a href="" class="btn btn-primary jsInsertImage btn-flat" data-id="{{ $file->id }}"
data-file-path="{{ Imagy::getThumbnail($file->path, 'mediumThumb') }}"
data-media-type="{{ $file->media_type }}" data-mimetype="{{ $file->mimetype }}">
{{ trans('media::media.insert') }}
</a>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
{!! Theme::script('vendor/jquery/jquery.min.js') !!}
{!! Theme::script('vendor/bootstrap/dist/js/bootstrap.min.js') !!}
{!! Theme::script('vendor/datatables.net/js/jquery.dataTables.min.js') !!}
{!! Theme::script('vendor/datatables.net-bs/js/dataTables.bootstrap.min.js') !!}
<script src="{!! Module::asset('media:js/dropzone.js') !!}"></script>
<?php $config = config('asgard.media.config'); ?>
<script>
var maxFilesize = '<?php echo $config['max-file-size'] ?>',
acceptedFiles = '<?php echo $config['allowed-types'] ?>';
</script>
<script src="{!! Module::asset('media:js/init-dropzone.js') !!}"></script>
<script>
$( document ).ready(function() {
$('.jsShowUploadForm').on('click',function (event) {
event.preventDefault();
$('.dropzone').fadeToggle();
});
});
</script>
<?php $locale = App::getLocale(); ?>
<script type="text/javascript">
$(function () {
$('.data-table').dataTable({
"paginate": true,
"lengthChange": true,
"filter": true,
"sort": true,
"info": true,
"autoWidth": true,
"order": [[ 0, "desc" ]],
"language": {
"url": '<?php echo Module::asset("core:js/vendor/datatables/{$locale}.json") ?>'
}
});
});
</script>
@extends('layouts.master')
@section('content-header')
<h1>
{{ trans('media::media.title.media') }}
</h1>
<ol class="breadcrumb">
<li><a href="{{ route('dashboard.index') }}"><i class="fa fa-dashboard"></i> {{ trans('core::core.breadcrumb.home') }}</a></li>
<li><i class="fa fa-camera"></i> {{ trans('media::media.breadcrumb.media') }}</li>
</ol>
@stop
@section('styles')
<link href="{!! Module::asset('media:css/dropzone.css') !!}" rel="stylesheet" type="text/css" />
<style>
.dropzone {
border: 1px dashed #CCC;
min-height: 227px;
margin-bottom: 20px;
}
</style>
@stop
@section('content')
<div class="row">
<div class="col-md-12">
<form method="POST" class="dropzone">
{!! Form::token() !!}
</form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-body">
<table class="data-table table table-bordered table-hover jsFileList">
<thead>
<tr>
<th>{{ trans('core::core.table.thumbnail') }}</th>
<th>{{ trans('media::media.table.filename') }}</th>
<th>{{ trans('core::core.table.created at') }}</th>
<th data-sortable="false">{{ trans('core::core.table.actions') }}</th>
</tr>
</thead>
<tbody>
<?php if ($files): ?>
<?php foreach ($files as $file): ?>
<tr>
<td>
<?php if ($file->isImage()): ?>
<img src="{{ Imagy::getThumbnail($file->path, 'smallThumb') }}" alt=""/>
<?php else: ?>
<i class="fa {{ FileHelper::getFaIcon($file->media_type) }}" style="font-size: 20px;"></i>
<?php endif; ?>
</td>
<td>
<a href="{{ route('admin.media.media.edit', [$file->id]) }}">
{{ $file->filename }}
</a>
</td>
<td>
<a href="{{ route('admin.media.media.edit', [$file->id]) }}">
{{ $file->created_at }}
</a>
</td>
<td>
<div class="btn-group">
<a href="{{ route('admin.media.media.edit', [$file->id]) }}" class="btn btn-default btn-flat"><i class="fa fa-pencil"></i></a>
<button class="btn btn-danger btn-flat" data-toggle="modal" data-target="#modal-delete-confirmation" data-action-target="{{ route('admin.media.media.destroy', [$file->id]) }}"><i class="fa fa-trash"></i></button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
<tfoot>
<tr>
<th>{{ trans('core::core.table.thumbnail') }}</th>
<th>{{ trans('media::media.table.filename') }}</th>
<th>{{ trans('core::core.table.created at') }}</th>
<th>{{ trans('core::core.table.actions') }}</th>
</tr>
</tfoot>
</table>
<!-- /.box-body -->
</div>
</div>
</div>
</div>
@include('core::partials.delete-modal')
@stop
@section('scripts')
<script src="{!! Module::asset('media:js/dropzone.js') !!}"></script>
<?php $config = config('asgard.media.config'); ?>
<script>
var maxFilesize = '<?php echo $config['max-file-size'] ?>',
acceptedFiles = '<?php echo $config['allowed-types'] ?>';
</script>
<script src="{!! Module::asset('media:js/init-dropzone.js') !!}"></script>
<?php $locale = App::getLocale(); ?>
<script type="text/javascript">
$(function () {
$('.data-table').dataTable({
"paginate": true,
"lengthChange": true,
"filter": true,
"sort": true,
"info": true,
"autoWidth": true,
"order": [[ 0, "desc" ]],
"language": {
"url": '<?php echo Module::asset("core:js/vendor/datatables/{$locale}.json") ?>'
}
});
});
</script>
@stop
<?php $altAttribute = isset($file->translate($lang)->alt_attribute) ? $file->translate($lang)->alt_attribute : '' ?>
<div class='form-group{{ $errors->has("{$lang}[alt_attribute]") ? ' has-error' : '' }}'>
{!! Form::label("{$lang}[alt_attribute]", trans('media::media.form.alt_attribute')) !!}
{!! Form::text("{$lang}[alt_attribute]", old("{$lang}[alt_attribute]", $altAttribute), ['class' => 'form-control', 'placeholder' => trans('media::media.form.alt_attribute')]) !!}
{!! $errors->first("{$lang}[alt_attribute]", '<span class="help-block">:message</span>') !!}
</div>
<?php $description = isset($file->translate($lang)->description) ? $file->translate($lang)->description : '' ?>
<div class='form-group{{ $errors->has("{$lang}[description]") ? ' has-error' : '' }}'>
{!! Form::label("{$lang}[description]", trans('media::media.form.description')) !!}
{!! Form::textarea("{$lang}[description]", old("{$lang}[description]", $description), ['class' => 'form-control', 'placeholder' => trans('media::media.form.description')]) !!}
{!! $errors->first("{$lang}[description]", '<span class="help-block">:message</span>') !!}
</div>
<?php $keywords = isset($file->translate($lang)->keywords) ? $file->translate($lang)->keywords : '' ?>
<div class='form-group{{ $errors->has("{$lang}[keywords]") ? ' has-error' : '' }}'>
{!! Form::label("{$lang}[keywords]", trans('media::media.form.keywords')) !!}
{!! Form::text("{$lang}[keywords]", old("{$lang}[keywords]", $keywords), ['class' => 'form-control', 'placeholder' => trans('media::media.form.keywords')]) !!}
{!! $errors->first("{$lang}[keywords]", '<span class="help-block">:message</span>') !!}
</div>
<?php
namespace Modules\Media\Services;
use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Modules\Media\Entities\File;
use Modules\Media\Jobs\CreateThumbnails;
use Modules\Media\Repositories\FileRepository;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class FileService
{
use DispatchesJobs;
/**
* @var FileRepository
*/
private $file;
/**
* @var Factory
*/
private $filesystem;
public function __construct(FileRepository $file, Factory $filesystem)
{
$this->file = $file;
$this->filesystem = $filesystem;
}
/**
* @param UploadedFile $file
* @return mixed
*/
public function store(UploadedFile $file)
{
$savedFile = $this->file->createFromFile($file);
$path = $this->getDestinationPath($savedFile->getOriginal('path'));
$stream = fopen($file->getRealPath(), 'r+');
$this->filesystem->disk($this->getConfiguredFilesystem())->writeStream($path, $stream, [
'visibility' => 'public',
'mimetype' => $savedFile->mimetype,
]);
$this->createThumbnails($savedFile);
return $savedFile;
}
/**
* Create the necessary thumbnails for the given file
* @param $savedFile
*/
private function createThumbnails(File $savedFile)
{
$this->dispatch(new CreateThumbnails($savedFile->path));
}
/**
* @param string $path
* @return string
*/
private function getDestinationPath($path)
{
if ($this->getConfiguredFilesystem() === 'local') {
return basename(public_path()) . $path;
}
return $path;
}
/**
* @return string
*/
private function getConfiguredFilesystem()
{
return config('asgard.media.config.filesystem');
}
}
<?php
namespace Modules\Media\Sidebar;
use Maatwebsite\Sidebar\Group;
use Maatwebsite\Sidebar\Item;
use Maatwebsite\Sidebar\Menu;
use Modules\User\Contracts\Authentication;
class SidebarExtender implements \Maatwebsite\Sidebar\SidebarExtender
{
/**
* @var Authentication
*/
protected $auth;
/**
* @param Authentication $auth
*
* @internal param Guard $guard
*/
public function __construct(Authentication $auth)
{
$this->auth = $auth;
}
/**
* @param Menu $menu
*
* @return Menu
*/
public function extendWith(Menu $menu)
{
$menu->group(trans('core::sidebar.content'), function (Group $group) {
$group->item(trans('media::media.title.media'), function (Item $item) {
$item->weight(2);
$item->icon('fa fa-camera');
$item->route('admin.media.media.index');
$item->authorize(
$this->auth->hasAccess('media.medias.index')
);
});
});
return $menu;
}
}
<?php
namespace Modules\Media\Support\Traits;
use Modules\Media\Entities\File;
trait MediaRelation
{
/**
* Make the Many To Many Morph To Relation
* @return object
*/
public function files()
{
return $this->morphToMany(File::class, 'imageable', 'media__imageables')->withPivot('zone', 'id')->withTimestamps()->orderBy('order');
}
/**
* Make the Many to Many Morph to Relation with specific zone
* @param string $zone
* @return object
*/
public function filesByZone($zone)
{
return $this->morphToMany(File::class, 'imageable', 'media__imageables')
->withPivot('zone', 'id')
->wherePivot('zone', '=', $zone)
->withTimestamps()
->orderBy('order');
}
}
<?php
namespace Modules\Media\Tests;
use Modules\Media\UrlResolvers\BaseUrlResolver;
class BaseUrlResolverTest extends MediaTestCase
{
/** @test */
public function it_returns_correct_local_uri()
{
config()->set('asgard.media.config.filesystem', 'local');
$resolver = new BaseUrlResolver();
$resolvedPath = $resolver->resolve('/assets/media/my_image.png');
$this->assertEquals(config('app.url') . '/assets/media/my_image.png', $resolvedPath);
}
/** @test */
public function it_returns_correct_aws_s3_uri()
{
config()->set('asgard.media.config.filesystem', 's3');
config()->set('filesystems.disks.s3.bucket', 'testing-bucket');
config()->set('filesystems.disks.s3.region', 'eu-west-1');
$resolver = new BaseUrlResolver();
$resolvedPath = $resolver->resolve('/assets/media/my_image.png');
$this->assertEquals('https://s3-eu-west-1.amazonaws.com/testing-bucket/assets/media/my_image.png', $resolvedPath);
}
}
<?php
namespace Modules\Media\Tests;
use Modules\Core\Tests\BaseTestCase;
use Modules\Media\Helpers\FileHelper;
class FileHelperTest extends BaseTestCase
{
/** @test */
public function it_should_return_slugged_name_with_extension()
{
$expected = 'file-name.png';
$name = FileHelper::slug('File Name.png');
$this->assertEquals($expected, $name);
}
/** @test */
public function it_should_return_slugged_name_when_uppercase_extension_provided()
{
$expected = 'file-name.png';
$name = FileHelper::slug('File Name.PNG');
$this->assertEquals($expected, $name);
}
/** @test */
public function it_should_get_the_first_part_of_mimetype()
{
$this->assertEquals('image', FileHelper::getTypeByMimetype('image/png'));
$this->assertEquals('video', FileHelper::getTypeByMimetype('video/png'));
$this->assertEquals('document', FileHelper::getTypeByMimetype('document/png'));
}
/** @test */
public function it_gets_the_correct_icon_class_for_type()
{
$this->assertEquals('fa-file-video-o', FileHelper::getFaIcon('video'));
$this->assertEquals('fa-file-audio-o', FileHelper::getFaIcon('audio'));
$this->assertEquals('fa-file', FileHelper::getFaIcon('file'));
$this->assertEquals('fa-file', FileHelper::getFaIcon('random'));
}
}
<?php
namespace Modules\Media\Tests;
use Mockery;
use Modules\Media\Entities\File;
use Modules\Media\Repositories\FileRepository;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class FileRepositoryTest extends MediaTestCase
{
/**
* @var FileRepository
*/
private $file;
public function setUp()
{
parent::setUp();
$this->resetDatabase();
$this->file = app(FileRepository::class);
}
/** @test */
public function it_can_update_file_info()
{
$file = $this->createFile();
$this->file->update($file, [
'en' => [
'description' => 'My description',
'alt_attribute' => 'My alt attribute',
'keywords' => 'keyword1, keyword2',
],
]);
$file = $this->file->find(1);
$this->assertEquals('My description', $file->description);
$this->assertEquals('My alt attribute', $file->alt_attribute);
$this->assertEquals('keyword1, keyword2', $file->keywords);
}
/** @test */
public function it_can_create_file_from_uploadedfile()
{
$uploadedFile = Mockery::mock(UploadedFile::class);
$fileInfo = Mockery::mock(SplFileInfo::class);
$fileInfo->shouldReceive('getSize')
->andReturn(1024)
->once();
$uploadedFile->shouldReceive('getClientOriginalName')
->andReturn('my-file.jpg')
->once();
$uploadedFile->shouldReceive('getClientMimeType')
->andReturn('image/jpg')
->once();
$uploadedFile->shouldReceive('getFileInfo')
->andReturn($fileInfo)
->once();
$this->file->createFromFile($uploadedFile);
$file = $this->file->find(1);
$this->assertCount(1, $this->file->all());
$this->assertEquals('my-file.jpg', $file->filename);
$this->assertEquals('jpg', $file->extension);
$this->assertEquals('image/jpg', $file->mimetype);
$this->assertEquals('1024', $file->filesize);
}
/** @test */
public function it_can_increment_file_name_version_if_name_exists()
{
$this->createFile('my-file.jpg');
$uploadedFile = Mockery::mock(UploadedFile::class);
$fileInfo = Mockery::mock(SplFileInfo::class);
$fileInfo->shouldReceive('getSize')
->andReturn(1024)
->once();
$uploadedFile->shouldReceive('getClientOriginalName')
->andReturn('my-file.jpg')
->once();
$uploadedFile->shouldReceive('getClientMimeType')
->andReturn('image/jpg')
->once();
$uploadedFile->shouldReceive('getFileInfo')
->andReturn($fileInfo)
->once();
$this->file->createFromFile($uploadedFile);
$file = $this->file->find(2);
$this->assertEquals('my-file_1.jpg', $file->filename);
}
/** @test */
public function it_can_delete_a_file()
{
$file = $this->createFile();
$this->file->destroy($file);
$this->assertCount(0, $this->file->all());
}
private function createFile($fileName = 'random/name.jpg')
{
return File::create([
'filename' => $fileName,
'path' => config('asgard.media.config.files-path') . $fileName,
'extension' => substr(strrchr($fileName, "."), 1),
'mimetype' => 'image/jpg',
'filesize' => '1024',
'folder_id' => 0,
]);
}
private function resetDatabase()
{
// Makes sure the migrations table is created
$this->artisan('migrate', [
'--database' => 'sqlite',
]);
// We empty all tables
$this->artisan('migrate:reset', [
'--database' => 'sqlite',
]);
// Migrate
$this->artisan('migrate', [
'--database' => 'sqlite',
]);
$this->artisan('migrate', [
'--database' => 'sqlite',
'--path' => 'Modules/Tag/Database/Migrations',
]);
}
}
<?php
namespace Modules\Media\Tests;
use Modules\Media\Entities\File;
use Modules\Media\Repositories\FileRepository;
use Modules\Media\ValueObjects\MediaPath;
class FileTest extends MediaTestCase
{
/**
* @var FileRepository
*/
private $file;
public function setUp()
{
parent::setUp();
$this->resetDatabase();
$this->file = app(FileRepository::class);
}
/** @test */
public function it_creates_a_file()
{
$this->createFile('my/file/name.jpg');
$this->assertCount(1, $this->file->all());
}
/** @test */
public function it_should_return_media_path_value_object_on_path_attribtue()
{
$file = $this->createFile('my/file/name.jpg');
$this->assertInstanceOf(MediaPath::class, $file->path);
}
/** @test */
public function it_should_cast_the_path_value_object_to_string()
{
$file = $this->createFile('my/file/name.jpg');
$this->assertEquals('http://localhost/my/file/name.jpg', $file->path_string);
}
/** @test */
public function it_should_guess_the_media_type_of_object()
{
$file = $this->createFile('my/file/name.jpg');
$this->assertEquals('image', $file->media_type);
}
/** @test */
public function it_can_check_if_file_is_an_image()
{
$this->assertTrue($this->createFile('my/file/name.jpg')->isImage());
$this->assertTrue($this->createFile('my/file/name.png')->isImage());
$this->assertTrue($this->createFile('my/file/name.jpeg')->isImage());
$this->assertTrue($this->createFile('my/file/name.gif')->isImage());
$this->assertFalse($this->createFile('my/file/name.pdf')->isImage());
$this->assertFalse($this->createFile('my/file/name.doc')->isImage());
}
/** @test */
public function it_can_get_the_thumbnail()
{
$file = $this->createFile('my/file/name.jpg');
$this->assertEquals('http://localhost/name_smallThumb.jpg', $file->getThumbnail('smallThumb'));
}
/** @test */
public function it_wont_get_thumbnail_of_non_image_file()
{
$file = $this->createFile('my/file/name.pdf');
$this->assertFalse($file->getThumbnail('smallThumb'));
}
private function createFile($fileName = 'random/name.jpg')
{
return File::create([
'filename' => $fileName,
'path' => config('asgard.media.config.files-path') . $fileName,
'extension' => substr(strrchr($fileName, "."), 1),
'mimetype' => 'image/jpg',
'filesize' => '1024',
'folder_id' => 0,
]);
}
private function resetDatabase()
{
// Makes sure the migrations table is created
$this->artisan('migrate', [
'--database' => 'sqlite',
]);
// We empty all tables
$this->artisan('migrate:reset', [
'--database' => 'sqlite',
]);
// Migrate
$this->artisan('migrate', [
'--database' => 'sqlite',
]);
}
}
<?php
namespace Modules\Media\Tests;
use Illuminate\Support\Facades\App;
use Modules\Media\Image\Imagy;
use Modules\Media\Image\Intervention\InterventionFactory;
use Modules\Media\Image\ThumbnailManager;
use Modules\Media\ValueObjects\MediaPath;
class ImagyTest extends MediaTestCase
{
/**
* @var Imagy
*/
protected $imagy;
/**
* @var \Illuminate\Filesystem\Filesystem
*/
protected $finder;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
protected $config;
/**
* @var string
*/
protected $mediaPath;
private $testbenchPublicPath;
/**
*
*/
public function setUp()
{
parent::setUp();
$this->config = App::make('Illuminate\Contracts\Config\Repository');
$module = App::make('modules');
$this->finder = App::make('Illuminate\Filesystem\Filesystem');
$this->imagy = new Imagy(new InterventionFactory(), app(ThumbnailManager::class), $this->config);
$this->testbenchPublicPath = __DIR__ . '/../vendor/orchestra/testbench/fixture/public/';
$this->mediaPath = __DIR__ . '/Fixtures/';
$this->finder->copy("{$this->mediaPath}google-map.png", "{$this->testbenchPublicPath}google-map.png");
}
public function tearDown()
{
$this->finder->delete("{$this->testbenchPublicPath}google-map.png");
$this->finder->delete("{$this->testbenchPublicPath}google-map_smallThumb.png");
}
public function it_should_create_a_file()
{
$path = new MediaPath("/google-map.png");
$this->imagy->get($path, 'smallThumb', true);
$this->assertTrue($this->finder->isFile("{$this->testbenchPublicPath}google-map_smallThumb.png"));
}
/** @test */
public function it_should_not_create_thumbs_for_pdf_files()
{
$this->imagy->get("{$this->mediaPath}test-pdf.pdf", 'smallThumb', true);
$this->assertFalse($this->finder->isFile(public_path() . "{$this->mediaPath}test-pdf_smallThumb.png"));
}
/** @test */
public function it_should_return_thumbnail_path()
{
$path = $this->imagy->getThumbnail("{$this->mediaPath}google-map.png", 'smallThumb');
$expected = config('app.url') . DIRECTORY_SEPARATOR . config('asgard.media.config.files-path') . 'google-map_smallThumb.png';
$this->assertEquals($expected, $path);
}
/** @test */
public function it_should_return_same_path_for_non_images()
{
$path = $this->imagy->getThumbnail("{$this->mediaPath}test-pdf.pdf", 'smallThumb');
$expected = "{$this->mediaPath}test-pdf.pdf";
$this->assertEquals($expected, $path);
}
/** @test */
public function it_should_detect_an_image()
{
$jpg = $this->imagy->isImage('image.jpg');
$png = $this->imagy->isImage('image.png');
$pdf = $this->imagy->isImage('pdf.pdf');
$this->assertTrue($jpg);
$this->assertTrue($png);
$this->assertFalse($pdf);
}
}
<?php
namespace Modules\Media\Tests;
use Modules\Media\ValueObjects\MediaPath;
class MediaPathTest extends \PHPUnit_Framework_TestCase
{
/** @test */
public function it_can_instantiate_value_object()
{
$path = new MediaPath('some/path.jpg');
$this->assertInstanceOf(MediaPath::class, $path);
}
/** @test */
public function it_only_acepts_a_string_as_argument()
{
$this->setExpectedException(\InvalidArgumentException::class);
new MediaPath(['something']);
}
/** @test */
public function it_can_get_the_url()
{
$path = new MediaPath('some/path.jpg');
$this->assertEquals('http://localhost/some/path.jpg', $path->getUrl());
}
/** @test */
public function it_can_get_the_relative_url()
{
$path = new MediaPath('some/path.jpg');
$this->assertEquals('some/path.jpg', $path->getRelativeUrl());
}
/** @test */
public function it_casts_media_path_to_string_using_url_method()
{
$path = new MediaPath('some/path.jpg');
$this->assertEquals('http://localhost/some/path.jpg', (string) $path);
$this->assertNotEquals('some/path.jpg', (string) $path);
}
}
<?php
namespace Modules\Media\Tests;
use Collective\Html\FormFacade;
use Collective\Html\HtmlFacade;
use Collective\Html\HtmlServiceProvider;
use Illuminate\Support\Facades\Validator;
use Illuminate\Translation\TranslationServiceProvider;
use Intervention\Image\ImageServiceProvider;
use Maatwebsite\Sidebar\SidebarServiceProvider;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
use Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider;
use Modules\Core\Providers\CoreServiceProvider;
use Modules\Media\Providers\MediaServiceProvider;
use Modules\Tag\Providers\TagServiceProvider;
use Nwidart\Modules\LaravelModulesServiceProvider;
use Nwidart\Modules\Providers\BootstrapServiceProvider;
use Orchestra\Testbench\TestCase;
abstract class MediaTestCase extends TestCase
{
protected function getPackageProviders($app)
{
return [
TranslationServiceProvider::class,
LaravelModulesServiceProvider::class,
BootstrapServiceProvider::class,
CoreServiceProvider::class,
TagServiceProvider::class,
\Modules\Media\Image\ImageServiceProvider::class,
MediaServiceProvider::class,
ImageServiceProvider::class,
LaravelLocalizationServiceProvider::class,
HtmlServiceProvider::class,
SidebarServiceProvider::class,
];
}
protected function getPackageAliases($app)
{
return [
'LaravelLocalization' => LaravelLocalization::class,
'Validator' => Validator::class,
'Form' => FormFacade::class,
'Html' => HtmlFacade::class,
];
}
protected function getEnvironmentSetUp($app)
{
$app['path.base'] = __DIR__ . '/..';
$app['config']->set('asgard.media.config', ['filesystem' => 'local']);
$app['config']->set('modules', [
'namespace' => 'Modules',
]);
$app['config']->set('modules.paths.modules', realpath(__DIR__ . '/../Modules'));
$app['config']->set('database.default', 'sqlite');
$app['config']->set('database.connections.sqlite', array(
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
));
$app['config']->set('translatable.locales', ['en', 'fr']);
}
}
<?php
namespace Modules\Tests;
use Modules\Media\Image\Thumbnail;
class ThumbnailTest extends \PHPUnit_Framework_TestCase
{
/** @test */
public function it_creates_thumbnail_class()
{
$thumbnail = Thumbnail::make($this->getBlogThumbnailConfig());
$this->assertInstanceOf('Modules\Media\Image\Thumbnail', $thumbnail);
}
/** @test */
public function it_gets_thumbnail_name()
{
$thumbnail = Thumbnail::make($this->getBlogThumbnailConfig());
$this->assertEquals('blogThumb', $thumbnail->name());
}
/** @test */
public function it_gets_thumbnail_filters()
{
$thumbnail = Thumbnail::make($this->getBlogThumbnailConfig());
$expected = [
'resize' => [
'width' => 150,
'height' => 250,
],
'fit' => [
'width' => 550,
'height' => 650,
],
];
$this->assertEquals($expected, $thumbnail->filters());
}
/** @test */
public function it_gets_thumbnail_width()
{
$thumbnail = Thumbnail::make($this->getBlogThumbnailConfig());
$this->assertSame(150, $thumbnail->width());
}
/** @test */
public function it_gets_thumbnail_height()
{
$thumbnail = Thumbnail::make($this->getBlogThumbnailConfig());
$this->assertSame(250, $thumbnail->height());
}
/** @test */
public function it_gets_thumbnail_size()
{
$thumbnail = Thumbnail::make($this->getBlogThumbnailConfig());
$this->assertSame('150x250', $thumbnail->size());
}
/** @test */
public function it_gets_multiple_thumbnails()
{
$thumbnails = Thumbnail::makeMultiple($this->getMediaThumbnails());
$this->assertCount(2, $thumbnails);
$this->assertEquals('smallThumb', $thumbnails[0]->name());
$this->assertEquals('mediumThumb', $thumbnails[1]->name());
}
private function getBlogThumbnailConfig()
{
return [
'blogThumb' => [
'resize' => [
'width' => 150,
'height' => 250,
],
'fit' => [
'width' => 550,
'height' => 650,
],
],
];
}
private function getMediaThumbnails()
{
return [
'smallThumb' => [
'resize' => [
'width' => 50,
'height' => null,
'callback' => function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
},
],
],
'mediumThumb' => [
'resize' => [
'width' => 180,
'height' => null,
'callback' => function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
},
],
],
];
}
}
<?php
namespace Modules\Tests;
use Modules\Media\Image\ThumbnailManager;
use Modules\Media\Tests\MediaTestCase;
class ThumbnailsManagerTest extends MediaTestCase
{
/**
* @var ThumbnailManager
*/
private $thumbnailManager;
public function setUp()
{
parent::setUp();
$this->thumbnailManager = app(ThumbnailManager::class);
}
/** @test */
public function it_initialises_empty_array()
{
$this->assertCount(2, $this->thumbnailManager->all());
}
/** @test */
public function it_can_add_a_thumbnail()
{
$this->thumbnailManager->registerThumbnail('coolThumb', []);
$this->assertCount(3, $this->thumbnailManager->all());
}
/** @test */
public function it_can_find_a_thumbnail()
{
$expected = [
'resize' => [
'width' => 180,
'height' => null,
'callback' => function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
},
],
];
$this->assertEquals($expected, $this->thumbnailManager->find('mediumThumb'));
}
/** @test */
public function it_returns_empty_array_if_no_thumbnail_found()
{
$this->assertEquals([], $this->thumbnailManager->find('someRandomThumb'));
}
}
<?php
namespace Modules\Media\UrlResolvers;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
class AwsS3UrlResolver
{
/**
* @param AwsS3Adapter $adapter
* @param string $path
* @return string
*/
public function resolve(AwsS3Adapter $adapter, $path)
{
return $adapter->getClient()->getObjectUrl(config('filesystems.disks.s3.bucket'), ltrim($path, '/'));
}
}
<?php
namespace Modules\Media\UrlResolvers;
use Illuminate\Contracts\Filesystem\Factory;
use League\Flysystem\Adapter\Ftp;
use League\Flysystem\Adapter\Local;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
class BaseUrlResolver
{
/**
* @var array
*/
private $resolvers = [];
public function __construct()
{
$this->resolvers = [
Local::class => new LocalUrlResolver(),
AwsS3Adapter::class => new AwsS3UrlResolver(),
Ftp::class => new FtpUrlResolver(),
];
}
/**
* Resolve the given path based on the set filesystem
* @param string $path
* @return string
*/
public function resolve($path)
{
$factory = app(Factory::class);
$adapter = $factory->disk($this->getConfiguredFilesystem())->getDriver()->getAdapter();
return $this->resolvers[get_class($adapter)]->resolve($adapter, $path);
}
/**
* @return string
*/
private function getConfiguredFilesystem()
{
return config('asgard.media.config.filesystem');
}
}
<?php
namespace Modules\Media\UrlResolvers;
use League\Flysystem\Adapter\Ftp;
class FtpUrlResolver
{
/**
* @param Ftp $adapter
* @param string $path
* @return string
*/
public function resolve(Ftp $adapter, $path)
{
return 'ftp://' . config('filesystems.disks.ftp.host') . $path;
}
}
<?php
namespace Modules\Media\UrlResolvers;
use League\Flysystem\Adapter\Local;
class LocalUrlResolver
{
/**
* @param Local $adapter
* @param string $path
* @return string
*/
public function resolve(Local $adapter, $path)
{
return asset($path);
}
}
<?php
namespace Modules\Media\Validators;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class MaxFolderSizeValidator
{
public function validateMaxSize($attribute, UploadedFile $value, $parameters)
{
$mediaPath = public_path(config('asgard.media.config.files-path'));
$folderSize = $this->getDirSize($mediaPath);
preg_match('/([0-9]+)/', $folderSize, $match);
return ($match[0] + $value->getSize()) < config('asgard.media.config.max-total-size');
}
/**
* Get the directory size
* @param string $directory
* @return int
*/
public function getDirSize($directory)
{
$size = 0;
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)) as $file) {
$size += $file->getSize();
}
return $size;
}
}
<?php
namespace Modules\Media\ValueObjects;
use Modules\Media\UrlResolvers\BaseUrlResolver;
class MediaPath
{
/**
* @var string
*/
private $path;
public function __construct($path)
{
if (! is_string($path)) {
throw new \InvalidArgumentException('The path must be a string');
}
$this->path = $path;
}
/**
* Get the URL depending on configured disk
* @return string
*/
public function getUrl()
{
return (new BaseUrlResolver())->resolve($this->path);
}
/**
* @return string
*/
public function getRelativeUrl()
{
return $this->path;
}
public function __toString()
{
try {
return $this->getUrl();
} catch (\Exception $e) {
return '';
}
}
}
url: https://github.com/AsgardCms/Media
versions:
"2.0@unreleased":
added:
- Laravel 5.2 compatibility
- New way to define thumbnails, no more via a config file but via a class.
- Adding a new resize canvas image manipulation
changed:
- Using new more flexible way of handle permissions via middleware
- Protecting api routes with api admin middleware
- Allow usage of multiple media partials on create views
- Cleaned up file partials, both create and edit views use the event system
- Allow <code>Imagy->getThumbnail()</code> to get the File entity directly
deleted:
- Removing laracasts/flash dependency
"1.25.1":
changed:
- Fixing setting default to empty array of getting the files of the form
"1.25.0":
changed:
- Use <code>$router</code> variable in routes file
"1.24.0":
changed:
- Add thumbnail helper into File model
"1.23.1":
changed:
- Set default argument to handle method on event handlers
"1.23.0":
changed:
- Removing the need of adding the events in <code>events.php</code>
"1.22.1":
changed:
- Fix thumbnail include
- Hide thumbnail dropdown on include partial if it's not via wysiwyg
"1.22.0":
added:
- Way to handle media on create views (multiple images and single image)
- FTP media file adapter
changed:
- Fixed bug in filename slug when file extension is uppercase
"1.21.0":
changed:
- Using the validator::extend instead of resolve.
- Renamed the <code>$thumbnail</code> variable in partial view of linking media
- Include data file attribute to non image files
"1.20.1":
changed:
- Re-releasing
"1.20.0":
added:
- Drag & drop on the multi file relation file
changed:
- A <code>thumbnail</code> option can be sent to the file partials to decide which thumbnail to use on display
- Added file path data attribute on the media grid popup
"1.19.0":
changed:
- Adding the publishing of the media main config file in the media module
"1.18.1":
changed:
- Fixing instantiation of the <code>RefreshThumbnailCommand</code>
"1.18.0":
changed:
- Queue closures in the <code>FileService</code> has ben extracted to a queued job
- The refresh thumbnail command now uses a queued job
"1.17.2":
added:
- Method to get the file path as a string using <code>$file->path_string</code>
"1.17.1":
changed:
- Fixed extension guessing
- Fixed file location for non images files
"1.17.0":
changed:
- Hiding the thumbnail display if file is not an image
- Fixing the filename property on the edit view
- Removed language files, they are now in the translation module
"1.16.0":
added:
- Adding the timesteamps on the morph files relation
- Adding support for local and s3 file storage!
changed:
- If file with already present filename, new unique filename will be generated instead of throwing error
"1.15.1":
changed:
- Including the Asgard js global definition
"1.15.0":
changed:
- Read the dropzone post URL from the Asgard global object
"1.14.2":
changed:
- Fixing the display of the max file size error message
"1.14.1":
changed:
- Fixed the include media pop up link to assets
"1.14.0":
changed:
- Use the new datatables way of marking column not sortable
- Use the single removal modal setup
"1.13.0":
changed:
- Allow multiple upload from media library fields on one page
- Allow media zone names to have multiple words separated with underscore
"1.12.0":
changed:
- The clic event on the <code>x</code> to unlink an image has been bound to a container to allow removal after dom generation
"1.11.0":
changed:
- Getting the correct field for the description input
- Naming the CkEditor route
"1.10.0":
added:
- Added Russian translations
"1.9.0":
added:
- Chinese Simplified language
changed:
- Setting default folder id to 0
- Fixed bug where thumbnails for images with uppercase extension wouldn't get created
"1.8.0":
added:
- Dutch and Portuguese language
"1.7.0":
added:
- Spanish translations
"1.6.0":
added:
- Added Brazilian Portuguese translations
"1.5.0":
changed:
- Added <code>$attributes</code> to validator resolver
"1.4.0":
changed:
- Use the manual route binding
"1.3.1":
changed:
- Fixing alt atribute display error
"1.3.0":
changed:
- Use the helper functions in controller
- Use manual route definition over the route resource shortcut
"1.2.0":
changed:
- Remove the <code>du</code> function usage for non *nix systems
"1.1.0":
changed:
- Adding table engine into migrations
"1.0.4":
changed:
- Change column ordering on index view
"1.0.3":
added:
- Using new sidebar extender class
removed:
- Old SidebarViewComposer
"1.0.2":
changed:
- Adding a changelog file
"1.0.1":
changed:
- Using tagged version of modules
"1.0.0":
changed:
- Initial release
{
"name": "asgardcms/media-module",
"type": "asgard-module",
"description": "Media module for AsgardCMS. Handles the media library.",
"keywords": [
"asgardcms",
"media",
"files",
"library",
"thumbnails",
"filters"
],
"license": "MIT",
"authors": [
{
"name": "Nicolas Widart",
"email": "info@asgardcms.com",
"role": "Developer"
}
],
"support": {
"email": "support@asgardcms.com",
"issues": "https://github.com/AsgardCms/Platform/issues",
"source": "https://github.com/AsgardCms/Media"
},
"require": {
"php": ">=5.5",
"composer/installers": "~1.0",
"intervention/image": "^2.3",
"asgardcms/core-module": "~2.0",
"asgardcms/tag-module": "^1.0@dev",
"guzzlehttp/psr7": "^1.3"
},
"require-dev": {
"phpunit/phpunit": "~4.0",
"orchestra/testbench": "3.3.*",
"league/flysystem-aws-s3-v3": "~1.0",
"mockery/mockery": "^0.9.5",
"phpro/grumphp": "^0.9.1",
"friendsofphp/php-cs-fixer": "^1.11",
"doctrine/dbal": "^2.5"
},
"autoload-dev": {
"psr-4": {
"Modules\\Media\\": ".",
"Modules\\": "Modules/"
}
},
"extra": {
"branch-alias": {
"dev-2.0": "2.0.x-dev"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
<?php
view()->composer(['media::admin.fields.new-*'], 'Modules\Media\Composers\Backend\PartialAssetComposer');
parameters:
git_dir: .
bin_dir: vendor/bin
stop_on_failure: true
tasks:
phpcsfixer:
config_file: .php_cs
composer:
file: ./composer.json
jsonlint:
ignore_patterns: []
{
"name": "Media",
"alias": "media",
"description": "A media library, used throughout the CMS.",
"keywords": [],
"version": "2.0.0",
"active": 1,
"order": 1,
"providers": [
"Modules\\Media\\Providers\\MediaServiceProvider",
"Modules\\Media\\Providers\\RouteServiceProvider",
"Modules\\Media\\Image\\ImageServiceProvider",
"Intervention\\Image\\ImageServiceProvider"
],
"aliases": {
"Image": "Intervention\\Image\\Facades\\Image",
"Imagy": "Modules\\Media\\Image\\Facade\\Imagy",
"FileHelper": "Modules\\Media\\Helpers\\FileHelper",
"MediaSingleDirective": "Modules\\Media\\Blade\\Facades\\MediaSingleDirective",
"MediaMultipleDirective": "Modules\\Media\\Blade\\Facades\\MediaMultipleDirective"
},
"files": [
"composers.php"
]
}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory>vendor/</directory>
<directory>Modules/</directory>
</blacklist>
</filter>
<logging>
<log type="tap" target="build/report.tap"/>
<log type="junit" target="build/report.junit.xml"/>
<log type="coverage-html" target="build/coverage" charset="UTF-8" yui="true" highlight="true"/>
<log type="coverage-text" target="build/coverage.txt"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
</phpunit>
# Media Module
[![Latest Version](https://img.shields.io/github/release/asgardcms/media.svg?style=flat-square)](https://github.com/asgardcms/media/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Quality Score](https://img.shields.io/scrutinizer/g/asgardcms/media.svg?style=flat-square)](https://scrutinizer-ci.com/g/asgardcms/media)
[![SensioLabs Insight](https://img.shields.io/sensiolabs/i/648270bf-8b9c-4994-b006-a948fef307b2.svg)](https://insight.sensiolabs.com/projects/648270bf-8b9c-4994-b006-a948fef307b2)
[![CodeClimate](https://img.shields.io/codeclimate/github/AsgardCms/Media.svg)](https://codeclimate.com/github/AsgardCms/Media)
[![Total Downloads](https://img.shields.io/packagist/dd/asgardcms/media-module.svg?style=flat-square)](https://packagist.org/packages/asgardcms/media-module)
[![Total Downloads](https://img.shields.io/packagist/dm/asgardcms/media-module.svg?style=flat-square)](https://packagist.org/packages/asgardcms/media-module)
[![Total Downloads](https://img.shields.io/packagist/dt/asgardcms/media-module.svg?style=flat-square)](https://packagist.org/packages/asgardcms/media-module)
[![Slack](http://slack.asgardcms.com/badge.svg)](http://slack.asgardcms.com/)
| Branch | Travis-ci |
| ---------------- | --------------- |
| master | [![Build Status](https://travis-ci.org/AsgardCms/Media.svg?branch=master)](https://travis-ci.org/AsgardCms/Media) |
## Resources
- [Contribute to AsgardCMS](https://asgardcms.com/en/docs/getting-started/contributing)
- [License](LICENSE.md)
- [Documentation](http://asgardcms.com/docs/media-module/thumbnails)
## Info
All AsgardCMS modules respect [Semantic Versioning](http://semver.org/).
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment