Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
E
elphel-web-393
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Elphel
elphel-web-393
Commits
5f4bb7de
Commit
5f4bb7de
authored
Nov 30, 2016
by
Oleg Dzhimiev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update software
parent
8a1d6ea4
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
3476 additions
and
1 deletion
+3476
-1
Makefile
Makefile
+1
-1
Makefile
src/update/Makefile
+29
-0
UploadHandler.php
src/update/fileupload/UploadHandler.php
+1393
-0
jquery.fileupload.css
src/update/fileupload/jquery.fileupload.css
+37
-0
jquery.fileupload.js
src/update/fileupload/jquery.fileupload.js
+1482
-0
jquery.iframe-transport.js
src/update/fileupload/jquery.iframe-transport.js
+217
-0
style.css
src/update/fileupload/style.css
+15
-0
update_nand.php
src/update/update_nand.php
+169
-0
update_software.html
src/update/update_software.html
+45
-0
update_software.js
src/update/update_software.js
+71
-0
update_software.php
src/update/update_software.php
+17
-0
No files found.
Makefile
View file @
5f4bb7de
# Runs 'make', 'make install', and 'make clean' in specified subdirectories
# Runs 'make', 'make install', and 'make clean' in specified subdirectories
SUBDIRS
:=
src/php_top src/python_tests src/debugfs-webgui src/jp4-canvas
# src1
SUBDIRS
:=
src/php_top src/python_tests src/debugfs-webgui src/jp4-canvas
src/update
# src1
INSTALLDIRS
=
$
(
SUBDIRS:%
=
install-%
)
INSTALLDIRS
=
$
(
SUBDIRS:%
=
install-%
)
CLEANDIRS
=
$
(
SUBDIRS:%
=
clean-%
)
CLEANDIRS
=
$
(
SUBDIRS:%
=
clean-%
)
...
...
src/update/Makefile
0 → 100644
View file @
5f4bb7de
DOCUMENTROOT
=
$(DESTDIR)
/www/pages
OWN
=
-o
root
-g
root
INSTDOCS
=
0644
INSTALL
=
install
DOCS
=
update_nand.php
\
update_software.html
\
update_software.js
\
update_software.php
LIB_DIR
=
fileupload
LIBS
=
$(LIB_DIR)
/jquery.fileupload.css
\
$(LIB_DIR)
/jquery.fileupload.js
\
$(LIB_DIR)
/jquery.iframe-transport.js
\
$(LIB_DIR)
/style.css
\
$(LIB_DIR)
/UploadHandler.php
all
:
@
echo
"make all in src"
install
:
@
echo
"make install in src"
$(INSTALL)
$(OWN)
-d
$(DOCUMENTROOT)
$(DOCUMENTROOT)
/js
$(DOCUMENTROOT)
/js/
$(LIB_DIR)
$(INSTALL)
$(OWN)
-m
$(INSTDOCS)
$(DOCS)
$(DOCUMENTROOT)
$(INSTALL)
$(OWN)
-m
$(INSTDOCS)
$(LIBS)
$(DOCUMENTROOT)
/js/
$(LIB_DIR)
clean
:
@
echo
"make clean in src"
src/update/fileupload/UploadHandler.php
0 → 100755
View file @
5f4bb7de
<?php
/*
* jQuery File Upload Plugin PHP Class
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
class
UploadHandler
{
protected
$options
;
// PHP File Upload error message codes:
// http://php.net/manual/en/features.file-upload.errors.php
protected
$error_messages
=
array
(
1
=>
'The uploaded file exceeds the upload_max_filesize directive in php.ini'
,
2
=>
'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'
,
3
=>
'The uploaded file was only partially uploaded'
,
4
=>
'No file was uploaded'
,
6
=>
'Missing a temporary folder'
,
7
=>
'Failed to write file to disk'
,
8
=>
'A PHP extension stopped the file upload'
,
'post_max_size'
=>
'The uploaded file exceeds the post_max_size directive in php.ini'
,
'max_file_size'
=>
'File is too big'
,
'min_file_size'
=>
'File is too small'
,
'accept_file_types'
=>
'Filetype not allowed'
,
'max_number_of_files'
=>
'Maximum number of files exceeded'
,
'max_width'
=>
'Image exceeds maximum width'
,
'min_width'
=>
'Image requires a minimum width'
,
'max_height'
=>
'Image exceeds maximum height'
,
'min_height'
=>
'Image requires a minimum height'
,
'abort'
=>
'File upload aborted'
,
'image_resize'
=>
'Failed to resize image'
);
protected
$image_objects
=
array
();
public
function
__construct
(
$options
=
null
,
$initialize
=
true
,
$error_messages
=
null
)
{
$this
->
response
=
array
();
$this
->
options
=
array
(
'script_url'
=>
$this
->
get_full_url
()
.
'/'
.
$this
->
basename
(
$this
->
get_server_var
(
'SCRIPT_NAME'
)),
'upload_dir'
=>
dirname
(
$this
->
get_server_var
(
'SCRIPT_FILENAME'
))
.
'/var/update/'
,
'upload_url'
=>
$this
->
get_full_url
()
.
'/var/update/'
,
'input_stream'
=>
'php://input'
,
'user_dirs'
=>
false
,
'mkdir_mode'
=>
0755
,
'param_name'
=>
'files'
,
// Set the following option to 'POST', if your server does not support
// DELETE requests. This is a parameter sent to the client:
'delete_type'
=>
'DELETE'
,
'access_control_allow_origin'
=>
'*'
,
'access_control_allow_credentials'
=>
false
,
'access_control_allow_methods'
=>
array
(
'OPTIONS'
,
'HEAD'
,
'GET'
,
'POST'
,
'PUT'
,
'PATCH'
,
'DELETE'
),
'access_control_allow_headers'
=>
array
(
'Content-Type'
,
'Content-Range'
,
'Content-Disposition'
),
// By default, allow redirects to the referer protocol+host:
'redirect_allow_target'
=>
'/^'
.
preg_quote
(
parse_url
(
$this
->
get_server_var
(
'HTTP_REFERER'
),
PHP_URL_SCHEME
)
.
'://'
.
parse_url
(
$this
->
get_server_var
(
'HTTP_REFERER'
),
PHP_URL_HOST
)
.
'/'
,
// Trailing slash to not match subdomains by mistake
'/'
// preg_quote delimiter param
)
.
'/'
,
// Enable to provide file downloads via GET requests to the PHP script:
// 1. Set to 1 to download files via readfile method through PHP
// 2. Set to 2 to send a X-Sendfile header for lighttpd/Apache
// 3. Set to 3 to send a X-Accel-Redirect header for nginx
// If set to 2 or 3, adjust the upload_url option to the base path of
// the redirect parameter, e.g. '/files/'.
'download_via_php'
=>
false
,
// Read files in chunks to avoid memory limits when download_via_php
// is enabled, set to 0 to disable chunked reading of files:
'readfile_chunk_size'
=>
10
*
1024
*
1024
,
// 10 MiB
// Defines which files can be displayed inline when downloaded:
'inline_file_types'
=>
'/\.(gif|jpe?g|png)$/i'
,
// Defines which files (based on their names) are accepted for upload:
'accept_file_types'
=>
'/.+$/i'
,
// The php.ini settings upload_max_filesize and post_max_size
// take precedence over the following max_file_size setting:
'max_file_size'
=>
null
,
'min_file_size'
=>
1
,
// The maximum number of files for the upload directory:
'max_number_of_files'
=>
null
,
// Defines which files are handled as image files:
'image_file_types'
=>
'/\.(gif|jpe?g|png)$/i'
,
// Use exif_imagetype on all files to correct file extensions:
'correct_image_extensions'
=>
false
,
// Image resolution restrictions:
'max_width'
=>
null
,
'max_height'
=>
null
,
'min_width'
=>
1
,
'min_height'
=>
1
,
// Set the following option to false to enable resumable uploads:
'discard_aborted_uploads'
=>
true
,
// Set to 0 to use the GD library to scale and orient images,
// set to 1 to use imagick (if installed, falls back to GD),
// set to 2 to use the ImageMagick convert binary directly:
'image_library'
=>
1
,
// Uncomment the following to define an array of resource limits
// for imagick:
/*
'imagick_resource_limits' => array(
imagick::RESOURCETYPE_MAP => 32,
imagick::RESOURCETYPE_MEMORY => 32
),
*/
// Command or path for to the ImageMagick convert binary:
'convert_bin'
=>
'convert'
,
// Uncomment the following to add parameters in front of each
// ImageMagick convert call (the limit constraints seem only
// to have an effect if put in front):
/*
'convert_params' => '-limit memory 32MiB -limit map 32MiB',
*/
// Command or path for to the ImageMagick identify binary:
'identify_bin'
=>
'identify'
,
'image_versions'
=>
array
(
// The empty image version key defines options for the original image:
''
=>
array
(
// Automatically rotate images based on EXIF meta data:
'auto_orient'
=>
true
),
// Uncomment the following to create medium sized images:
/*
'medium' => array(
'max_width' => 800,
'max_height' => 600
),
*/
'thumbnail'
=>
array
(
// Uncomment the following to use a defined directory for the thumbnails
// instead of a subdirectory based on the version identifier.
// Make sure that this directory doesn't allow execution of files if you
// don't pose any restrictions on the type of uploaded files, e.g. by
// copying the .htaccess file from the files directory for Apache:
//'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/thumb/',
//'upload_url' => $this->get_full_url().'/thumb/',
// Uncomment the following to force the max
// dimensions and e.g. create square thumbnails:
//'crop' => true,
'max_width'
=>
80
,
'max_height'
=>
80
)
),
'print_response'
=>
true
);
if
(
$options
)
{
$this
->
options
=
$options
+
$this
->
options
;
}
if
(
$error_messages
)
{
$this
->
error_messages
=
$error_messages
+
$this
->
error_messages
;
}
if
(
$initialize
)
{
$this
->
initialize
();
}
}
protected
function
initialize
()
{
switch
(
$this
->
get_server_var
(
'REQUEST_METHOD'
))
{
case
'OPTIONS'
:
case
'HEAD'
:
$this
->
head
();
break
;
case
'GET'
:
$this
->
get
(
$this
->
options
[
'print_response'
]);
break
;
case
'PATCH'
:
case
'PUT'
:
case
'POST'
:
$this
->
post
(
$this
->
options
[
'print_response'
]);
break
;
case
'DELETE'
:
$this
->
delete
(
$this
->
options
[
'print_response'
]);
break
;
default
:
$this
->
header
(
'HTTP/1.1 405 Method Not Allowed'
);
}
}
protected
function
get_full_url
()
{
$https
=
!
empty
(
$_SERVER
[
'HTTPS'
])
&&
strcasecmp
(
$_SERVER
[
'HTTPS'
],
'on'
)
===
0
||
!
empty
(
$_SERVER
[
'HTTP_X_FORWARDED_PROTO'
])
&&
strcasecmp
(
$_SERVER
[
'HTTP_X_FORWARDED_PROTO'
],
'https'
)
===
0
;
return
(
$https
?
'https://'
:
'http://'
)
.
(
!
empty
(
$_SERVER
[
'REMOTE_USER'
])
?
$_SERVER
[
'REMOTE_USER'
]
.
'@'
:
''
)
.
(
isset
(
$_SERVER
[
'HTTP_HOST'
])
?
$_SERVER
[
'HTTP_HOST'
]
:
(
$_SERVER
[
'SERVER_NAME'
]
.
(
$https
&&
$_SERVER
[
'SERVER_PORT'
]
===
443
||
$_SERVER
[
'SERVER_PORT'
]
===
80
?
''
:
':'
.
$_SERVER
[
'SERVER_PORT'
])))
.
substr
(
$_SERVER
[
'SCRIPT_NAME'
],
0
,
strrpos
(
$_SERVER
[
'SCRIPT_NAME'
],
'/'
));
}
protected
function
get_user_id
()
{
@
session_start
();
return
session_id
();
}
protected
function
get_user_path
()
{
if
(
$this
->
options
[
'user_dirs'
])
{
return
$this
->
get_user_id
()
.
'/'
;
}
return
''
;
}
protected
function
get_upload_path
(
$file_name
=
null
,
$version
=
null
)
{
$file_name
=
$file_name
?
$file_name
:
''
;
if
(
empty
(
$version
))
{
$version_path
=
''
;
}
else
{
$version_dir
=
@
$this
->
options
[
'image_versions'
][
$version
][
'upload_dir'
];
if
(
$version_dir
)
{
return
$version_dir
.
$this
->
get_user_path
()
.
$file_name
;
}
$version_path
=
$version
.
'/'
;
}
return
$this
->
options
[
'upload_dir'
]
.
$this
->
get_user_path
()
.
$version_path
.
$file_name
;
}
protected
function
get_query_separator
(
$url
)
{
return
strpos
(
$url
,
'?'
)
===
false
?
'?'
:
'&'
;
}
protected
function
get_download_url
(
$file_name
,
$version
=
null
,
$direct
=
false
)
{
if
(
!
$direct
&&
$this
->
options
[
'download_via_php'
])
{
$url
=
$this
->
options
[
'script_url'
]
.
$this
->
get_query_separator
(
$this
->
options
[
'script_url'
])
.
$this
->
get_singular_param_name
()
.
'='
.
rawurlencode
(
$file_name
);
if
(
$version
)
{
$url
.=
'&version='
.
rawurlencode
(
$version
);
}
return
$url
.
'&download=1'
;
}
if
(
empty
(
$version
))
{
$version_path
=
''
;
}
else
{
$version_url
=
@
$this
->
options
[
'image_versions'
][
$version
][
'upload_url'
];
if
(
$version_url
)
{
return
$version_url
.
$this
->
get_user_path
()
.
rawurlencode
(
$file_name
);
}
$version_path
=
rawurlencode
(
$version
)
.
'/'
;
}
return
$this
->
options
[
'upload_url'
]
.
$this
->
get_user_path
()
.
$version_path
.
rawurlencode
(
$file_name
);
}
protected
function
set_additional_file_properties
(
$file
)
{
$file
->
deleteUrl
=
$this
->
options
[
'script_url'
]
.
$this
->
get_query_separator
(
$this
->
options
[
'script_url'
])
.
$this
->
get_singular_param_name
()
.
'='
.
rawurlencode
(
$file
->
name
);
$file
->
deleteType
=
$this
->
options
[
'delete_type'
];
if
(
$file
->
deleteType
!==
'DELETE'
)
{
$file
->
deleteUrl
.=
'&_method=DELETE'
;
}
if
(
$this
->
options
[
'access_control_allow_credentials'
])
{
$file
->
deleteWithCredentials
=
true
;
}
}
// Fix for overflowing signed 32 bit integers,
// works for sizes up to 2^32-1 bytes (4 GiB - 1):
protected
function
fix_integer_overflow
(
$size
)
{
if
(
$size
<
0
)
{
$size
+=
2.0
*
(
PHP_INT_MAX
+
1
);
}
return
$size
;
}
protected
function
get_file_size
(
$file_path
,
$clear_stat_cache
=
false
)
{
if
(
$clear_stat_cache
)
{
if
(
version_compare
(
PHP_VERSION
,
'5.3.0'
)
>=
0
)
{
clearstatcache
(
true
,
$file_path
);
}
else
{
clearstatcache
();
}
}
return
$this
->
fix_integer_overflow
(
filesize
(
$file_path
));
}
protected
function
is_valid_file_object
(
$file_name
)
{
$file_path
=
$this
->
get_upload_path
(
$file_name
);
if
(
is_file
(
$file_path
)
&&
$file_name
[
0
]
!==
'.'
)
{
return
true
;
}
return
false
;
}
protected
function
get_file_object
(
$file_name
)
{
if
(
$this
->
is_valid_file_object
(
$file_name
))
{
$file
=
new
\stdClass
();
$file
->
name
=
$file_name
;
$file
->
size
=
$this
->
get_file_size
(
$this
->
get_upload_path
(
$file_name
)
);
$file
->
url
=
$this
->
get_download_url
(
$file
->
name
);
foreach
(
$this
->
options
[
'image_versions'
]
as
$version
=>
$options
)
{
if
(
!
empty
(
$version
))
{
if
(
is_file
(
$this
->
get_upload_path
(
$file_name
,
$version
)))
{
$file
->
{
$version
.
'Url'
}
=
$this
->
get_download_url
(
$file
->
name
,
$version
);
}
}
}
$this
->
set_additional_file_properties
(
$file
);
return
$file
;
}
return
null
;
}
protected
function
get_file_objects
(
$iteration_method
=
'get_file_object'
)
{
$upload_dir
=
$this
->
get_upload_path
();
if
(
!
is_dir
(
$upload_dir
))
{
return
array
();
}
return
array_values
(
array_filter
(
array_map
(
array
(
$this
,
$iteration_method
),
scandir
(
$upload_dir
)
)));
}
protected
function
count_file_objects
()
{
return
count
(
$this
->
get_file_objects
(
'is_valid_file_object'
));
}
protected
function
get_error_message
(
$error
)
{
return
isset
(
$this
->
error_messages
[
$error
])
?
$this
->
error_messages
[
$error
]
:
$error
;
}
public
function
get_config_bytes
(
$val
)
{
$val
=
trim
(
$val
);
$last
=
strtolower
(
$val
[
strlen
(
$val
)
-
1
]);
$val
=
(
int
)
$val
;
switch
(
$last
)
{
case
'g'
:
$val
*=
1024
;
case
'm'
:
$val
*=
1024
;
case
'k'
:
$val
*=
1024
;
}
return
$this
->
fix_integer_overflow
(
$val
);
}
protected
function
validate
(
$uploaded_file
,
$file
,
$error
,
$index
)
{
if
(
$error
)
{
$file
->
error
=
$this
->
get_error_message
(
$error
);
return
false
;
}
$content_length
=
$this
->
fix_integer_overflow
(
(
int
)
$this
->
get_server_var
(
'CONTENT_LENGTH'
)
);
$post_max_size
=
$this
->
get_config_bytes
(
ini_get
(
'post_max_size'
));
if
(
$post_max_size
&&
(
$content_length
>
$post_max_size
))
{
$file
->
error
=
$this
->
get_error_message
(
'post_max_size'
);
return
false
;
}
if
(
!
preg_match
(
$this
->
options
[
'accept_file_types'
],
$file
->
name
))
{
$file
->
error
=
$this
->
get_error_message
(
'accept_file_types'
);
return
false
;
}
if
(
$uploaded_file
&&
is_uploaded_file
(
$uploaded_file
))
{
$file_size
=
$this
->
get_file_size
(
$uploaded_file
);
}
else
{
$file_size
=
$content_length
;
}
if
(
$this
->
options
[
'max_file_size'
]
&&
(
$file_size
>
$this
->
options
[
'max_file_size'
]
||
$file
->
size
>
$this
->
options
[
'max_file_size'
])
)
{
$file
->
error
=
$this
->
get_error_message
(
'max_file_size'
);
return
false
;
}
if
(
$this
->
options
[
'min_file_size'
]
&&
$file_size
<
$this
->
options
[
'min_file_size'
])
{
$file
->
error
=
$this
->
get_error_message
(
'min_file_size'
);
return
false
;
}
if
(
is_int
(
$this
->
options
[
'max_number_of_files'
])
&&
(
$this
->
count_file_objects
()
>=
$this
->
options
[
'max_number_of_files'
])
&&
// Ignore additional chunks of existing files:
!
is_file
(
$this
->
get_upload_path
(
$file
->
name
)))
{
$file
->
error
=
$this
->
get_error_message
(
'max_number_of_files'
);
return
false
;
}
$max_width
=
@
$this
->
options
[
'max_width'
];
$max_height
=
@
$this
->
options
[
'max_height'
];
$min_width
=
@
$this
->
options
[
'min_width'
];
$min_height
=
@
$this
->
options
[
'min_height'
];
if
((
$max_width
||
$max_height
||
$min_width
||
$min_height
)
&&
preg_match
(
$this
->
options
[
'image_file_types'
],
$file
->
name
))
{
list
(
$img_width
,
$img_height
)
=
$this
->
get_image_size
(
$uploaded_file
);
// If we are auto rotating the image by default, do the checks on
// the correct orientation
if
(
@
$this
->
options
[
'image_versions'
][
''
][
'auto_orient'
]
&&
function_exists
(
'exif_read_data'
)
&&
(
$exif
=
@
exif_read_data
(
$uploaded_file
))
&&
(((
int
)
@
$exif
[
'Orientation'
])
>=
5
)
)
{
$tmp
=
$img_width
;
$img_width
=
$img_height
;
$img_height
=
$tmp
;
unset
(
$tmp
);
}
}
if
(
!
empty
(
$img_width
))
{
if
(
$max_width
&&
$img_width
>
$max_width
)
{
$file
->
error
=
$this
->
get_error_message
(
'max_width'
);
return
false
;
}
if
(
$max_height
&&
$img_height
>
$max_height
)
{
$file
->
error
=
$this
->
get_error_message
(
'max_height'
);
return
false
;
}
if
(
$min_width
&&
$img_width
<
$min_width
)
{
$file
->
error
=
$this
->
get_error_message
(
'min_width'
);
return
false
;
}
if
(
$min_height
&&
$img_height
<
$min_height
)
{
$file
->
error
=
$this
->
get_error_message
(
'min_height'
);
return
false
;
}
}
return
true
;
}
protected
function
upcount_name_callback
(
$matches
)
{
$index
=
isset
(
$matches
[
1
])
?
((
int
)
$matches
[
1
])
+
1
:
1
;
$ext
=
isset
(
$matches
[
2
])
?
$matches
[
2
]
:
''
;
return
' ('
.
$index
.
')'
.
$ext
;
}
protected
function
upcount_name
(
$name
)
{
return
preg_replace_callback
(
'/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/'
,
array
(
$this
,
'upcount_name_callback'
),
$name
,
1
);
}
protected
function
get_unique_filename
(
$file_path
,
$name
,
$size
,
$type
,
$error
,
$index
,
$content_range
)
{
while
(
is_dir
(
$this
->
get_upload_path
(
$name
)))
{
$name
=
$this
->
upcount_name
(
$name
);
}
// Keep an existing filename if this is part of a chunked upload:
$uploaded_bytes
=
$this
->
fix_integer_overflow
((
int
)
$content_range
[
1
]);
while
(
is_file
(
$this
->
get_upload_path
(
$name
)))
{
if
(
$uploaded_bytes
===
$this
->
get_file_size
(
$this
->
get_upload_path
(
$name
)))
{
break
;
}
$name
=
$this
->
upcount_name
(
$name
);
}
return
$name
;
}
protected
function
fix_file_extension
(
$file_path
,
$name
,
$size
,
$type
,
$error
,
$index
,
$content_range
)
{
// Add missing file extension for known image types:
if
(
strpos
(
$name
,
'.'
)
===
false
&&
preg_match
(
'/^image\/(gif|jpe?g|png)/'
,
$type
,
$matches
))
{
$name
.=
'.'
.
$matches
[
1
];
}
if
(
$this
->
options
[
'correct_image_extensions'
]
&&
function_exists
(
'exif_imagetype'
))
{
switch
(
@
exif_imagetype
(
$file_path
)){
case
IMAGETYPE_JPEG
:
$extensions
=
array
(
'jpg'
,
'jpeg'
);
break
;
case
IMAGETYPE_PNG
:
$extensions
=
array
(
'png'
);
break
;
case
IMAGETYPE_GIF
:
$extensions
=
array
(
'gif'
);
break
;
}
// Adjust incorrect image file extensions:
if
(
!
empty
(
$extensions
))
{
$parts
=
explode
(
'.'
,
$name
);
$extIndex
=
count
(
$parts
)
-
1
;
$ext
=
strtolower
(
@
$parts
[
$extIndex
]);
if
(
!
in_array
(
$ext
,
$extensions
))
{
$parts
[
$extIndex
]
=
$extensions
[
0
];
$name
=
implode
(
'.'
,
$parts
);
}
}
}
return
$name
;
}
protected
function
trim_file_name
(
$file_path
,
$name
,
$size
,
$type
,
$error
,
$index
,
$content_range
)
{
// Remove path information and dots around the filename, to prevent uploading
// into different directories or replacing hidden system files.
// Also remove control characters and spaces (\x00..\x20) around the filename:
$name
=
trim
(
$this
->
basename
(
stripslashes
(
$name
)),
".
\x00
..
\x20
"
);
// Use a timestamp for empty filenames:
if
(
!
$name
)
{
$name
=
str_replace
(
'.'
,
'-'
,
microtime
(
true
));
}
return
$name
;
}
protected
function
get_file_name
(
$file_path
,
$name
,
$size
,
$type
,
$error
,
$index
,
$content_range
)
{
$name
=
$this
->
trim_file_name
(
$file_path
,
$name
,
$size
,
$type
,
$error
,
$index
,
$content_range
);
return
$this
->
get_unique_filename
(
$file_path
,
$this
->
fix_file_extension
(
$file_path
,
$name
,
$size
,
$type
,
$error
,
$index
,
$content_range
),
$size
,
$type
,
$error
,
$index
,
$content_range
);
}
protected
function
get_scaled_image_file_paths
(
$file_name
,
$version
)
{
$file_path
=
$this
->
get_upload_path
(
$file_name
);
if
(
!
empty
(
$version
))
{
$version_dir
=
$this
->
get_upload_path
(
null
,
$version
);
if
(
!
is_dir
(
$version_dir
))
{
mkdir
(
$version_dir
,
$this
->
options
[
'mkdir_mode'
],
true
);
}
$new_file_path
=
$version_dir
.
'/'
.
$file_name
;
}
else
{
$new_file_path
=
$file_path
;
}
return
array
(
$file_path
,
$new_file_path
);
}
protected
function
gd_get_image_object
(
$file_path
,
$func
,
$no_cache
=
false
)
{
if
(
empty
(
$this
->
image_objects
[
$file_path
])
||
$no_cache
)
{
$this
->
gd_destroy_image_object
(
$file_path
);
$this
->
image_objects
[
$file_path
]
=
$func
(
$file_path
);
}
return
$this
->
image_objects
[
$file_path
];
}
protected
function
gd_set_image_object
(
$file_path
,
$image
)
{
$this
->
gd_destroy_image_object
(
$file_path
);
$this
->
image_objects
[
$file_path
]
=
$image
;
}
protected
function
gd_destroy_image_object
(
$file_path
)
{
$image
=
(
isset
(
$this
->
image_objects
[
$file_path
]))
?
$this
->
image_objects
[
$file_path
]
:
null
;
return
$image
&&
imagedestroy
(
$image
);
}
protected
function
gd_imageflip
(
$image
,
$mode
)
{
if
(
function_exists
(
'imageflip'
))
{
return
imageflip
(
$image
,
$mode
);
}
$new_width
=
$src_width
=
imagesx
(
$image
);
$new_height
=
$src_height
=
imagesy
(
$image
);
$new_img
=
imagecreatetruecolor
(
$new_width
,
$new_height
);
$src_x
=
0
;
$src_y
=
0
;
switch
(
$mode
)
{
case
'1'
:
// flip on the horizontal axis
$src_y
=
$new_height
-
1
;
$src_height
=
-
$new_height
;
break
;
case
'2'
:
// flip on the vertical axis
$src_x
=
$new_width
-
1
;
$src_width
=
-
$new_width
;
break
;
case
'3'
:
// flip on both axes
$src_y
=
$new_height
-
1
;
$src_height
=
-
$new_height
;
$src_x
=
$new_width
-
1
;
$src_width
=
-
$new_width
;
break
;
default
:
return
$image
;
}
imagecopyresampled
(
$new_img
,
$image
,
0
,
0
,
$src_x
,
$src_y
,
$new_width
,
$new_height
,
$src_width
,
$src_height
);
return
$new_img
;
}
protected
function
gd_orient_image
(
$file_path
,
$src_img
)
{
if
(
!
function_exists
(
'exif_read_data'
))
{
return
false
;
}
$exif
=
@
exif_read_data
(
$file_path
);
if
(
$exif
===
false
)
{
return
false
;
}
$orientation
=
(
int
)
@
$exif
[
'Orientation'
];
if
(
$orientation
<
2
||
$orientation
>
8
)
{
return
false
;
}
switch
(
$orientation
)
{
case
2
:
$new_img
=
$this
->
gd_imageflip
(
$src_img
,
defined
(
'IMG_FLIP_VERTICAL'
)
?
IMG_FLIP_VERTICAL
:
2
);
break
;
case
3
:
$new_img
=
imagerotate
(
$src_img
,
180
,
0
);
break
;
case
4
:
$new_img
=
$this
->
gd_imageflip
(
$src_img
,
defined
(
'IMG_FLIP_HORIZONTAL'
)
?
IMG_FLIP_HORIZONTAL
:
1
);
break
;
case
5
:
$tmp_img
=
$this
->
gd_imageflip
(
$src_img
,
defined
(
'IMG_FLIP_HORIZONTAL'
)
?
IMG_FLIP_HORIZONTAL
:
1
);
$new_img
=
imagerotate
(
$tmp_img
,
270
,
0
);
imagedestroy
(
$tmp_img
);
break
;
case
6
:
$new_img
=
imagerotate
(
$src_img
,
270
,
0
);
break
;
case
7
:
$tmp_img
=
$this
->
gd_imageflip
(
$src_img
,
defined
(
'IMG_FLIP_VERTICAL'
)
?
IMG_FLIP_VERTICAL
:
2
);
$new_img
=
imagerotate
(
$tmp_img
,
270
,
0
);
imagedestroy
(
$tmp_img
);
break
;
case
8
:
$new_img
=
imagerotate
(
$src_img
,
90
,
0
);
break
;
default
:
return
false
;
}
$this
->
gd_set_image_object
(
$file_path
,
$new_img
);
return
true
;
}
protected
function
gd_create_scaled_image
(
$file_name
,
$version
,
$options
)
{
if
(
!
function_exists
(
'imagecreatetruecolor'
))
{
error_log
(
'Function not found: imagecreatetruecolor'
);
return
false
;
}
list
(
$file_path
,
$new_file_path
)
=
$this
->
get_scaled_image_file_paths
(
$file_name
,
$version
);
$type
=
strtolower
(
substr
(
strrchr
(
$file_name
,
'.'
),
1
));
switch
(
$type
)
{
case
'jpg'
:
case
'jpeg'
:
$src_func
=
'imagecreatefromjpeg'
;
$write_func
=
'imagejpeg'
;
$image_quality
=
isset
(
$options
[
'jpeg_quality'
])
?
$options
[
'jpeg_quality'
]
:
75
;
break
;
case
'gif'
:
$src_func
=
'imagecreatefromgif'
;
$write_func
=
'imagegif'
;
$image_quality
=
null
;
break
;
case
'png'
:
$src_func
=
'imagecreatefrompng'
;
$write_func
=
'imagepng'
;
$image_quality
=
isset
(
$options
[
'png_quality'
])
?
$options
[
'png_quality'
]
:
9
;
break
;
default
:
return
false
;
}
$src_img
=
$this
->
gd_get_image_object
(
$file_path
,
$src_func
,
!
empty
(
$options
[
'no_cache'
])
);
$image_oriented
=
false
;
if
(
!
empty
(
$options
[
'auto_orient'
])
&&
$this
->
gd_orient_image
(
$file_path
,
$src_img
))
{
$image_oriented
=
true
;
$src_img
=
$this
->
gd_get_image_object
(
$file_path
,
$src_func
);
}
$max_width
=
$img_width
=
imagesx
(
$src_img
);
$max_height
=
$img_height
=
imagesy
(
$src_img
);
if
(
!
empty
(
$options
[
'max_width'
]))
{
$max_width
=
$options
[
'max_width'
];
}
if
(
!
empty
(
$options
[
'max_height'
]))
{
$max_height
=
$options
[
'max_height'
];
}
$scale
=
min
(
$max_width
/
$img_width
,
$max_height
/
$img_height
);
if
(
$scale
>=
1
)
{
if
(
$image_oriented
)
{
return
$write_func
(
$src_img
,
$new_file_path
,
$image_quality
);
}
if
(
$file_path
!==
$new_file_path
)
{
return
copy
(
$file_path
,
$new_file_path
);
}
return
true
;
}
if
(
empty
(
$options
[
'crop'
]))
{
$new_width
=
$img_width
*
$scale
;
$new_height
=
$img_height
*
$scale
;
$dst_x
=
0
;
$dst_y
=
0
;
$new_img
=
imagecreatetruecolor
(
$new_width
,
$new_height
);
}
else
{
if
((
$img_width
/
$img_height
)
>=
(
$max_width
/
$max_height
))
{
$new_width
=
$img_width
/
(
$img_height
/
$max_height
);
$new_height
=
$max_height
;
}
else
{
$new_width
=
$max_width
;
$new_height
=
$img_height
/
(
$img_width
/
$max_width
);
}
$dst_x
=
0
-
(
$new_width
-
$max_width
)
/
2
;
$dst_y
=
0
-
(
$new_height
-
$max_height
)
/
2
;
$new_img
=
imagecreatetruecolor
(
$max_width
,
$max_height
);
}
// Handle transparency in GIF and PNG images:
switch
(
$type
)
{
case
'gif'
:
case
'png'
:
imagecolortransparent
(
$new_img
,
imagecolorallocate
(
$new_img
,
0
,
0
,
0
));
case
'png'
:
imagealphablending
(
$new_img
,
false
);
imagesavealpha
(
$new_img
,
true
);
break
;
}
$success
=
imagecopyresampled
(
$new_img
,
$src_img
,
$dst_x
,
$dst_y
,
0
,
0
,
$new_width
,
$new_height
,
$img_width
,
$img_height
)
&&
$write_func
(
$new_img
,
$new_file_path
,
$image_quality
);
$this
->
gd_set_image_object
(
$file_path
,
$new_img
);
return
$success
;
}
protected
function
imagick_get_image_object
(
$file_path
,
$no_cache
=
false
)
{
if
(
empty
(
$this
->
image_objects
[
$file_path
])
||
$no_cache
)
{
$this
->
imagick_destroy_image_object
(
$file_path
);
$image
=
new
\Imagick
();
if
(
!
empty
(
$this
->
options
[
'imagick_resource_limits'
]))
{
foreach
(
$this
->
options
[
'imagick_resource_limits'
]
as
$type
=>
$limit
)
{
$image
->
setResourceLimit
(
$type
,
$limit
);
}
}
$image
->
readImage
(
$file_path
);
$this
->
image_objects
[
$file_path
]
=
$image
;
}
return
$this
->
image_objects
[
$file_path
];
}
protected
function
imagick_set_image_object
(
$file_path
,
$image
)
{
$this
->
imagick_destroy_image_object
(
$file_path
);
$this
->
image_objects
[
$file_path
]
=
$image
;
}
protected
function
imagick_destroy_image_object
(
$file_path
)
{
$image
=
(
isset
(
$this
->
image_objects
[
$file_path
]))
?
$this
->
image_objects
[
$file_path
]
:
null
;
return
$image
&&
$image
->
destroy
();
}
protected
function
imagick_orient_image
(
$image
)
{
$orientation
=
$image
->
getImageOrientation
();
$background
=
new
\ImagickPixel
(
'none'
);
switch
(
$orientation
)
{
case
\imagick
::
ORIENTATION_TOPRIGHT
:
// 2
$image
->
flopImage
();
// horizontal flop around y-axis
break
;
case
\imagick
::
ORIENTATION_BOTTOMRIGHT
:
// 3
$image
->
rotateImage
(
$background
,
180
);
break
;
case
\imagick
::
ORIENTATION_BOTTOMLEFT
:
// 4
$image
->
flipImage
();
// vertical flip around x-axis
break
;
case
\imagick
::
ORIENTATION_LEFTTOP
:
// 5
$image
->
flopImage
();
// horizontal flop around y-axis
$image
->
rotateImage
(
$background
,
270
);
break
;
case
\imagick
::
ORIENTATION_RIGHTTOP
:
// 6
$image
->
rotateImage
(
$background
,
90
);
break
;
case
\imagick
::
ORIENTATION_RIGHTBOTTOM
:
// 7
$image
->
flipImage
();
// vertical flip around x-axis
$image
->
rotateImage
(
$background
,
270
);
break
;
case
\imagick
::
ORIENTATION_LEFTBOTTOM
:
// 8
$image
->
rotateImage
(
$background
,
270
);
break
;
default
:
return
false
;
}
$image
->
setImageOrientation
(
\imagick
::
ORIENTATION_TOPLEFT
);
// 1
return
true
;
}
protected
function
imagick_create_scaled_image
(
$file_name
,
$version
,
$options
)
{
list
(
$file_path
,
$new_file_path
)
=
$this
->
get_scaled_image_file_paths
(
$file_name
,
$version
);
$image
=
$this
->
imagick_get_image_object
(
$file_path
,
!
empty
(
$options
[
'crop'
])
||
!
empty
(
$options
[
'no_cache'
])
);
if
(
$image
->
getImageFormat
()
===
'GIF'
)
{
// Handle animated GIFs:
$images
=
$image
->
coalesceImages
();
foreach
(
$images
as
$frame
)
{
$image
=
$frame
;
$this
->
imagick_set_image_object
(
$file_name
,
$image
);
break
;
}
}
$image_oriented
=
false
;
if
(
!
empty
(
$options
[
'auto_orient'
]))
{
$image_oriented
=
$this
->
imagick_orient_image
(
$image
);
}
$new_width
=
$max_width
=
$img_width
=
$image
->
getImageWidth
();
$new_height
=
$max_height
=
$img_height
=
$image
->
getImageHeight
();
if
(
!
empty
(
$options
[
'max_width'
]))
{
$new_width
=
$max_width
=
$options
[
'max_width'
];
}
if
(
!
empty
(
$options
[
'max_height'
]))
{
$new_height
=
$max_height
=
$options
[
'max_height'
];
}
if
(
!
(
$image_oriented
||
$max_width
<
$img_width
||
$max_height
<
$img_height
))
{
if
(
$file_path
!==
$new_file_path
)
{
return
copy
(
$file_path
,
$new_file_path
);
}
return
true
;
}
$crop
=
!
empty
(
$options
[
'crop'
]);
if
(
$crop
)
{
$x
=
0
;
$y
=
0
;
if
((
$img_width
/
$img_height
)
>=
(
$max_width
/
$max_height
))
{
$new_width
=
0
;
// Enables proportional scaling based on max_height
$x
=
(
$img_width
/
(
$img_height
/
$max_height
)
-
$max_width
)
/
2
;
}
else
{
$new_height
=
0
;
// Enables proportional scaling based on max_width
$y
=
(
$img_height
/
(
$img_width
/
$max_width
)
-
$max_height
)
/
2
;
}
}
$success
=
$image
->
resizeImage
(
$new_width
,
$new_height
,
isset
(
$options
[
'filter'
])
?
$options
[
'filter'
]
:
\imagick
::
FILTER_LANCZOS
,
isset
(
$options
[
'blur'
])
?
$options
[
'blur'
]
:
1
,
$new_width
&&
$new_height
// fit image into constraints if not to be cropped
);
if
(
$success
&&
$crop
)
{
$success
=
$image
->
cropImage
(
$max_width
,
$max_height
,
$x
,
$y
);
if
(
$success
)
{
$success
=
$image
->
setImagePage
(
$max_width
,
$max_height
,
0
,
0
);
}
}
$type
=
strtolower
(
substr
(
strrchr
(
$file_name
,
'.'
),
1
));
switch
(
$type
)
{
case
'jpg'
:
case
'jpeg'
:
if
(
!
empty
(
$options
[
'jpeg_quality'
]))
{
$image
->
setImageCompression
(
\imagick
::
COMPRESSION_JPEG
);
$image
->
setImageCompressionQuality
(
$options
[
'jpeg_quality'
]);
}
break
;
}
if
(
!
empty
(
$options
[
'strip'
]))
{
$image
->
stripImage
();
}
return
$success
&&
$image
->
writeImage
(
$new_file_path
);
}
protected
function
imagemagick_create_scaled_image
(
$file_name
,
$version
,
$options
)
{
list
(
$file_path
,
$new_file_path
)
=
$this
->
get_scaled_image_file_paths
(
$file_name
,
$version
);
$resize
=
@
$options
[
'max_width'
]
.
(
empty
(
$options
[
'max_height'
])
?
''
:
'X'
.
$options
[
'max_height'
]);
if
(
!
$resize
&&
empty
(
$options
[
'auto_orient'
]))
{
if
(
$file_path
!==
$new_file_path
)
{
return
copy
(
$file_path
,
$new_file_path
);
}
return
true
;
}
$cmd
=
$this
->
options
[
'convert_bin'
];
if
(
!
empty
(
$this
->
options
[
'convert_params'
]))
{
$cmd
.=
' '
.
$this
->
options
[
'convert_params'
];
}
$cmd
.=
' '
.
escapeshellarg
(
$file_path
);
if
(
!
empty
(
$options
[
'auto_orient'
]))
{
$cmd
.=
' -auto-orient'
;
}
if
(
$resize
)
{
// Handle animated GIFs:
$cmd
.=
' -coalesce'
;
if
(
empty
(
$options
[
'crop'
]))
{
$cmd
.=
' -resize '
.
escapeshellarg
(
$resize
.
'>'
);
}
else
{
$cmd
.=
' -resize '
.
escapeshellarg
(
$resize
.
'^'
);
$cmd
.=
' -gravity center'
;
$cmd
.=
' -crop '
.
escapeshellarg
(
$resize
.
'+0+0'
);
}
// Make sure the page dimensions are correct (fixes offsets of animated GIFs):
$cmd
.=
' +repage'
;
}
if
(
!
empty
(
$options
[
'convert_params'
]))
{
$cmd
.=
' '
.
$options
[
'convert_params'
];
}
$cmd
.=
' '
.
escapeshellarg
(
$new_file_path
);
exec
(
$cmd
,
$output
,
$error
);
if
(
$error
)
{
error_log
(
implode
(
'\n'
,
$output
));
return
false
;
}
return
true
;
}
protected
function
get_image_size
(
$file_path
)
{
if
(
$this
->
options
[
'image_library'
])
{
if
(
extension_loaded
(
'imagick'
))
{
$image
=
new
\Imagick
();
try
{
if
(
@
$image
->
pingImage
(
$file_path
))
{
$dimensions
=
array
(
$image
->
getImageWidth
(),
$image
->
getImageHeight
());
$image
->
destroy
();
return
$dimensions
;
}
return
false
;
}
catch
(
\Exception
$e
)
{
error_log
(
$e
->
getMessage
());
}
}
if
(
$this
->
options
[
'image_library'
]
===
2
)
{
$cmd
=
$this
->
options
[
'identify_bin'
];
$cmd
.=
' -ping '
.
escapeshellarg
(
$file_path
);
exec
(
$cmd
,
$output
,
$error
);
if
(
!
$error
&&
!
empty
(
$output
))
{
// image.jpg JPEG 1920x1080 1920x1080+0+0 8-bit sRGB 465KB 0.000u 0:00.000
$infos
=
preg_split
(
'/\s+/'
,
substr
(
$output
[
0
],
strlen
(
$file_path
)));
$dimensions
=
preg_split
(
'/x/'
,
$infos
[
2
]);
return
$dimensions
;
}
return
false
;
}
}
if
(
!
function_exists
(
'getimagesize'
))
{
error_log
(
'Function not found: getimagesize'
);
return
false
;
}
return
@
getimagesize
(
$file_path
);
}
protected
function
create_scaled_image
(
$file_name
,
$version
,
$options
)
{
if
(
$this
->
options
[
'image_library'
]
===
2
)
{
return
$this
->
imagemagick_create_scaled_image
(
$file_name
,
$version
,
$options
);
}
if
(
$this
->
options
[
'image_library'
]
&&
extension_loaded
(
'imagick'
))
{
return
$this
->
imagick_create_scaled_image
(
$file_name
,
$version
,
$options
);
}
return
$this
->
gd_create_scaled_image
(
$file_name
,
$version
,
$options
);
}
protected
function
destroy_image_object
(
$file_path
)
{
if
(
$this
->
options
[
'image_library'
]
&&
extension_loaded
(
'imagick'
))
{
return
$this
->
imagick_destroy_image_object
(
$file_path
);
}
}
protected
function
is_valid_image_file
(
$file_path
)
{
if
(
!
preg_match
(
$this
->
options
[
'image_file_types'
],
$file_path
))
{
return
false
;
}
if
(
function_exists
(
'exif_imagetype'
))
{
return
@
exif_imagetype
(
$file_path
);
}
$image_info
=
$this
->
get_image_size
(
$file_path
);
return
$image_info
&&
$image_info
[
0
]
&&
$image_info
[
1
];
}
protected
function
handle_image_file
(
$file_path
,
$file
)
{
$failed_versions
=
array
();
foreach
(
$this
->
options
[
'image_versions'
]
as
$version
=>
$options
)
{
if
(
$this
->
create_scaled_image
(
$file
->
name
,
$version
,
$options
))
{
if
(
!
empty
(
$version
))
{
$file
->
{
$version
.
'Url'
}
=
$this
->
get_download_url
(
$file
->
name
,
$version
);
}
else
{
$file
->
size
=
$this
->
get_file_size
(
$file_path
,
true
);
}
}
else
{
$failed_versions
[]
=
$version
?
$version
:
'original'
;
}
}
if
(
count
(
$failed_versions
))
{
$file
->
error
=
$this
->
get_error_message
(
'image_resize'
)
.
' ('
.
implode
(
$failed_versions
,
', '
)
.
')'
;
}
// Free memory:
$this
->
destroy_image_object
(
$file_path
);
}
protected
function
handle_file_upload
(
$uploaded_file
,
$name
,
$size
,
$type
,
$error
,
$index
=
null
,
$content_range
=
null
)
{
$file
=
new
\stdClass
();
$file
->
name
=
$this
->
get_file_name
(
$uploaded_file
,
$name
,
$size
,
$type
,
$error
,
$index
,
$content_range
);
$file
->
size
=
$this
->
fix_integer_overflow
((
int
)
$size
);
$file
->
type
=
$type
;
if
(
$this
->
validate
(
$uploaded_file
,
$file
,
$error
,
$index
))
{
$this
->
handle_form_data
(
$file
,
$index
);
$upload_dir
=
$this
->
get_upload_path
();
if
(
!
is_dir
(
$upload_dir
))
{
mkdir
(
$upload_dir
,
$this
->
options
[
'mkdir_mode'
],
true
);
}
$file_path
=
$this
->
get_upload_path
(
$file
->
name
);
$append_file
=
$content_range
&&
is_file
(
$file_path
)
&&
$file
->
size
>
$this
->
get_file_size
(
$file_path
);
if
(
$uploaded_file
&&
is_uploaded_file
(
$uploaded_file
))
{
// multipart/formdata uploads (POST method uploads)
if
(
$append_file
)
{
file_put_contents
(
$file_path
,
fopen
(
$uploaded_file
,
'r'
),
FILE_APPEND
);
}
else
{
move_uploaded_file
(
$uploaded_file
,
$file_path
);
}
}
else
{
// Non-multipart uploads (PUT method support)
file_put_contents
(
$file_path
,
fopen
(
$this
->
options
[
'input_stream'
],
'r'
),
$append_file
?
FILE_APPEND
:
0
);
}
$file_size
=
$this
->
get_file_size
(
$file_path
,
$append_file
);
if
(
$file_size
===
$file
->
size
)
{
$file
->
url
=
$this
->
get_download_url
(
$file
->
name
);
if
(
$this
->
is_valid_image_file
(
$file_path
))
{
$this
->
handle_image_file
(
$file_path
,
$file
);
}
}
else
{
$file
->
size
=
$file_size
;
if
(
!
$content_range
&&
$this
->
options
[
'discard_aborted_uploads'
])
{
unlink
(
$file_path
);
$file
->
error
=
$this
->
get_error_message
(
'abort'
);
}
}
$this
->
set_additional_file_properties
(
$file
);
}
return
$file
;
}
protected
function
readfile
(
$file_path
)
{
$file_size
=
$this
->
get_file_size
(
$file_path
);
$chunk_size
=
$this
->
options
[
'readfile_chunk_size'
];
if
(
$chunk_size
&&
$file_size
>
$chunk_size
)
{
$handle
=
fopen
(
$file_path
,
'rb'
);
while
(
!
feof
(
$handle
))
{
echo
fread
(
$handle
,
$chunk_size
);
@
ob_flush
();
@
flush
();
}
fclose
(
$handle
);
return
$file_size
;
}
return
readfile
(
$file_path
);
}
protected
function
body
(
$str
)
{
echo
$str
;
}
protected
function
header
(
$str
)
{
header
(
$str
);
}
protected
function
get_upload_data
(
$id
)
{
return
@
$_FILES
[
$id
];
}
protected
function
get_post_param
(
$id
)
{
return
@
$_POST
[
$id
];
}
protected
function
get_query_param
(
$id
)
{
return
@
$_GET
[
$id
];
}
protected
function
get_server_var
(
$id
)
{
return
@
$_SERVER
[
$id
];
}
protected
function
handle_form_data
(
$file
,
$index
)
{
// Handle form data, e.g. $_POST['description'][$index]
}
protected
function
get_version_param
()
{
return
$this
->
basename
(
stripslashes
(
$this
->
get_query_param
(
'version'
)));
}
protected
function
get_singular_param_name
()
{
return
substr
(
$this
->
options
[
'param_name'
],
0
,
-
1
);
}
protected
function
get_file_name_param
()
{
$name
=
$this
->
get_singular_param_name
();
return
$this
->
basename
(
stripslashes
(
$this
->
get_query_param
(
$name
)));
}
protected
function
get_file_names_params
()
{
$params
=
$this
->
get_query_param
(
$this
->
options
[
'param_name'
]);
if
(
!
$params
)
{
return
null
;
}
foreach
(
$params
as
$key
=>
$value
)
{
$params
[
$key
]
=
$this
->
basename
(
stripslashes
(
$value
));
}
return
$params
;
}
protected
function
get_file_type
(
$file_path
)
{
switch
(
strtolower
(
pathinfo
(
$file_path
,
PATHINFO_EXTENSION
)))
{
case
'jpeg'
:
case
'jpg'
:
return
'image/jpeg'
;
case
'png'
:
return
'image/png'
;
case
'gif'
:
return
'image/gif'
;
default
:
return
''
;
}
}
protected
function
download
()
{
switch
(
$this
->
options
[
'download_via_php'
])
{
case
1
:
$redirect_header
=
null
;
break
;
case
2
:
$redirect_header
=
'X-Sendfile'
;
break
;
case
3
:
$redirect_header
=
'X-Accel-Redirect'
;
break
;
default
:
return
$this
->
header
(
'HTTP/1.1 403 Forbidden'
);
}
$file_name
=
$this
->
get_file_name_param
();
if
(
!
$this
->
is_valid_file_object
(
$file_name
))
{
return
$this
->
header
(
'HTTP/1.1 404 Not Found'
);
}
if
(
$redirect_header
)
{
return
$this
->
header
(
$redirect_header
.
': '
.
$this
->
get_download_url
(
$file_name
,
$this
->
get_version_param
(),
true
)
);
}
$file_path
=
$this
->
get_upload_path
(
$file_name
,
$this
->
get_version_param
());
// Prevent browsers from MIME-sniffing the content-type:
$this
->
header
(
'X-Content-Type-Options: nosniff'
);
if
(
!
preg_match
(
$this
->
options
[
'inline_file_types'
],
$file_name
))
{
$this
->
header
(
'Content-Type: application/octet-stream'
);
$this
->
header
(
'Content-Disposition: attachment; filename="'
.
$file_name
.
'"'
);
}
else
{
$this
->
header
(
'Content-Type: '
.
$this
->
get_file_type
(
$file_path
));
$this
->
header
(
'Content-Disposition: inline; filename="'
.
$file_name
.
'"'
);
}
$this
->
header
(
'Content-Length: '
.
$this
->
get_file_size
(
$file_path
));
$this
->
header
(
'Last-Modified: '
.
gmdate
(
'D, d M Y H:i:s T'
,
filemtime
(
$file_path
)));
$this
->
readfile
(
$file_path
);
}
protected
function
send_content_type_header
()
{
$this
->
header
(
'Vary: Accept'
);
if
(
strpos
(
$this
->
get_server_var
(
'HTTP_ACCEPT'
),
'application/json'
)
!==
false
)
{
$this
->
header
(
'Content-type: application/json'
);
}
else
{
$this
->
header
(
'Content-type: text/plain'
);
}
}
protected
function
send_access_control_headers
()
{
$this
->
header
(
'Access-Control-Allow-Origin: '
.
$this
->
options
[
'access_control_allow_origin'
]);
$this
->
header
(
'Access-Control-Allow-Credentials: '
.
(
$this
->
options
[
'access_control_allow_credentials'
]
?
'true'
:
'false'
));
$this
->
header
(
'Access-Control-Allow-Methods: '
.
implode
(
', '
,
$this
->
options
[
'access_control_allow_methods'
]));
$this
->
header
(
'Access-Control-Allow-Headers: '
.
implode
(
', '
,
$this
->
options
[
'access_control_allow_headers'
]));
}
public
function
generate_response
(
$content
,
$print_response
=
true
)
{
$this
->
response
=
$content
;
if
(
$print_response
)
{
$json
=
json_encode
(
$content
);
$redirect
=
stripslashes
(
$this
->
get_post_param
(
'redirect'
));
if
(
$redirect
&&
preg_match
(
$this
->
options
[
'redirect_allow_target'
],
$redirect
))
{
$this
->
header
(
'Location: '
.
sprintf
(
$redirect
,
rawurlencode
(
$json
)));
return
;
}
$this
->
head
();
if
(
$this
->
get_server_var
(
'HTTP_CONTENT_RANGE'
))
{
$files
=
isset
(
$content
[
$this
->
options
[
'param_name'
]])
?
$content
[
$this
->
options
[
'param_name'
]]
:
null
;
if
(
$files
&&
is_array
(
$files
)
&&
is_object
(
$files
[
0
])
&&
$files
[
0
]
->
size
)
{
$this
->
header
(
'Range: 0-'
.
(
$this
->
fix_integer_overflow
((
int
)
$files
[
0
]
->
size
)
-
1
));
}
}
$this
->
body
(
$json
);
}
return
$content
;
}
public
function
get_response
()
{
return
$this
->
response
;
}
public
function
head
()
{
$this
->
header
(
'Pragma: no-cache'
);
$this
->
header
(
'Cache-Control: no-store, no-cache, must-revalidate'
);
$this
->
header
(
'Content-Disposition: inline; filename="files.json"'
);
// Prevent Internet Explorer from MIME-sniffing the content-type:
$this
->
header
(
'X-Content-Type-Options: nosniff'
);
if
(
$this
->
options
[
'access_control_allow_origin'
])
{
$this
->
send_access_control_headers
();
}
$this
->
send_content_type_header
();
}
public
function
get
(
$print_response
=
true
)
{
if
(
$print_response
&&
$this
->
get_query_param
(
'download'
))
{
return
$this
->
download
();
}
$file_name
=
$this
->
get_file_name_param
();
if
(
$file_name
)
{
$response
=
array
(
$this
->
get_singular_param_name
()
=>
$this
->
get_file_object
(
$file_name
)
);
}
else
{
$response
=
array
(
$this
->
options
[
'param_name'
]
=>
$this
->
get_file_objects
()
);
}
return
$this
->
generate_response
(
$response
,
$print_response
);
}
public
function
post
(
$print_response
=
true
)
{
if
(
$this
->
get_query_param
(
'_method'
)
===
'DELETE'
)
{
return
$this
->
delete
(
$print_response
);
}
$upload
=
$this
->
get_upload_data
(
$this
->
options
[
'param_name'
]);
// Parse the Content-Disposition header, if available:
$content_disposition_header
=
$this
->
get_server_var
(
'HTTP_CONTENT_DISPOSITION'
);
$file_name
=
$content_disposition_header
?
rawurldecode
(
preg_replace
(
'/(^[^"]+")|("$)/'
,
''
,
$content_disposition_header
))
:
null
;
// Parse the Content-Range header, which has the following form:
// Content-Range: bytes 0-524287/2000000
$content_range_header
=
$this
->
get_server_var
(
'HTTP_CONTENT_RANGE'
);
$content_range
=
$content_range_header
?
preg_split
(
'/[^0-9]+/'
,
$content_range_header
)
:
null
;
$size
=
$content_range
?
$content_range
[
3
]
:
null
;
$files
=
array
();
if
(
$upload
)
{
if
(
is_array
(
$upload
[
'tmp_name'
]))
{
// param_name is an array identifier like "files[]",
// $upload is a multi-dimensional array:
foreach
(
$upload
[
'tmp_name'
]
as
$index
=>
$value
)
{
$files
[]
=
$this
->
handle_file_upload
(
$upload
[
'tmp_name'
][
$index
],
$file_name
?
$file_name
:
$upload
[
'name'
][
$index
],
$size
?
$size
:
$upload
[
'size'
][
$index
],
$upload
[
'type'
][
$index
],
$upload
[
'error'
][
$index
],
$index
,
$content_range
);
}
}
else
{
// param_name is a single object identifier like "file",
// $upload is a one-dimensional array:
$files
[]
=
$this
->
handle_file_upload
(
isset
(
$upload
[
'tmp_name'
])
?
$upload
[
'tmp_name'
]
:
null
,
$file_name
?
$file_name
:
(
isset
(
$upload
[
'name'
])
?
$upload
[
'name'
]
:
null
),
$size
?
$size
:
(
isset
(
$upload
[
'size'
])
?
$upload
[
'size'
]
:
$this
->
get_server_var
(
'CONTENT_LENGTH'
)),
isset
(
$upload
[
'type'
])
?
$upload
[
'type'
]
:
$this
->
get_server_var
(
'CONTENT_TYPE'
),
isset
(
$upload
[
'error'
])
?
$upload
[
'error'
]
:
null
,
null
,
$content_range
);
}
}
$response
=
array
(
$this
->
options
[
'param_name'
]
=>
$files
);
return
$this
->
generate_response
(
$response
,
$print_response
);
}
public
function
delete
(
$print_response
=
true
)
{
$file_names
=
$this
->
get_file_names_params
();
if
(
empty
(
$file_names
))
{
$file_names
=
array
(
$this
->
get_file_name_param
());
}
$response
=
array
();
foreach
(
$file_names
as
$file_name
)
{
$file_path
=
$this
->
get_upload_path
(
$file_name
);
$success
=
is_file
(
$file_path
)
&&
$file_name
[
0
]
!==
'.'
&&
unlink
(
$file_path
);
if
(
$success
)
{
foreach
(
$this
->
options
[
'image_versions'
]
as
$version
=>
$options
)
{
if
(
!
empty
(
$version
))
{
$file
=
$this
->
get_upload_path
(
$file_name
,
$version
);
if
(
is_file
(
$file
))
{
unlink
(
$file
);
}
}
}
}
$response
[
$file_name
]
=
$success
;
}
return
$this
->
generate_response
(
$response
,
$print_response
);
}
protected
function
basename
(
$filepath
,
$suffix
=
null
)
{
$splited
=
preg_split
(
'/\//'
,
rtrim
(
$filepath
,
'/ '
));
return
substr
(
basename
(
'X'
.
$splited
[
count
(
$splited
)
-
1
],
$suffix
),
1
);
}
}
src/update/fileupload/jquery.fileupload.css
0 → 100644
View file @
5f4bb7de
@charset
"UTF-8"
;
/*
* jQuery File Upload Plugin CSS
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
.fileinput-button
{
position
:
relative
;
overflow
:
hidden
;
display
:
inline-block
;
}
.fileinput-button
input
{
position
:
absolute
;
top
:
0
;
right
:
0
;
margin
:
0
;
opacity
:
0
;
-ms-filter
:
'alpha(opacity=0)'
;
font-size
:
200px
!important
;
direction
:
ltr
;
cursor
:
pointer
;
}
/* Fixes for IE < 8 */
@media
screen
\
9
{
.fileinput-button
input
{
filter
:
alpha
(
opacity
=
0
);
font-size
:
100%
;
height
:
100%
;
}
}
src/update/fileupload/jquery.fileupload.js
0 → 100644
View file @
5f4bb7de
/*
* jQuery File Upload Plugin
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/* jshint nomen:false */
/* global define, require, window, document, location, Blob, FormData */
;(
function
(
factory
)
{
'use strict'
;
if
(
typeof
define
===
'function'
&&
define
.
amd
)
{
// Register as an anonymous AMD module:
define
([
'jquery'
,
'jquery-ui/widget'
],
factory
);
}
else
if
(
typeof
exports
===
'object'
)
{
// Node/CommonJS:
factory
(
require
(
'jquery'
),
require
(
'./vendor/jquery.ui.widget'
)
);
}
else
{
// Browser globals:
factory
(
window
.
jQuery
);
}
}(
function
(
$
)
{
'use strict'
;
// Detect file input support, based on
// http://viljamis.com/blog/2012/file-upload-support-on-mobile/
$
.
support
.
fileInput
=
!
(
new
RegExp
(
// Handle devices which give false positives for the feature detection:
'(Android (1
\\
.[0156]|2
\\
.[01]))'
+
'|(Windows Phone (OS 7|8
\\
.0))|(XBLWP)|(ZuneWP)|(WPDesktop)'
+
'|(w(eb)?OSBrowser)|(webOS)'
+
'|(Kindle/(1
\\
.0|2
\\
.[05]|3
\\
.0))'
).
test
(
window
.
navigator
.
userAgent
)
||
// Feature detection for all other devices:
$
(
'<input type="file">'
).
prop
(
'disabled'
));
// The FileReader API is not actually used, but works as feature detection,
// as some Safari versions (5?) support XHR file uploads via the FormData API,
// but not non-multipart XHR file uploads.
// window.XMLHttpRequestUpload is not available on IE10, so we check for
// window.ProgressEvent instead to detect XHR2 file upload capability:
$
.
support
.
xhrFileUpload
=
!!
(
window
.
ProgressEvent
&&
window
.
FileReader
);
$
.
support
.
xhrFormDataFileUpload
=
!!
window
.
FormData
;
// Detect support for Blob slicing (required for chunked uploads):
$
.
support
.
blobSlice
=
window
.
Blob
&&
(
Blob
.
prototype
.
slice
||
Blob
.
prototype
.
webkitSlice
||
Blob
.
prototype
.
mozSlice
);
// Helper function to create drag handlers for dragover/dragenter/dragleave:
function
getDragHandler
(
type
)
{
var
isDragOver
=
type
===
'dragover'
;
return
function
(
e
)
{
e
.
dataTransfer
=
e
.
originalEvent
&&
e
.
originalEvent
.
dataTransfer
;
var
dataTransfer
=
e
.
dataTransfer
;
if
(
dataTransfer
&&
$
.
inArray
(
'Files'
,
dataTransfer
.
types
)
!==
-
1
&&
this
.
_trigger
(
type
,
$
.
Event
(
type
,
{
delegatedEvent
:
e
})
)
!==
false
)
{
e
.
preventDefault
();
if
(
isDragOver
)
{
dataTransfer
.
dropEffect
=
'copy'
;
}
}
};
}
// The fileupload widget listens for change events on file input fields defined
// via fileInput setting and paste or drop events of the given dropZone.
// In addition to the default jQuery Widget methods, the fileupload widget
// exposes the "add" and "send" methods, to add or directly send files using
// the fileupload API.
// By default, files added via file input selection, paste, drag & drop or
// "add" method are uploaded immediately, but it is possible to override
// the "add" callback option to queue file uploads.
$
.
widget
(
'blueimp.fileupload'
,
{
options
:
{
// The drop target element(s), by the default the complete document.
// Set to null to disable drag & drop support:
dropZone
:
$
(
document
),
// The paste target element(s), by the default undefined.
// Set to a DOM node or jQuery object to enable file pasting:
pasteZone
:
undefined
,
// The file input field(s), that are listened to for change events.
// If undefined, it is set to the file input fields inside
// of the widget element on plugin initialization.
// Set to null to disable the change listener.
fileInput
:
undefined
,
// By default, the file input field is replaced with a clone after
// each input field change event. This is required for iframe transport
// queues and allows change events to be fired for the same file
// selection, but can be disabled by setting the following option to false:
replaceFileInput
:
true
,
// The parameter name for the file form data (the request argument name).
// If undefined or empty, the name property of the file input field is
// used, or "files[]" if the file input name property is also empty,
// can be a string or an array of strings:
paramName
:
undefined
,
// By default, each file of a selection is uploaded using an individual
// request for XHR type uploads. Set to false to upload file
// selections in one request each:
singleFileUploads
:
true
,
// To limit the number of files uploaded with one XHR request,
// set the following option to an integer greater than 0:
limitMultiFileUploads
:
undefined
,
// The following option limits the number of files uploaded with one
// XHR request to keep the request size under or equal to the defined
// limit in bytes:
limitMultiFileUploadSize
:
undefined
,
// Multipart file uploads add a number of bytes to each uploaded file,
// therefore the following option adds an overhead for each file used
// in the limitMultiFileUploadSize configuration:
limitMultiFileUploadSizeOverhead
:
512
,
// Set the following option to true to issue all file upload requests
// in a sequential order:
sequentialUploads
:
false
,
// To limit the number of concurrent uploads,
// set the following option to an integer greater than 0:
limitConcurrentUploads
:
undefined
,
// Set the following option to true to force iframe transport uploads:
forceIframeTransport
:
false
,
// Set the following option to the location of a redirect url on the
// origin server, for cross-domain iframe transport uploads:
redirect
:
undefined
,
// The parameter name for the redirect url, sent as part of the form
// data and set to 'redirect' if this option is empty:
redirectParamName
:
undefined
,
// Set the following option to the location of a postMessage window,
// to enable postMessage transport uploads:
postMessage
:
undefined
,
// By default, XHR file uploads are sent as multipart/form-data.
// The iframe transport is always using multipart/form-data.
// Set to false to enable non-multipart XHR uploads:
multipart
:
true
,
// To upload large files in smaller chunks, set the following option
// to a preferred maximum chunk size. If set to 0, null or undefined,
// or the browser does not support the required Blob API, files will
// be uploaded as a whole.
maxChunkSize
:
undefined
,
// When a non-multipart upload or a chunked multipart upload has been
// aborted, this option can be used to resume the upload by setting
// it to the size of the already uploaded bytes. This option is most
// useful when modifying the options object inside of the "add" or
// "send" callbacks, as the options are cloned for each file upload.
uploadedBytes
:
undefined
,
// By default, failed (abort or error) file uploads are removed from the
// global progress calculation. Set the following option to false to
// prevent recalculating the global progress data:
recalculateProgress
:
true
,
// Interval in milliseconds to calculate and trigger progress events:
progressInterval
:
100
,
// Interval in milliseconds to calculate progress bitrate:
bitrateInterval
:
500
,
// By default, uploads are started automatically when adding files:
autoUpload
:
true
,
// Error and info messages:
messages
:
{
uploadedBytes
:
'Uploaded bytes exceed file size'
},
// Translation function, gets the message key to be translated
// and an object with context specific data as arguments:
i18n
:
function
(
message
,
context
)
{
message
=
this
.
messages
[
message
]
||
message
.
toString
();
if
(
context
)
{
$
.
each
(
context
,
function
(
key
,
value
)
{
message
=
message
.
replace
(
'{'
+
key
+
'}'
,
value
);
});
}
return
message
;
},
// Additional form data to be sent along with the file uploads can be set
// using this option, which accepts an array of objects with name and
// value properties, a function returning such an array, a FormData
// object (for XHR file uploads), or a simple object.
// The form of the first fileInput is given as parameter to the function:
formData
:
function
(
form
)
{
return
form
.
serializeArray
();
},
// The add callback is invoked as soon as files are added to the fileupload
// widget (via file input selection, drag & drop, paste or add API call).
// If the singleFileUploads option is enabled, this callback will be
// called once for each file in the selection for XHR file uploads, else
// once for each file selection.
//
// The upload starts when the submit method is invoked on the data parameter.
// The data object contains a files property holding the added files
// and allows you to override plugin options as well as define ajax settings.
//
// Listeners for this callback can also be bound the following way:
// .bind('fileuploadadd', func);
//
// data.submit() returns a Promise object and allows to attach additional
// handlers using jQuery's Deferred callbacks:
// data.submit().done(func).fail(func).always(func);
add
:
function
(
e
,
data
)
{
if
(
e
.
isDefaultPrevented
())
{
return
false
;
}
if
(
data
.
autoUpload
||
(
data
.
autoUpload
!==
false
&&
$
(
this
).
fileupload
(
'option'
,
'autoUpload'
)))
{
data
.
process
().
done
(
function
()
{
data
.
submit
();
});
}
},
// Other callbacks:
// Callback for the submit event of each file upload:
// submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
// Callback for the start of each file upload request:
// send: function (e, data) {}, // .bind('fileuploadsend', func);
// Callback for successful uploads:
// done: function (e, data) {}, // .bind('fileuploaddone', func);
// Callback for failed (abort or error) uploads:
// fail: function (e, data) {}, // .bind('fileuploadfail', func);
// Callback for completed (success, abort or error) requests:
// always: function (e, data) {}, // .bind('fileuploadalways', func);
// Callback for upload progress events:
// progress: function (e, data) {}, // .bind('fileuploadprogress', func);
// Callback for global upload progress events:
// progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
// Callback for uploads start, equivalent to the global ajaxStart event:
// start: function (e) {}, // .bind('fileuploadstart', func);
// Callback for uploads stop, equivalent to the global ajaxStop event:
// stop: function (e) {}, // .bind('fileuploadstop', func);
// Callback for change events of the fileInput(s):
// change: function (e, data) {}, // .bind('fileuploadchange', func);
// Callback for paste events to the pasteZone(s):
// paste: function (e, data) {}, // .bind('fileuploadpaste', func);
// Callback for drop events of the dropZone(s):
// drop: function (e, data) {}, // .bind('fileuploaddrop', func);
// Callback for dragover events of the dropZone(s):
// dragover: function (e) {}, // .bind('fileuploaddragover', func);
// Callback for the start of each chunk upload request:
// chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
// Callback for successful chunk uploads:
// chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
// Callback for failed (abort or error) chunk uploads:
// chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
// Callback for completed (success, abort or error) chunk upload requests:
// chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
// The plugin options are used as settings object for the ajax calls.
// The following are jQuery ajax settings required for the file uploads:
processData
:
false
,
contentType
:
false
,
cache
:
false
,
timeout
:
0
},
// A list of options that require reinitializing event listeners and/or
// special initialization code:
_specialOptions
:
[
'fileInput'
,
'dropZone'
,
'pasteZone'
,
'multipart'
,
'forceIframeTransport'
],
_blobSlice
:
$
.
support
.
blobSlice
&&
function
()
{
var
slice
=
this
.
slice
||
this
.
webkitSlice
||
this
.
mozSlice
;
return
slice
.
apply
(
this
,
arguments
);
},
_BitrateTimer
:
function
()
{
this
.
timestamp
=
((
Date
.
now
)
?
Date
.
now
()
:
(
new
Date
()).
getTime
());
this
.
loaded
=
0
;
this
.
bitrate
=
0
;
this
.
getBitrate
=
function
(
now
,
loaded
,
interval
)
{
var
timeDiff
=
now
-
this
.
timestamp
;
if
(
!
this
.
bitrate
||
!
interval
||
timeDiff
>
interval
)
{
this
.
bitrate
=
(
loaded
-
this
.
loaded
)
*
(
1000
/
timeDiff
)
*
8
;
this
.
loaded
=
loaded
;
this
.
timestamp
=
now
;
}
return
this
.
bitrate
;
};
},
_isXHRUpload
:
function
(
options
)
{
return
!
options
.
forceIframeTransport
&&
((
!
options
.
multipart
&&
$
.
support
.
xhrFileUpload
)
||
$
.
support
.
xhrFormDataFileUpload
);
},
_getFormData
:
function
(
options
)
{
var
formData
;
if
(
$
.
type
(
options
.
formData
)
===
'function'
)
{
return
options
.
formData
(
options
.
form
);
}
if
(
$
.
isArray
(
options
.
formData
))
{
return
options
.
formData
;
}
if
(
$
.
type
(
options
.
formData
)
===
'object'
)
{
formData
=
[];
$
.
each
(
options
.
formData
,
function
(
name
,
value
)
{
formData
.
push
({
name
:
name
,
value
:
value
});
});
return
formData
;
}
return
[];
},
_getTotal
:
function
(
files
)
{
var
total
=
0
;
$
.
each
(
files
,
function
(
index
,
file
)
{
total
+=
file
.
size
||
1
;
});
return
total
;
},
_initProgressObject
:
function
(
obj
)
{
var
progress
=
{
loaded
:
0
,
total
:
0
,
bitrate
:
0
};
if
(
obj
.
_progress
)
{
$
.
extend
(
obj
.
_progress
,
progress
);
}
else
{
obj
.
_progress
=
progress
;
}
},
_initResponseObject
:
function
(
obj
)
{
var
prop
;
if
(
obj
.
_response
)
{
for
(
prop
in
obj
.
_response
)
{
if
(
obj
.
_response
.
hasOwnProperty
(
prop
))
{
delete
obj
.
_response
[
prop
];
}
}
}
else
{
obj
.
_response
=
{};
}
},
_onProgress
:
function
(
e
,
data
)
{
if
(
e
.
lengthComputable
)
{
var
now
=
((
Date
.
now
)
?
Date
.
now
()
:
(
new
Date
()).
getTime
()),
loaded
;
if
(
data
.
_time
&&
data
.
progressInterval
&&
(
now
-
data
.
_time
<
data
.
progressInterval
)
&&
e
.
loaded
!==
e
.
total
)
{
return
;
}
data
.
_time
=
now
;
loaded
=
Math
.
floor
(
e
.
loaded
/
e
.
total
*
(
data
.
chunkSize
||
data
.
_progress
.
total
)
)
+
(
data
.
uploadedBytes
||
0
);
// Add the difference from the previously loaded state
// to the global loaded counter:
this
.
_progress
.
loaded
+=
(
loaded
-
data
.
_progress
.
loaded
);
this
.
_progress
.
bitrate
=
this
.
_bitrateTimer
.
getBitrate
(
now
,
this
.
_progress
.
loaded
,
data
.
bitrateInterval
);
data
.
_progress
.
loaded
=
data
.
loaded
=
loaded
;
data
.
_progress
.
bitrate
=
data
.
bitrate
=
data
.
_bitrateTimer
.
getBitrate
(
now
,
loaded
,
data
.
bitrateInterval
);
// Trigger a custom progress event with a total data property set
// to the file size(s) of the current upload and a loaded data
// property calculated accordingly:
this
.
_trigger
(
'progress'
,
$
.
Event
(
'progress'
,
{
delegatedEvent
:
e
}),
data
);
// Trigger a global progress event for all current file uploads,
// including ajax calls queued for sequential file uploads:
this
.
_trigger
(
'progressall'
,
$
.
Event
(
'progressall'
,
{
delegatedEvent
:
e
}),
this
.
_progress
);
}
},
_initProgressListener
:
function
(
options
)
{
var
that
=
this
,
xhr
=
options
.
xhr
?
options
.
xhr
()
:
$
.
ajaxSettings
.
xhr
();
// Accesss to the native XHR object is required to add event listeners
// for the upload progress event:
if
(
xhr
.
upload
)
{
$
(
xhr
.
upload
).
bind
(
'progress'
,
function
(
e
)
{
var
oe
=
e
.
originalEvent
;
// Make sure the progress event properties get copied over:
e
.
lengthComputable
=
oe
.
lengthComputable
;
e
.
loaded
=
oe
.
loaded
;
e
.
total
=
oe
.
total
;
that
.
_onProgress
(
e
,
options
);
});
options
.
xhr
=
function
()
{
return
xhr
;
};
}
},
_isInstanceOf
:
function
(
type
,
obj
)
{
// Cross-frame instanceof check
return
Object
.
prototype
.
toString
.
call
(
obj
)
===
'[object '
+
type
+
']'
;
},
_initXHRData
:
function
(
options
)
{
var
that
=
this
,
formData
,
file
=
options
.
files
[
0
],
// Ignore non-multipart setting if not supported:
multipart
=
options
.
multipart
||
!
$
.
support
.
xhrFileUpload
,
paramName
=
$
.
type
(
options
.
paramName
)
===
'array'
?
options
.
paramName
[
0
]
:
options
.
paramName
;
options
.
headers
=
$
.
extend
({},
options
.
headers
);
if
(
options
.
contentRange
)
{
options
.
headers
[
'Content-Range'
]
=
options
.
contentRange
;
}
if
(
!
multipart
||
options
.
blob
||
!
this
.
_isInstanceOf
(
'File'
,
file
))
{
options
.
headers
[
'Content-Disposition'
]
=
'attachment; filename="'
+
encodeURI
(
file
.
name
)
+
'"'
;
}
if
(
!
multipart
)
{
options
.
contentType
=
file
.
type
||
'application/octet-stream'
;
options
.
data
=
options
.
blob
||
file
;
}
else
if
(
$
.
support
.
xhrFormDataFileUpload
)
{
if
(
options
.
postMessage
)
{
// window.postMessage does not allow sending FormData
// objects, so we just add the File/Blob objects to
// the formData array and let the postMessage window
// create the FormData object out of this array:
formData
=
this
.
_getFormData
(
options
);
if
(
options
.
blob
)
{
formData
.
push
({
name
:
paramName
,
value
:
options
.
blob
});
}
else
{
$
.
each
(
options
.
files
,
function
(
index
,
file
)
{
formData
.
push
({
name
:
(
$
.
type
(
options
.
paramName
)
===
'array'
&&
options
.
paramName
[
index
])
||
paramName
,
value
:
file
});
});
}
}
else
{
if
(
that
.
_isInstanceOf
(
'FormData'
,
options
.
formData
))
{
formData
=
options
.
formData
;
}
else
{
formData
=
new
FormData
();
$
.
each
(
this
.
_getFormData
(
options
),
function
(
index
,
field
)
{
formData
.
append
(
field
.
name
,
field
.
value
);
});
}
if
(
options
.
blob
)
{
formData
.
append
(
paramName
,
options
.
blob
,
file
.
name
);
}
else
{
$
.
each
(
options
.
files
,
function
(
index
,
file
)
{
// This check allows the tests to run with
// dummy objects:
if
(
that
.
_isInstanceOf
(
'File'
,
file
)
||
that
.
_isInstanceOf
(
'Blob'
,
file
))
{
formData
.
append
(
(
$
.
type
(
options
.
paramName
)
===
'array'
&&
options
.
paramName
[
index
])
||
paramName
,
file
,
file
.
uploadName
||
file
.
name
);
}
});
}
}
options
.
data
=
formData
;
}
// Blob reference is not needed anymore, free memory:
options
.
blob
=
null
;
},
_initIframeSettings
:
function
(
options
)
{
var
targetHost
=
$
(
'<a></a>'
).
prop
(
'href'
,
options
.
url
).
prop
(
'host'
);
// Setting the dataType to iframe enables the iframe transport:
options
.
dataType
=
'iframe '
+
(
options
.
dataType
||
''
);
// The iframe transport accepts a serialized array as form data:
options
.
formData
=
this
.
_getFormData
(
options
);
// Add redirect url to form data on cross-domain uploads:
if
(
options
.
redirect
&&
targetHost
&&
targetHost
!==
location
.
host
)
{
options
.
formData
.
push
({
name
:
options
.
redirectParamName
||
'redirect'
,
value
:
options
.
redirect
});
}
},
_initDataSettings
:
function
(
options
)
{
if
(
this
.
_isXHRUpload
(
options
))
{
if
(
!
this
.
_chunkedUpload
(
options
,
true
))
{
if
(
!
options
.
data
)
{
this
.
_initXHRData
(
options
);
}
this
.
_initProgressListener
(
options
);
}
if
(
options
.
postMessage
)
{
// Setting the dataType to postmessage enables the
// postMessage transport:
options
.
dataType
=
'postmessage '
+
(
options
.
dataType
||
''
);
}
}
else
{
this
.
_initIframeSettings
(
options
);
}
},
_getParamName
:
function
(
options
)
{
var
fileInput
=
$
(
options
.
fileInput
),
paramName
=
options
.
paramName
;
if
(
!
paramName
)
{
paramName
=
[];
fileInput
.
each
(
function
()
{
var
input
=
$
(
this
),
name
=
input
.
prop
(
'name'
)
||
'files[]'
,
i
=
(
input
.
prop
(
'files'
)
||
[
1
]).
length
;
while
(
i
)
{
paramName
.
push
(
name
);
i
-=
1
;
}
});
if
(
!
paramName
.
length
)
{
paramName
=
[
fileInput
.
prop
(
'name'
)
||
'files[]'
];
}
}
else
if
(
!
$
.
isArray
(
paramName
))
{
paramName
=
[
paramName
];
}
return
paramName
;
},
_initFormSettings
:
function
(
options
)
{
// Retrieve missing options from the input field and the
// associated form, if available:
if
(
!
options
.
form
||
!
options
.
form
.
length
)
{
options
.
form
=
$
(
options
.
fileInput
.
prop
(
'form'
));
// If the given file input doesn't have an associated form,
// use the default widget file input's form:
if
(
!
options
.
form
.
length
)
{
options
.
form
=
$
(
this
.
options
.
fileInput
.
prop
(
'form'
));
}
}
options
.
paramName
=
this
.
_getParamName
(
options
);
if
(
!
options
.
url
)
{
options
.
url
=
options
.
form
.
prop
(
'action'
)
||
location
.
href
;
}
// The HTTP request method must be "POST" or "PUT":
options
.
type
=
(
options
.
type
||
(
$
.
type
(
options
.
form
.
prop
(
'method'
))
===
'string'
&&
options
.
form
.
prop
(
'method'
))
||
''
).
toUpperCase
();
if
(
options
.
type
!==
'POST'
&&
options
.
type
!==
'PUT'
&&
options
.
type
!==
'PATCH'
)
{
options
.
type
=
'POST'
;
}
if
(
!
options
.
formAcceptCharset
)
{
options
.
formAcceptCharset
=
options
.
form
.
attr
(
'accept-charset'
);
}
},
_getAJAXSettings
:
function
(
data
)
{
var
options
=
$
.
extend
({},
this
.
options
,
data
);
this
.
_initFormSettings
(
options
);
this
.
_initDataSettings
(
options
);
return
options
;
},
// jQuery 1.6 doesn't provide .state(),
// while jQuery 1.8+ removed .isRejected() and .isResolved():
_getDeferredState
:
function
(
deferred
)
{
if
(
deferred
.
state
)
{
return
deferred
.
state
();
}
if
(
deferred
.
isResolved
())
{
return
'resolved'
;
}
if
(
deferred
.
isRejected
())
{
return
'rejected'
;
}
return
'pending'
;
},
// Maps jqXHR callbacks to the equivalent
// methods of the given Promise object:
_enhancePromise
:
function
(
promise
)
{
promise
.
success
=
promise
.
done
;
promise
.
error
=
promise
.
fail
;
promise
.
complete
=
promise
.
always
;
return
promise
;
},
// Creates and returns a Promise object enhanced with
// the jqXHR methods abort, success, error and complete:
_getXHRPromise
:
function
(
resolveOrReject
,
context
,
args
)
{
var
dfd
=
$
.
Deferred
(),
promise
=
dfd
.
promise
();
context
=
context
||
this
.
options
.
context
||
promise
;
if
(
resolveOrReject
===
true
)
{
dfd
.
resolveWith
(
context
,
args
);
}
else
if
(
resolveOrReject
===
false
)
{
dfd
.
rejectWith
(
context
,
args
);
}
promise
.
abort
=
dfd
.
promise
;
return
this
.
_enhancePromise
(
promise
);
},
// Adds convenience methods to the data callback argument:
_addConvenienceMethods
:
function
(
e
,
data
)
{
var
that
=
this
,
getPromise
=
function
(
args
)
{
return
$
.
Deferred
().
resolveWith
(
that
,
args
).
promise
();
};
data
.
process
=
function
(
resolveFunc
,
rejectFunc
)
{
if
(
resolveFunc
||
rejectFunc
)
{
data
.
_processQueue
=
this
.
_processQueue
=
(
this
.
_processQueue
||
getPromise
([
this
])).
then
(
function
()
{
if
(
data
.
errorThrown
)
{
return
$
.
Deferred
()
.
rejectWith
(
that
,
[
data
]).
promise
();
}
return
getPromise
(
arguments
);
}
).
then
(
resolveFunc
,
rejectFunc
);
}
return
this
.
_processQueue
||
getPromise
([
this
]);
};
data
.
submit
=
function
()
{
if
(
this
.
state
()
!==
'pending'
)
{
data
.
jqXHR
=
this
.
jqXHR
=
(
that
.
_trigger
(
'submit'
,
$
.
Event
(
'submit'
,
{
delegatedEvent
:
e
}),
this
)
!==
false
)
&&
that
.
_onSend
(
e
,
this
);
}
return
this
.
jqXHR
||
that
.
_getXHRPromise
();
};
data
.
abort
=
function
()
{
if
(
this
.
jqXHR
)
{
return
this
.
jqXHR
.
abort
();
}
this
.
errorThrown
=
'abort'
;
that
.
_trigger
(
'fail'
,
null
,
this
);
return
that
.
_getXHRPromise
(
false
);
};
data
.
state
=
function
()
{
if
(
this
.
jqXHR
)
{
return
that
.
_getDeferredState
(
this
.
jqXHR
);
}
if
(
this
.
_processQueue
)
{
return
that
.
_getDeferredState
(
this
.
_processQueue
);
}
};
data
.
processing
=
function
()
{
return
!
this
.
jqXHR
&&
this
.
_processQueue
&&
that
.
_getDeferredState
(
this
.
_processQueue
)
===
'pending'
;
};
data
.
progress
=
function
()
{
return
this
.
_progress
;
};
data
.
response
=
function
()
{
return
this
.
_response
;
};
},
// Parses the Range header from the server response
// and returns the uploaded bytes:
_getUploadedBytes
:
function
(
jqXHR
)
{
var
range
=
jqXHR
.
getResponseHeader
(
'Range'
),
parts
=
range
&&
range
.
split
(
'-'
),
upperBytesPos
=
parts
&&
parts
.
length
>
1
&&
parseInt
(
parts
[
1
],
10
);
return
upperBytesPos
&&
upperBytesPos
+
1
;
},
// Uploads a file in multiple, sequential requests
// by splitting the file up in multiple blob chunks.
// If the second parameter is true, only tests if the file
// should be uploaded in chunks, but does not invoke any
// upload requests:
_chunkedUpload
:
function
(
options
,
testOnly
)
{
options
.
uploadedBytes
=
options
.
uploadedBytes
||
0
;
var
that
=
this
,
file
=
options
.
files
[
0
],
fs
=
file
.
size
,
ub
=
options
.
uploadedBytes
,
mcs
=
options
.
maxChunkSize
||
fs
,
slice
=
this
.
_blobSlice
,
dfd
=
$
.
Deferred
(),
promise
=
dfd
.
promise
(),
jqXHR
,
upload
;
if
(
!
(
this
.
_isXHRUpload
(
options
)
&&
slice
&&
(
ub
||
mcs
<
fs
))
||
options
.
data
)
{
return
false
;
}
if
(
testOnly
)
{
return
true
;
}
if
(
ub
>=
fs
)
{
file
.
error
=
options
.
i18n
(
'uploadedBytes'
);
return
this
.
_getXHRPromise
(
false
,
options
.
context
,
[
null
,
'error'
,
file
.
error
]
);
}
// The chunk upload method:
upload
=
function
()
{
// Clone the options object for each chunk upload:
var
o
=
$
.
extend
({},
options
),
currentLoaded
=
o
.
_progress
.
loaded
;
o
.
blob
=
slice
.
call
(
file
,
ub
,
ub
+
mcs
,
file
.
type
);
// Store the current chunk size, as the blob itself
// will be dereferenced after data processing:
o
.
chunkSize
=
o
.
blob
.
size
;
// Expose the chunk bytes position range:
o
.
contentRange
=
'bytes '
+
ub
+
'-'
+
(
ub
+
o
.
chunkSize
-
1
)
+
'/'
+
fs
;
// Process the upload data (the blob and potential form data):
that
.
_initXHRData
(
o
);
// Add progress listeners for this chunk upload:
that
.
_initProgressListener
(
o
);
jqXHR
=
((
that
.
_trigger
(
'chunksend'
,
null
,
o
)
!==
false
&&
$
.
ajax
(
o
))
||
that
.
_getXHRPromise
(
false
,
o
.
context
))
.
done
(
function
(
result
,
textStatus
,
jqXHR
)
{
ub
=
that
.
_getUploadedBytes
(
jqXHR
)
||
(
ub
+
o
.
chunkSize
);
// Create a progress event if no final progress event
// with loaded equaling total has been triggered
// for this chunk:
if
(
currentLoaded
+
o
.
chunkSize
-
o
.
_progress
.
loaded
)
{
that
.
_onProgress
(
$
.
Event
(
'progress'
,
{
lengthComputable
:
true
,
loaded
:
ub
-
o
.
uploadedBytes
,
total
:
ub
-
o
.
uploadedBytes
}),
o
);
}
options
.
uploadedBytes
=
o
.
uploadedBytes
=
ub
;
o
.
result
=
result
;
o
.
textStatus
=
textStatus
;
o
.
jqXHR
=
jqXHR
;
that
.
_trigger
(
'chunkdone'
,
null
,
o
);
that
.
_trigger
(
'chunkalways'
,
null
,
o
);
if
(
ub
<
fs
)
{
// File upload not yet complete,
// continue with the next chunk:
upload
();
}
else
{
dfd
.
resolveWith
(
o
.
context
,
[
result
,
textStatus
,
jqXHR
]
);
}
})
.
fail
(
function
(
jqXHR
,
textStatus
,
errorThrown
)
{
o
.
jqXHR
=
jqXHR
;
o
.
textStatus
=
textStatus
;
o
.
errorThrown
=
errorThrown
;
that
.
_trigger
(
'chunkfail'
,
null
,
o
);
that
.
_trigger
(
'chunkalways'
,
null
,
o
);
dfd
.
rejectWith
(
o
.
context
,
[
jqXHR
,
textStatus
,
errorThrown
]
);
});
};
this
.
_enhancePromise
(
promise
);
promise
.
abort
=
function
()
{
return
jqXHR
.
abort
();
};
upload
();
return
promise
;
},
_beforeSend
:
function
(
e
,
data
)
{
if
(
this
.
_active
===
0
)
{
// the start callback is triggered when an upload starts
// and no other uploads are currently running,
// equivalent to the global ajaxStart event:
this
.
_trigger
(
'start'
);
// Set timer for global bitrate progress calculation:
this
.
_bitrateTimer
=
new
this
.
_BitrateTimer
();
// Reset the global progress values:
this
.
_progress
.
loaded
=
this
.
_progress
.
total
=
0
;
this
.
_progress
.
bitrate
=
0
;
}
// Make sure the container objects for the .response() and
// .progress() methods on the data object are available
// and reset to their initial state:
this
.
_initResponseObject
(
data
);
this
.
_initProgressObject
(
data
);
data
.
_progress
.
loaded
=
data
.
loaded
=
data
.
uploadedBytes
||
0
;
data
.
_progress
.
total
=
data
.
total
=
this
.
_getTotal
(
data
.
files
)
||
1
;
data
.
_progress
.
bitrate
=
data
.
bitrate
=
0
;
this
.
_active
+=
1
;
// Initialize the global progress values:
this
.
_progress
.
loaded
+=
data
.
loaded
;
this
.
_progress
.
total
+=
data
.
total
;
},
_onDone
:
function
(
result
,
textStatus
,
jqXHR
,
options
)
{
var
total
=
options
.
_progress
.
total
,
response
=
options
.
_response
;
if
(
options
.
_progress
.
loaded
<
total
)
{
// Create a progress event if no final progress event
// with loaded equaling total has been triggered:
this
.
_onProgress
(
$
.
Event
(
'progress'
,
{
lengthComputable
:
true
,
loaded
:
total
,
total
:
total
}),
options
);
}
response
.
result
=
options
.
result
=
result
;
response
.
textStatus
=
options
.
textStatus
=
textStatus
;
response
.
jqXHR
=
options
.
jqXHR
=
jqXHR
;
this
.
_trigger
(
'done'
,
null
,
options
);
},
_onFail
:
function
(
jqXHR
,
textStatus
,
errorThrown
,
options
)
{
var
response
=
options
.
_response
;
if
(
options
.
recalculateProgress
)
{
// Remove the failed (error or abort) file upload from
// the global progress calculation:
this
.
_progress
.
loaded
-=
options
.
_progress
.
loaded
;
this
.
_progress
.
total
-=
options
.
_progress
.
total
;
}
response
.
jqXHR
=
options
.
jqXHR
=
jqXHR
;
response
.
textStatus
=
options
.
textStatus
=
textStatus
;
response
.
errorThrown
=
options
.
errorThrown
=
errorThrown
;
this
.
_trigger
(
'fail'
,
null
,
options
);
},
_onAlways
:
function
(
jqXHRorResult
,
textStatus
,
jqXHRorError
,
options
)
{
// jqXHRorResult, textStatus and jqXHRorError are added to the
// options object via done and fail callbacks
this
.
_trigger
(
'always'
,
null
,
options
);
},
_onSend
:
function
(
e
,
data
)
{
if
(
!
data
.
submit
)
{
this
.
_addConvenienceMethods
(
e
,
data
);
}
var
that
=
this
,
jqXHR
,
aborted
,
slot
,
pipe
,
options
=
that
.
_getAJAXSettings
(
data
),
send
=
function
()
{
that
.
_sending
+=
1
;
// Set timer for bitrate progress calculation:
options
.
_bitrateTimer
=
new
that
.
_BitrateTimer
();
jqXHR
=
jqXHR
||
(
((
aborted
||
that
.
_trigger
(
'send'
,
$
.
Event
(
'send'
,
{
delegatedEvent
:
e
}),
options
)
===
false
)
&&
that
.
_getXHRPromise
(
false
,
options
.
context
,
aborted
))
||
that
.
_chunkedUpload
(
options
)
||
$
.
ajax
(
options
)
).
done
(
function
(
result
,
textStatus
,
jqXHR
)
{
that
.
_onDone
(
result
,
textStatus
,
jqXHR
,
options
);
}).
fail
(
function
(
jqXHR
,
textStatus
,
errorThrown
)
{
that
.
_onFail
(
jqXHR
,
textStatus
,
errorThrown
,
options
);
}).
always
(
function
(
jqXHRorResult
,
textStatus
,
jqXHRorError
)
{
that
.
_onAlways
(
jqXHRorResult
,
textStatus
,
jqXHRorError
,
options
);
that
.
_sending
-=
1
;
that
.
_active
-=
1
;
if
(
options
.
limitConcurrentUploads
&&
options
.
limitConcurrentUploads
>
that
.
_sending
)
{
// Start the next queued upload,
// that has not been aborted:
var
nextSlot
=
that
.
_slots
.
shift
();
while
(
nextSlot
)
{
if
(
that
.
_getDeferredState
(
nextSlot
)
===
'pending'
)
{
nextSlot
.
resolve
();
break
;
}
nextSlot
=
that
.
_slots
.
shift
();
}
}
if
(
that
.
_active
===
0
)
{
// The stop callback is triggered when all uploads have
// been completed, equivalent to the global ajaxStop event:
that
.
_trigger
(
'stop'
);
}
});
return
jqXHR
;
};
this
.
_beforeSend
(
e
,
options
);
if
(
this
.
options
.
sequentialUploads
||
(
this
.
options
.
limitConcurrentUploads
&&
this
.
options
.
limitConcurrentUploads
<=
this
.
_sending
))
{
if
(
this
.
options
.
limitConcurrentUploads
>
1
)
{
slot
=
$
.
Deferred
();
this
.
_slots
.
push
(
slot
);
pipe
=
slot
.
then
(
send
);
}
else
{
this
.
_sequence
=
this
.
_sequence
.
then
(
send
,
send
);
pipe
=
this
.
_sequence
;
}
// Return the piped Promise object, enhanced with an abort method,
// which is delegated to the jqXHR object of the current upload,
// and jqXHR callbacks mapped to the equivalent Promise methods:
pipe
.
abort
=
function
()
{
aborted
=
[
undefined
,
'abort'
,
'abort'
];
if
(
!
jqXHR
)
{
if
(
slot
)
{
slot
.
rejectWith
(
options
.
context
,
aborted
);
}
return
send
();
}
return
jqXHR
.
abort
();
};
return
this
.
_enhancePromise
(
pipe
);
}
return
send
();
},
_onAdd
:
function
(
e
,
data
)
{
var
that
=
this
,
result
=
true
,
options
=
$
.
extend
({},
this
.
options
,
data
),
files
=
data
.
files
,
filesLength
=
files
.
length
,
limit
=
options
.
limitMultiFileUploads
,
limitSize
=
options
.
limitMultiFileUploadSize
,
overhead
=
options
.
limitMultiFileUploadSizeOverhead
,
batchSize
=
0
,
paramName
=
this
.
_getParamName
(
options
),
paramNameSet
,
paramNameSlice
,
fileSet
,
i
,
j
=
0
;
if
(
!
filesLength
)
{
return
false
;
}
if
(
limitSize
&&
files
[
0
].
size
===
undefined
)
{
limitSize
=
undefined
;
}
if
(
!
(
options
.
singleFileUploads
||
limit
||
limitSize
)
||
!
this
.
_isXHRUpload
(
options
))
{
fileSet
=
[
files
];
paramNameSet
=
[
paramName
];
}
else
if
(
!
(
options
.
singleFileUploads
||
limitSize
)
&&
limit
)
{
fileSet
=
[];
paramNameSet
=
[];
for
(
i
=
0
;
i
<
filesLength
;
i
+=
limit
)
{
fileSet
.
push
(
files
.
slice
(
i
,
i
+
limit
));
paramNameSlice
=
paramName
.
slice
(
i
,
i
+
limit
);
if
(
!
paramNameSlice
.
length
)
{
paramNameSlice
=
paramName
;
}
paramNameSet
.
push
(
paramNameSlice
);
}
}
else
if
(
!
options
.
singleFileUploads
&&
limitSize
)
{
fileSet
=
[];
paramNameSet
=
[];
for
(
i
=
0
;
i
<
filesLength
;
i
=
i
+
1
)
{
batchSize
+=
files
[
i
].
size
+
overhead
;
if
(
i
+
1
===
filesLength
||
((
batchSize
+
files
[
i
+
1
].
size
+
overhead
)
>
limitSize
)
||
(
limit
&&
i
+
1
-
j
>=
limit
))
{
fileSet
.
push
(
files
.
slice
(
j
,
i
+
1
));
paramNameSlice
=
paramName
.
slice
(
j
,
i
+
1
);
if
(
!
paramNameSlice
.
length
)
{
paramNameSlice
=
paramName
;
}
paramNameSet
.
push
(
paramNameSlice
);
j
=
i
+
1
;
batchSize
=
0
;
}
}
}
else
{
paramNameSet
=
paramName
;
}
data
.
originalFiles
=
files
;
$
.
each
(
fileSet
||
files
,
function
(
index
,
element
)
{
var
newData
=
$
.
extend
({},
data
);
newData
.
files
=
fileSet
?
element
:
[
element
];
newData
.
paramName
=
paramNameSet
[
index
];
that
.
_initResponseObject
(
newData
);
that
.
_initProgressObject
(
newData
);
that
.
_addConvenienceMethods
(
e
,
newData
);
result
=
that
.
_trigger
(
'add'
,
$
.
Event
(
'add'
,
{
delegatedEvent
:
e
}),
newData
);
return
result
;
});
return
result
;
},
_replaceFileInput
:
function
(
data
)
{
var
input
=
data
.
fileInput
,
inputClone
=
input
.
clone
(
true
),
restoreFocus
=
input
.
is
(
document
.
activeElement
);
// Add a reference for the new cloned file input to the data argument:
data
.
fileInputClone
=
inputClone
;
$
(
'<form></form>'
).
append
(
inputClone
)[
0
].
reset
();
// Detaching allows to insert the fileInput on another form
// without loosing the file input value:
input
.
after
(
inputClone
).
detach
();
// If the fileInput had focus before it was detached,
// restore focus to the inputClone.
if
(
restoreFocus
)
{
inputClone
.
focus
();
}
// Avoid memory leaks with the detached file input:
$
.
cleanData
(
input
.
unbind
(
'remove'
));
// Replace the original file input element in the fileInput
// elements set with the clone, which has been copied including
// event handlers:
this
.
options
.
fileInput
=
this
.
options
.
fileInput
.
map
(
function
(
i
,
el
)
{
if
(
el
===
input
[
0
])
{
return
inputClone
[
0
];
}
return
el
;
});
// If the widget has been initialized on the file input itself,
// override this.element with the file input clone:
if
(
input
[
0
]
===
this
.
element
[
0
])
{
this
.
element
=
inputClone
;
}
},
_handleFileTreeEntry
:
function
(
entry
,
path
)
{
var
that
=
this
,
dfd
=
$
.
Deferred
(),
entries
=
[],
dirReader
,
errorHandler
=
function
(
e
)
{
if
(
e
&&
!
e
.
entry
)
{
e
.
entry
=
entry
;
}
// Since $.when returns immediately if one
// Deferred is rejected, we use resolve instead.
// This allows valid files and invalid items
// to be returned together in one set:
dfd
.
resolve
([
e
]);
},
successHandler
=
function
(
entries
)
{
that
.
_handleFileTreeEntries
(
entries
,
path
+
entry
.
name
+
'/'
).
done
(
function
(
files
)
{
dfd
.
resolve
(
files
);
}).
fail
(
errorHandler
);
},
readEntries
=
function
()
{
dirReader
.
readEntries
(
function
(
results
)
{
if
(
!
results
.
length
)
{
successHandler
(
entries
);
}
else
{
entries
=
entries
.
concat
(
results
);
readEntries
();
}
},
errorHandler
);
};
path
=
path
||
''
;
if
(
entry
.
isFile
)
{
if
(
entry
.
_file
)
{
// Workaround for Chrome bug #149735
entry
.
_file
.
relativePath
=
path
;
dfd
.
resolve
(
entry
.
_file
);
}
else
{
entry
.
file
(
function
(
file
)
{
file
.
relativePath
=
path
;
dfd
.
resolve
(
file
);
},
errorHandler
);
}
}
else
if
(
entry
.
isDirectory
)
{
dirReader
=
entry
.
createReader
();
readEntries
();
}
else
{
// Return an empy list for file system items
// other than files or directories:
dfd
.
resolve
([]);
}
return
dfd
.
promise
();
},
_handleFileTreeEntries
:
function
(
entries
,
path
)
{
var
that
=
this
;
return
$
.
when
.
apply
(
$
,
$
.
map
(
entries
,
function
(
entry
)
{
return
that
.
_handleFileTreeEntry
(
entry
,
path
);
})
).
then
(
function
()
{
return
Array
.
prototype
.
concat
.
apply
(
[],
arguments
);
});
},
_getDroppedFiles
:
function
(
dataTransfer
)
{
dataTransfer
=
dataTransfer
||
{};
var
items
=
dataTransfer
.
items
;
if
(
items
&&
items
.
length
&&
(
items
[
0
].
webkitGetAsEntry
||
items
[
0
].
getAsEntry
))
{
return
this
.
_handleFileTreeEntries
(
$
.
map
(
items
,
function
(
item
)
{
var
entry
;
if
(
item
.
webkitGetAsEntry
)
{
entry
=
item
.
webkitGetAsEntry
();
if
(
entry
)
{
// Workaround for Chrome bug #149735:
entry
.
_file
=
item
.
getAsFile
();
}
return
entry
;
}
return
item
.
getAsEntry
();
})
);
}
return
$
.
Deferred
().
resolve
(
$
.
makeArray
(
dataTransfer
.
files
)
).
promise
();
},
_getSingleFileInputFiles
:
function
(
fileInput
)
{
fileInput
=
$
(
fileInput
);
var
entries
=
fileInput
.
prop
(
'webkitEntries'
)
||
fileInput
.
prop
(
'entries'
),
files
,
value
;
if
(
entries
&&
entries
.
length
)
{
return
this
.
_handleFileTreeEntries
(
entries
);
}
files
=
$
.
makeArray
(
fileInput
.
prop
(
'files'
));
if
(
!
files
.
length
)
{
value
=
fileInput
.
prop
(
'value'
);
if
(
!
value
)
{
return
$
.
Deferred
().
resolve
([]).
promise
();
}
// If the files property is not available, the browser does not
// support the File API and we add a pseudo File object with
// the input value as name with path information removed:
files
=
[{
name
:
value
.
replace
(
/^.*
\\
/
,
''
)}];
}
else
if
(
files
[
0
].
name
===
undefined
&&
files
[
0
].
fileName
)
{
// File normalization for Safari 4 and Firefox 3:
$
.
each
(
files
,
function
(
index
,
file
)
{
file
.
name
=
file
.
fileName
;
file
.
size
=
file
.
fileSize
;
});
}
return
$
.
Deferred
().
resolve
(
files
).
promise
();
},
_getFileInputFiles
:
function
(
fileInput
)
{
if
(
!
(
fileInput
instanceof
$
)
||
fileInput
.
length
===
1
)
{
return
this
.
_getSingleFileInputFiles
(
fileInput
);
}
return
$
.
when
.
apply
(
$
,
$
.
map
(
fileInput
,
this
.
_getSingleFileInputFiles
)
).
then
(
function
()
{
return
Array
.
prototype
.
concat
.
apply
(
[],
arguments
);
});
},
_onChange
:
function
(
e
)
{
var
that
=
this
,
data
=
{
fileInput
:
$
(
e
.
target
),
form
:
$
(
e
.
target
.
form
)
};
this
.
_getFileInputFiles
(
data
.
fileInput
).
always
(
function
(
files
)
{
data
.
files
=
files
;
if
(
that
.
options
.
replaceFileInput
)
{
that
.
_replaceFileInput
(
data
);
}
if
(
that
.
_trigger
(
'change'
,
$
.
Event
(
'change'
,
{
delegatedEvent
:
e
}),
data
)
!==
false
)
{
that
.
_onAdd
(
e
,
data
);
}
});
},
_onPaste
:
function
(
e
)
{
var
items
=
e
.
originalEvent
&&
e
.
originalEvent
.
clipboardData
&&
e
.
originalEvent
.
clipboardData
.
items
,
data
=
{
files
:
[]};
if
(
items
&&
items
.
length
)
{
$
.
each
(
items
,
function
(
index
,
item
)
{
var
file
=
item
.
getAsFile
&&
item
.
getAsFile
();
if
(
file
)
{
data
.
files
.
push
(
file
);
}
});
if
(
this
.
_trigger
(
'paste'
,
$
.
Event
(
'paste'
,
{
delegatedEvent
:
e
}),
data
)
!==
false
)
{
this
.
_onAdd
(
e
,
data
);
}
}
},
_onDrop
:
function
(
e
)
{
e
.
dataTransfer
=
e
.
originalEvent
&&
e
.
originalEvent
.
dataTransfer
;
var
that
=
this
,
dataTransfer
=
e
.
dataTransfer
,
data
=
{};
if
(
dataTransfer
&&
dataTransfer
.
files
&&
dataTransfer
.
files
.
length
)
{
e
.
preventDefault
();
this
.
_getDroppedFiles
(
dataTransfer
).
always
(
function
(
files
)
{
data
.
files
=
files
;
if
(
that
.
_trigger
(
'drop'
,
$
.
Event
(
'drop'
,
{
delegatedEvent
:
e
}),
data
)
!==
false
)
{
that
.
_onAdd
(
e
,
data
);
}
});
}
},
_onDragOver
:
getDragHandler
(
'dragover'
),
_onDragEnter
:
getDragHandler
(
'dragenter'
),
_onDragLeave
:
getDragHandler
(
'dragleave'
),
_initEventHandlers
:
function
()
{
if
(
this
.
_isXHRUpload
(
this
.
options
))
{
this
.
_on
(
this
.
options
.
dropZone
,
{
dragover
:
this
.
_onDragOver
,
drop
:
this
.
_onDrop
,
// event.preventDefault() on dragenter is required for IE10+:
dragenter
:
this
.
_onDragEnter
,
// dragleave is not required, but added for completeness:
dragleave
:
this
.
_onDragLeave
});
this
.
_on
(
this
.
options
.
pasteZone
,
{
paste
:
this
.
_onPaste
});
}
if
(
$
.
support
.
fileInput
)
{
this
.
_on
(
this
.
options
.
fileInput
,
{
change
:
this
.
_onChange
});
}
},
_destroyEventHandlers
:
function
()
{
this
.
_off
(
this
.
options
.
dropZone
,
'dragenter dragleave dragover drop'
);
this
.
_off
(
this
.
options
.
pasteZone
,
'paste'
);
this
.
_off
(
this
.
options
.
fileInput
,
'change'
);
},
_destroy
:
function
()
{
this
.
_destroyEventHandlers
();
},
_setOption
:
function
(
key
,
value
)
{
var
reinit
=
$
.
inArray
(
key
,
this
.
_specialOptions
)
!==
-
1
;
if
(
reinit
)
{
this
.
_destroyEventHandlers
();
}
this
.
_super
(
key
,
value
);
if
(
reinit
)
{
this
.
_initSpecialOptions
();
this
.
_initEventHandlers
();
}
},
_initSpecialOptions
:
function
()
{
var
options
=
this
.
options
;
if
(
options
.
fileInput
===
undefined
)
{
options
.
fileInput
=
this
.
element
.
is
(
'input[type="file"]'
)
?
this
.
element
:
this
.
element
.
find
(
'input[type="file"]'
);
}
else
if
(
!
(
options
.
fileInput
instanceof
$
))
{
options
.
fileInput
=
$
(
options
.
fileInput
);
}
if
(
!
(
options
.
dropZone
instanceof
$
))
{
options
.
dropZone
=
$
(
options
.
dropZone
);
}
if
(
!
(
options
.
pasteZone
instanceof
$
))
{
options
.
pasteZone
=
$
(
options
.
pasteZone
);
}
},
_getRegExp
:
function
(
str
)
{
var
parts
=
str
.
split
(
'/'
),
modifiers
=
parts
.
pop
();
parts
.
shift
();
return
new
RegExp
(
parts
.
join
(
'/'
),
modifiers
);
},
_isRegExpOption
:
function
(
key
,
value
)
{
return
key
!==
'url'
&&
$
.
type
(
value
)
===
'string'
&&
/^
\/
.*
\/[
igm
]{0,3}
$/
.
test
(
value
);
},
_initDataAttributes
:
function
()
{
var
that
=
this
,
options
=
this
.
options
,
data
=
this
.
element
.
data
();
// Initialize options set via HTML5 data-attributes:
$
.
each
(
this
.
element
[
0
].
attributes
,
function
(
index
,
attr
)
{
var
key
=
attr
.
name
.
toLowerCase
(),
value
;
if
(
/^data-/
.
test
(
key
))
{
// Convert hyphen-ated key to camelCase:
key
=
key
.
slice
(
5
).
replace
(
/-
[
a-z
]
/g
,
function
(
str
)
{
return
str
.
charAt
(
1
).
toUpperCase
();
});
value
=
data
[
key
];
if
(
that
.
_isRegExpOption
(
key
,
value
))
{
value
=
that
.
_getRegExp
(
value
);
}
options
[
key
]
=
value
;
}
}
);
},
_create
:
function
()
{
this
.
_initDataAttributes
();
this
.
_initSpecialOptions
();
this
.
_slots
=
[];
this
.
_sequence
=
this
.
_getXHRPromise
(
true
);
this
.
_sending
=
this
.
_active
=
0
;
this
.
_initProgressObject
(
this
);
this
.
_initEventHandlers
();
},
// This method is exposed to the widget API and allows to query
// the number of active uploads:
active
:
function
()
{
return
this
.
_active
;
},
// This method is exposed to the widget API and allows to query
// the widget upload progress.
// It returns an object with loaded, total and bitrate properties
// for the running uploads:
progress
:
function
()
{
return
this
.
_progress
;
},
// This method is exposed to the widget API and allows adding files
// using the fileupload API. The data parameter accepts an object which
// must have a files property and can contain additional options:
// .fileupload('add', {files: filesList});
add
:
function
(
data
)
{
var
that
=
this
;
if
(
!
data
||
this
.
options
.
disabled
)
{
return
;
}
if
(
data
.
fileInput
&&
!
data
.
files
)
{
this
.
_getFileInputFiles
(
data
.
fileInput
).
always
(
function
(
files
)
{
data
.
files
=
files
;
that
.
_onAdd
(
null
,
data
);
});
}
else
{
data
.
files
=
$
.
makeArray
(
data
.
files
);
this
.
_onAdd
(
null
,
data
);
}
},
// This method is exposed to the widget API and allows sending files
// using the fileupload API. The data parameter accepts an object which
// must have a files or fileInput property and can contain additional options:
// .fileupload('send', {files: filesList});
// The method returns a Promise object for the file upload call.
send
:
function
(
data
)
{
if
(
data
&&
!
this
.
options
.
disabled
)
{
if
(
data
.
fileInput
&&
!
data
.
files
)
{
var
that
=
this
,
dfd
=
$
.
Deferred
(),
promise
=
dfd
.
promise
(),
jqXHR
,
aborted
;
promise
.
abort
=
function
()
{
aborted
=
true
;
if
(
jqXHR
)
{
return
jqXHR
.
abort
();
}
dfd
.
reject
(
null
,
'abort'
,
'abort'
);
return
promise
;
};
this
.
_getFileInputFiles
(
data
.
fileInput
).
always
(
function
(
files
)
{
if
(
aborted
)
{
return
;
}
if
(
!
files
.
length
)
{
dfd
.
reject
();
return
;
}
data
.
files
=
files
;
jqXHR
=
that
.
_onSend
(
null
,
data
);
jqXHR
.
then
(
function
(
result
,
textStatus
,
jqXHR
)
{
dfd
.
resolve
(
result
,
textStatus
,
jqXHR
);
},
function
(
jqXHR
,
textStatus
,
errorThrown
)
{
dfd
.
reject
(
jqXHR
,
textStatus
,
errorThrown
);
}
);
}
);
return
this
.
_enhancePromise
(
promise
);
}
data
.
files
=
$
.
makeArray
(
data
.
files
);
if
(
data
.
files
.
length
)
{
return
this
.
_onSend
(
null
,
data
);
}
}
return
this
.
_getXHRPromise
(
false
,
data
&&
data
.
context
);
}
});
}));
src/update/fileupload/jquery.iframe-transport.js
0 → 100644
View file @
5f4bb7de
/*
* jQuery Iframe Transport Plugin
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/* global define, require, window, document */
;(
function
(
factory
)
{
'use strict'
;
if
(
typeof
define
===
'function'
&&
define
.
amd
)
{
// Register as an anonymous AMD module:
define
([
'jquery'
],
factory
);
}
else
if
(
typeof
exports
===
'object'
)
{
// Node/CommonJS:
factory
(
require
(
'jquery'
));
}
else
{
// Browser globals:
factory
(
window
.
jQuery
);
}
}(
function
(
$
)
{
'use strict'
;
// Helper variable to create unique names for the transport iframes:
var
counter
=
0
;
// The iframe transport accepts four additional options:
// options.fileInput: a jQuery collection of file input fields
// options.paramName: the parameter name for the file form data,
// overrides the name property of the file input field(s),
// can be a string or an array of strings.
// options.formData: an array of objects with name and value properties,
// equivalent to the return data of .serializeArray(), e.g.:
// [{name: 'a', value: 1}, {name: 'b', value: 2}]
// options.initialIframeSrc: the URL of the initial iframe src,
// by default set to "javascript:false;"
$
.
ajaxTransport
(
'iframe'
,
function
(
options
)
{
if
(
options
.
async
)
{
// javascript:false as initial iframe src
// prevents warning popups on HTTPS in IE6:
/*jshint scripturl: true */
var
initialIframeSrc
=
options
.
initialIframeSrc
||
'javascript:false;'
,
/*jshint scripturl: false */
form
,
iframe
,
addParamChar
;
return
{
send
:
function
(
_
,
completeCallback
)
{
form
=
$
(
'<form style="display:none;"></form>'
);
form
.
attr
(
'accept-charset'
,
options
.
formAcceptCharset
);
addParamChar
=
/
\?
/
.
test
(
options
.
url
)
?
'&'
:
'?'
;
// XDomainRequest only supports GET and POST:
if
(
options
.
type
===
'DELETE'
)
{
options
.
url
=
options
.
url
+
addParamChar
+
'_method=DELETE'
;
options
.
type
=
'POST'
;
}
else
if
(
options
.
type
===
'PUT'
)
{
options
.
url
=
options
.
url
+
addParamChar
+
'_method=PUT'
;
options
.
type
=
'POST'
;
}
else
if
(
options
.
type
===
'PATCH'
)
{
options
.
url
=
options
.
url
+
addParamChar
+
'_method=PATCH'
;
options
.
type
=
'POST'
;
}
// IE versions below IE8 cannot set the name property of
// elements that have already been added to the DOM,
// so we set the name along with the iframe HTML markup:
counter
+=
1
;
iframe
=
$
(
'<iframe src="'
+
initialIframeSrc
+
'" name="iframe-transport-'
+
counter
+
'"></iframe>'
).
bind
(
'load'
,
function
()
{
var
fileInputClones
,
paramNames
=
$
.
isArray
(
options
.
paramName
)
?
options
.
paramName
:
[
options
.
paramName
];
iframe
.
unbind
(
'load'
)
.
bind
(
'load'
,
function
()
{
var
response
;
// Wrap in a try/catch block to catch exceptions thrown
// when trying to access cross-domain iframe contents:
try
{
response
=
iframe
.
contents
();
// Google Chrome and Firefox do not throw an
// exception when calling iframe.contents() on
// cross-domain requests, so we unify the response:
if
(
!
response
.
length
||
!
response
[
0
].
firstChild
)
{
throw
new
Error
();
}
}
catch
(
e
)
{
response
=
undefined
;
}
// The complete callback returns the
// iframe content document as response object:
completeCallback
(
200
,
'success'
,
{
'iframe'
:
response
}
);
// Fix for IE endless progress bar activity bug
// (happens on form submits to iframe targets):
$
(
'<iframe src="'
+
initialIframeSrc
+
'"></iframe>'
)
.
appendTo
(
form
);
window
.
setTimeout
(
function
()
{
// Removing the form in a setTimeout call
// allows Chrome's developer tools to display
// the response result
form
.
remove
();
},
0
);
});
form
.
prop
(
'target'
,
iframe
.
prop
(
'name'
))
.
prop
(
'action'
,
options
.
url
)
.
prop
(
'method'
,
options
.
type
);
if
(
options
.
formData
)
{
$
.
each
(
options
.
formData
,
function
(
index
,
field
)
{
$
(
'<input type="hidden"/>'
)
.
prop
(
'name'
,
field
.
name
)
.
val
(
field
.
value
)
.
appendTo
(
form
);
});
}
if
(
options
.
fileInput
&&
options
.
fileInput
.
length
&&
options
.
type
===
'POST'
)
{
fileInputClones
=
options
.
fileInput
.
clone
();
// Insert a clone for each file input field:
options
.
fileInput
.
after
(
function
(
index
)
{
return
fileInputClones
[
index
];
});
if
(
options
.
paramName
)
{
options
.
fileInput
.
each
(
function
(
index
)
{
$
(
this
).
prop
(
'name'
,
paramNames
[
index
]
||
options
.
paramName
);
});
}
// Appending the file input fields to the hidden form
// removes them from their original location:
form
.
append
(
options
.
fileInput
)
.
prop
(
'enctype'
,
'multipart/form-data'
)
// enctype must be set as encoding for IE:
.
prop
(
'encoding'
,
'multipart/form-data'
);
// Remove the HTML5 form attribute from the input(s):
options
.
fileInput
.
removeAttr
(
'form'
);
}
form
.
submit
();
// Insert the file input fields at their original location
// by replacing the clones with the originals:
if
(
fileInputClones
&&
fileInputClones
.
length
)
{
options
.
fileInput
.
each
(
function
(
index
,
input
)
{
var
clone
=
$
(
fileInputClones
[
index
]);
// Restore the original name and form properties:
$
(
input
)
.
prop
(
'name'
,
clone
.
prop
(
'name'
))
.
attr
(
'form'
,
clone
.
attr
(
'form'
));
clone
.
replaceWith
(
input
);
});
}
});
form
.
append
(
iframe
).
appendTo
(
document
.
body
);
},
abort
:
function
()
{
if
(
iframe
)
{
// javascript:false as iframe src aborts the request
// and prevents warning popups on HTTPS in IE6.
// concat is used to avoid the "Script URL" JSLint error:
iframe
.
unbind
(
'load'
)
.
prop
(
'src'
,
initialIframeSrc
);
}
if
(
form
)
{
form
.
remove
();
}
}
};
}
});
// The iframe transport returns the iframe content document as response.
// The following adds converters from iframe to text, json, html, xml
// and script.
// Please note that the Content-Type for JSON responses has to be text/plain
// or text/html, if the browser doesn't include application/json in the
// Accept header, else IE will show a download dialog.
// The Content-Type for XML responses on the other hand has to be always
// application/xml or text/xml, so IE properly parses the XML response.
// See also
// https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
$
.
ajaxSetup
({
converters
:
{
'iframe text'
:
function
(
iframe
)
{
return
iframe
&&
$
(
iframe
[
0
].
body
).
text
();
},
'iframe json'
:
function
(
iframe
)
{
return
iframe
&&
$
.
parseJSON
(
$
(
iframe
[
0
].
body
).
text
());
},
'iframe html'
:
function
(
iframe
)
{
return
iframe
&&
$
(
iframe
[
0
].
body
).
html
();
},
'iframe xml'
:
function
(
iframe
)
{
var
xmlDoc
=
iframe
&&
iframe
[
0
];
return
xmlDoc
&&
$
.
isXMLDoc
(
xmlDoc
)
?
xmlDoc
:
$
.
parseXML
((
xmlDoc
.
XMLDocument
&&
xmlDoc
.
XMLDocument
.
xml
)
||
$
(
xmlDoc
.
body
).
html
());
},
'iframe script'
:
function
(
iframe
)
{
return
iframe
&&
$
.
globalEval
(
$
(
iframe
[
0
].
body
).
text
());
}
}
});
}));
src/update/fileupload/style.css
0 → 100644
View file @
5f4bb7de
@charset
"UTF-8"
;
/*
* jQuery File Upload Plugin CSS Example
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
body
{
padding-top
:
60px
;
}
src/update/update_nand.php
0 → 100644
View file @
5f4bb7de
<?php
# hardcoded
$UPDATE_DIR
=
"/var/volatile/html/update"
;
$NAND_PATH
=
"/tmp/rootfs.ro"
;
$FLASH_LOG
=
"/var/volatile/html/flash.log"
;
$FLASH_LOG_LINK
=
"var/flash.log"
;
$UBI_MNT
=
"/tmp/ubi0"
;
$BKP_NAME
=
"elphel393_backup.tar.gz"
;
$BKP_DIR
=
"/etc/elphel393"
;
# update files
# file, expertise level, nand partition, size - see http://wiki.elphel.com/index.php?title=NAND_flash_boot_rootfs
# partitions are also listed in the device tree
# WARNING: DO NOT CHANGE
$UPDATE_LIST
=
array
(
array
(
0
,
"boot.bin"
,
"/dev/mtd0"
,
"0 2"
),
array
(
0
,
"u-boot-dtb.img"
,
"/dev/mtd1"
,
"0 8"
),
array
(
0
,
"devicetree.dtb"
,
"/dev/mtd2"
,
"0 8"
),
array
(
0
,
"uImage"
,
"/dev/mtd3"
,
"0 128"
),
array
(
1
,
"rootfs.tar.gz"
,
""
,
""
),
array
(
0
,
"rootfs.ubi"
,
"/dev/mtd4"
,
"0 2048"
,
"-s 2048 -O 2048"
),
array
(
1
,
"rootfs.ubifs"
,
"/dev/mtd4"
,
"/dev/ubi_ctrl -m 4"
,
"ubiupdatevol /dev/ubi0_0"
),
);
//respond_xml($updatedir);
function
lookup_name
(
$name
){
global
$UPDATE_LIST
;
foreach
(
$UPDATE_LIST
as
$e
){
if
(
$name
==
$e
[
1
])
return
$e
;
}
return
false
;
}
function
verify
(
$v
){
global
$UPDATE_DIR
;
global
$NAND_PATH
;
global
$UPDATE_LIST
;
$safe_list
=
array
();
if
(
is_dir
(
$UPDATE_DIR
)){
foreach
(
scandir
(
$UPDATE_DIR
)
as
$e
){
$tmp
=
lookup_name
(
$e
);
if
(
$tmp
!=
false
){
if
(
$tmp
[
0
]
==
0
){
array_push
(
$safe_list
,
$tmp
);
}
}
}
}
if
(
count
(
$safe_list
)
==
0
){
$tmp
=
""
;
foreach
(
$UPDATE_LIST
as
$e
){
if
(
$e
[
0
]
==
0
){
$tmp
.=
"<li>${e[1]}</li>"
;
}
}
backup_note
();
$msg
=
<<<TXT
<b style='color:red;'>ERROR</b>: Files not found. Accepted files are:
<ul>
$tmp
</ul>
TXT;
die
(
$msg
);
}
else
{
$tmp
=
""
;
foreach
(
$safe_list
as
$e
){
$tmp
.=
"<li>
{
$e
[
1
]
}
</li>"
;
}
if
(
$v
)
printf
(
"Files to be flashed:<ul>
$tmp
</ul>"
);
}
//$safe_list is ready
if
(
is_dir
(
$NAND_PATH
)){
backup_note
();
die
(
"<b style='color:red'>ERROR</b>: Please boot from mmc (<a href='http://wiki.elphel.com/index.php?title=Tmp_manual#Boot'>instructions</a>)"
);
}
else
{
if
(
$v
)
{
backup_note
();
printf
(
"<span style='color:green'>Ready for flashing.</span>"
);
}
}
return
$safe_list
;
}
function
backup_note
(){
$tmp
=
strrpos
(
$_SERVER
[
'SCRIPT_NAME'
],
"/"
);
if
(
$tmp
==
0
)
$base
=
$_SERVER
[
'SCRIPT_NAME'
];
else
$base
=
substr
(
$_SERVER
[
'SCRIPT_NAME'
],
0
,
$tmp
+
1
);
print
(
"<b>NOTE</b>: If flashing rootfs, please download a backup copy of <a href='
$base
?cmd=backup'>/etc/elphel393</a><br/>"
);
}
function
nandflash
(
$list
){
global
$UPDATE_DIR
;
global
$FLASH_LOG
;
global
$FLASH_LOG_LINK
;
foreach
(
$list
as
$e
){
if
(
$e
[
0
]
==
0
){
exec
(
"flash_unlock ${e[2]} >>
$FLASH_LOG
"
);
exec
(
"flash_erase ${e[2]} ${e[3]} >>
$FLASH_LOG
"
);
if
(
$e
[
1
]
!=
"rootfs.ubi"
)
exec
(
"nandwrite -n ${e[2]} -p
$UPDATE_DIR
/${e[1]} >>
$FLASH_LOG
"
);
else
exec
(
"ubiformat ${e[2]} -f
$UPDATE_DIR
/${e[1]} ${e[4]} >>
$FLASH_LOG
"
);
}
}
print
(
"Done. See/Download <a href='
$FLASH_LOG_LINK
'>flash.log</a>. Then power cycle."
);
}
function
backup
(){
global
$NAND_PATH
;
global
$UBI_MNT
;
global
$BKP_NAME
;
global
$BKP_DIR
;
if
(
!
is_dir
(
$NAND_PATH
)){
exec
(
"flash_unlock /dev/mtd4"
);
exec
(
"ubiattach /dev/ubi_ctrl -m 4"
);
if
(
!
is_dir
(
$UBI_MNT
))
mkdir
(
$UBI_MNT
);
exec
(
"mount -t ubifs -o ro /dev/ubi0_0
$UBI_MNT
"
);
exec
(
"tar -czvf var/
$BKP_NAME
-C ${UBI_MNT}${BKP_DIR} ."
);
exec
(
"umount
$UBI_MNT
"
);
if
(
is_dir
(
$UBI_MNT
))
rmdir
(
$UBI_MNT
);
exec
(
"ubidetach /dev/ubi_ctrl -m 4"
);
}
else
{
//booted from nand
exec
(
"tar -czvf var/
$BKP_NAME
-C ${BKP_DIR} ."
);
}
header
(
"Content-Type: application/octet-stream"
);
header
(
'Content-Disposition: attachment; filename='
.
$BKP_NAME
);
print
(
file_get_contents
(
"var/
$BKP_NAME
"
));
}
function
remove
(){
global
$UPDATE_DIR
;
exec
(
"rm -rf
$UPDATE_DIR
/*; sync"
);
backup_note
();
print
(
"<b>NOTE</b>: All files have been removed from <b>
$UPDATE_DIR
</b>."
);
}
$cmd
=
"donothing"
;
if
(
isset
(
$_GET
[
'cmd'
]))
$cmd
=
$_GET
[
'cmd'
];
else
if
(
isset
(
$argv
[
1
]))
$cmd
=
$argv
[
1
];
switch
(
$cmd
){
case
"flash"
:
$flash_list
=
verify
(
false
);
nandflash
(
$flash_list
);
break
;
case
"backup"
:
backup
();
break
;
case
"remove"
:
remove
();
break
;
default
:
verify
(
true
);
}
?>
src/update/update_software.html
0 → 100644
View file @
5f4bb7de
<!doctype html>
<html
lang=
"en"
>
<head>
<meta
charset=
"utf-8"
/>
<meta
name=
"author"
content=
"?"
/>
<link
rel=
"stylesheet"
href=
"js/bootstrap/css/bootstrap.min.css"
>
<link
rel=
"stylesheet"
href=
"js/fileupload/style.css"
>
<link
rel=
"stylesheet"
href=
"js/fileupload/jquery.fileupload.css"
>
<script
src=
"js/jquery-2.2.3.min.js"
></script>
<script
src=
"js/jquery-ui/jquery-ui.min.js"
></script>
<script
src=
"js/fileupload/jquery.iframe-transport.js"
></script>
<script
src=
"js/fileupload/jquery.fileupload.js"
></script>
<script
src=
"js/bootstrap/js/bootstrap.min.js"
></script>
<script
src=
"update_software.js"
></script>
</head>
<body
onload=
'init()'
style=
'padding-top:0px;'
>
<h2
id=
'title'
style=
'padding-left:10px;'
>
Update software:
</h2>
<div
id=
'test'
style=
'padding:10px;'
>
<!-- The fileinput-button span is used to style the file input field as button -->
<span
class=
"btn btn-success fileinput-button"
>
<i
class=
"glyphicon glyphicon-plus"
></i>
<span>
Select files...
</span>
<!-- The file input field used as target for the file upload widget -->
<input
id=
"fileupload"
type=
"file"
name=
"files[]"
multiple
>
</span>
<span
id=
'btn_remove'
class=
"btn btn-danger"
><b>
Remove files
</b></span>
<br>
<br>
<!-- The global progress bar -->
<div
id=
"progress"
class=
"progress"
style=
'width:250px;'
>
<div
class=
"progress-bar progress-bar-success"
></div>
</div>
<!-- The container for the uploaded files -->
<div
id=
"files"
class=
"files"
></div>
</div>
<div
style=
'padding-left:10px;'
>
<span
id=
'btn_verify'
class=
"btn btn-success"
><b>
Verify
</b></span>
<span
id=
'btn_flash'
class=
"btn btn-danger"
><b>
Flash
</b></span>
<span
style=
'padding-left:10px;'
><a
href=
'var/flash.log'
>
flash.log
</a></span>
</div>
<div
style=
'padding:10px;'
>
<div
id=
'status'
class=
'blink'
></div>
</div>
</body>
</html>
src/update/update_software.js
0 → 100644
View file @
5f4bb7de
/*
FILE NAME : debugfs.js
DESCRIPTION: dynamic debug frontend
REVISION: 1.00
AUTHOR: Oleg Dzhimiev <oleg@elphel.com>
LICENSE: AGPL, see http://www.gnu.org/licenses/agpl.txt
Copyright (C) 2016 Elphel, Inc.
*/
var
blink_intvl
;
function
init
(){
console
.
log
(
"init"
);
var
url
=
"update_software.php"
;
$
(
'#fileupload'
).
fileupload
({
url
:
url
,
dataType
:
'json'
,
done
:
function
(
e
,
data
)
{
$
.
each
(
data
.
result
.
files
,
function
(
index
,
file
)
{
$
(
'<p/>'
).
text
(
file
.
name
).
appendTo
(
'#files'
);
});
},
progressall
:
function
(
e
,
data
)
{
var
progress
=
parseInt
(
data
.
loaded
/
data
.
total
*
100
,
10
);
$
(
'#progress .progress-bar'
).
css
(
'width'
,
progress
+
'%'
);
}
}).
prop
(
'disabled'
,
!
$
.
support
.
fileInput
).
parent
().
addClass
(
$
.
support
.
fileInput
?
undefined
:
'disabled'
);
$
(
'#btn_remove'
).
click
(
function
(){
$
.
ajax
({
url
:
"update_nand.php?cmd=remove"
,
success
:
function
(
result
){
$
(
"#status"
).
html
(
result
);
}
});
});
$
(
'#btn_verify'
).
click
(
function
(){
$
.
ajax
({
url
:
"update_nand.php"
,
success
:
function
(
result
){
$
(
"#status"
).
addClass
(
"blink"
).
html
(
result
);
}
});
});
$
(
'#btn_flash'
).
click
(
function
(){
$
(
"#status"
).
html
(
"Flashing..."
);
blink_intvl
=
setInterval
(
blink
,
1000
);
$
.
ajax
({
url
:
"update_nand.php?cmd=flash"
,
success
:
function
(
result
){
clearInterval
(
blink_intvl
);
$
(
"#status"
).
html
(
result
);
}
});
});
}
function
blink
(){
$
(
'.blink'
).
fadeOut
(
500
);
$
(
'.blink'
).
fadeIn
(
500
);
}
function
upload
(){
console
.
log
(
"upload"
);
}
\ No newline at end of file
src/update/update_software.php
0 → 100644
View file @
5f4bb7de
<?php
/*
* jQuery File Upload Plugin PHP Example
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
error_reporting
(
E_ALL
|
E_STRICT
);
require
(
'js/fileupload/UploadHandler.php'
);
$upload_handler
=
new
UploadHandler
();
?>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment