diff --git a/README.md b/README.md
index 2a2fa53f5..e21151c7e 100755
--- a/README.md
+++ b/README.md
@@ -53,3 +53,13 @@ Note: jQuery help shorten a lot of the javascript code within NearBeach. There a
+### jQuery File Upload
+Creator(s): Sebastian Tschan
+
+Github: https://github.com/blueimp/jQuery-File-Upload
+
+Link: https://blueimp.github.io/jQuery-File-Upload/
+
+License : MIT
+
+Note: jQuery File Upload is used when there is a file to be uploaded through the AJAX method.
\ No newline at end of file
diff --git a/admin.py b/admin.py
index 700662803..a856948bb 100755
--- a/admin.py
+++ b/admin.py
@@ -14,6 +14,11 @@
admin.site.register(folders)
admin.site.register(group_permissions)
admin.site.register(groups)
+admin.site.register(kanban_board)
+admin.site.register(kanban_comment)
+admin.site.register(kanban_card)
+admin.site.register(kanban_column)
+admin.site.register(kanban_level)
admin.site.register(list_of_amount_type)
admin.site.register(list_of_currency)
admin.site.register(list_of_contact_types)
@@ -24,6 +29,7 @@
admin.site.register(list_of_quote_stages)
admin.site.register(list_of_requirement_item_status)
admin.site.register(list_of_requirement_item_type)
+admin.site.register(list_of_requirement_status)
admin.site.register(list_of_taxes)
admin.site.register(list_of_titles)
admin.site.register(opportunity)
@@ -53,6 +59,7 @@
admin.site.register(tasks_groups)
admin.site.register(tasks_history)
admin.site.register(tasks)
+admin.site.register(to_do)
admin.site.register(user_groups)
diff --git a/forms.py b/forms.py
index 2f3a5f724..890b1aa94 100644
--- a/forms.py
+++ b/forms.py
@@ -573,6 +573,93 @@ class information_task_history_form(forms.Form):
)
+class kanban_card_form(ModelForm):
+ def __init__(self, *args, **kwargs):
+ kanban_board_id = kwargs.pop('kanban_board_id')
+
+ super(kanban_card_form, self).__init__(*args, **kwargs)
+
+ self.fields['kanban_column'].queryset = kanban_column.objects.filter(kanban_board=kanban_board_id)
+ self.fields['kanban_level'].queryset = kanban_level.objects.filter(kanban_board=kanban_board_id)
+
+ kanban_card_comment = forms.CharField(
+ required=False,
+ widget=TextInput(attrs={
+ 'placeholder': 'Card Comments',
+ }),
+ )
+
+ kanban_card_text = forms.CharField(
+ required=True,
+ max_length=255,
+ widget=TextInput(attrs={
+ 'placeholder': 'Card Text',
+ }),
+ )
+
+ class Meta:
+ model = kanban_card
+ fields = {
+ 'kanban_card_text',
+ 'kanban_column',
+ 'kanban_level',
+ }
+
+
+class kanban_board_form(forms.Form):
+ kanban_board_name = forms.CharField(
+ max_length=255,
+ widget=TextInput(attrs={
+ 'placeholder': 'Board Name',
+ }),
+ )
+ kanban_board_column = forms.CharField(
+ widget=forms.Textarea(attrs={
+ 'placeholder': 'Please place each new column on a new line. Each name will be truncated to 255 characters',
+ }),
+ required=True,
+ )
+ kanban_board_level = forms.CharField(
+ widget=forms.Textarea(attrs={
+ 'placeholder': 'Please place each new level on a new line. Each name will be truncated to 255 characters',
+ }),
+ required=True,
+ )
+
+
+class kanban_properties_form(ModelForm):
+ kanban_board_name=forms.CharField(
+ max_length=255,
+ widget=TextInput(attrs={
+ 'placeholder': 'Board Name',
+ }),
+ required=True,
+ )
+ class Meta:
+ model = kanban_board
+ fields = {
+ 'kanban_board_name',
+ }
+
+
+
+
+class kanban_new_link_form(ModelForm):
+ def __init__(self, *args, **kwargs):
+ kanban_board_id = kwargs.pop('kanban_board_id')
+
+ super(kanban_new_link_form, self).__init__(*args, **kwargs)
+
+ self.fields['kanban_column'].queryset = kanban_column.objects.filter(kanban_board=kanban_board_id)
+ self.fields['kanban_level'].queryset = kanban_level.objects.filter(kanban_board=kanban_board_id)
+ class Meta:
+ model = kanban_card
+ fields = {
+ 'kanban_column',
+ 'kanban_level',
+ }
+
+
class list_of_taxes_form(ModelForm):
class Meta:
model = list_of_taxes
@@ -1133,6 +1220,7 @@ class Meta:
'requirement_title',
'requirement_scope',
'requirement_type',
+ 'requirement_status',
}
@@ -1732,6 +1820,20 @@ class Meta:
}
+class to_do_form(ModelForm):
+ to_do = forms.CharField(
+ max_length=255,
+ widget=forms.TextInput(attrs={
+ 'placeholder': 'To do next?',
+ })
+ )
+ class Meta:
+ model=to_do
+ fields={
+ 'to_do'
+ }
+
+
class user_information_form(ModelForm):
password1 = forms.CharField(
diff --git a/migrations/0001_initial.py b/migrations/0001_initial.py
index c0562383a..4fb62920b 100644
--- a/migrations/0001_initial.py
+++ b/migrations/0001_initial.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Generated by Django 1.11.7 on 2018-01-26 22:56
+# Generated by Django 1.11 on 2018-03-21 01:34
from __future__ import unicode_literals
import NearBeach.private_media
@@ -22,7 +22,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='assigned_users',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('assigned_users_id', models.AutoField(primary_key=True, serialize=False)),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_modified', models.DateTimeField(auto_now=True)),
('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
@@ -96,7 +96,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='document_permissions',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('document_permisssions_id', models.AutoField(primary_key=True, serialize=False)),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_modified', models.DateTimeField(auto_now=True)),
('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
@@ -126,7 +126,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='documents_folder',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('documents_folder_id', models.AutoField(primary_key=True, serialize=False)),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_modified', models.DateTimeField(auto_now=True)),
('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
@@ -175,6 +175,86 @@ class Migration(migrations.Migration):
'db_table': 'groups',
},
),
+ migrations.CreateModel(
+ name='kanban_board',
+ fields=[
+ ('kanban_board_id', models.AutoField(primary_key=True, serialize=False)),
+ ('kanban_board_name', models.CharField(max_length=255)),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ('date_modified', models.DateTimeField(auto_now=True)),
+ ('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
+ ('change_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='kanban_board_change_user', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'db_table': 'kanban_board',
+ },
+ ),
+ migrations.CreateModel(
+ name='kanban_card',
+ fields=[
+ ('kanban_card_id', models.AutoField(primary_key=True, serialize=False)),
+ ('kanban_card_text', models.CharField(max_length=255)),
+ ('kanban_card_sort_number', models.IntegerField()),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ('date_modified', models.DateTimeField(auto_now=True)),
+ ('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
+ ('change_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='kanban_card_change_user', to=settings.AUTH_USER_MODEL)),
+ ('kanban_board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.kanban_board')),
+ ],
+ options={
+ 'db_table': 'kanban_card',
+ },
+ ),
+ migrations.CreateModel(
+ name='kanban_column',
+ fields=[
+ ('kanban_column_id', models.AutoField(primary_key=True, serialize=False)),
+ ('kanban_column_name', models.CharField(max_length=255)),
+ ('kanban_column_sort_number', models.IntegerField()),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ('date_modified', models.DateTimeField(auto_now=True)),
+ ('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
+ ('change_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='kanban_column_change_user', to=settings.AUTH_USER_MODEL)),
+ ('kanban_board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.kanban_board')),
+ ],
+ options={
+ 'db_table': 'kanban_column',
+ },
+ ),
+ migrations.CreateModel(
+ name='kanban_comment',
+ fields=[
+ ('kanban_comment_id', models.AutoField(primary_key=True, serialize=False)),
+ ('kanban_comment', models.TextField()),
+ ('user_infomation', models.CharField(max_length=255)),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ('date_modified', models.DateTimeField(auto_now=True)),
+ ('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
+ ('change_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='kanban_comment_change_user', to=settings.AUTH_USER_MODEL)),
+ ('kanban_board', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.kanban_board')),
+ ('kanban_card', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.kanban_card')),
+ ('user_id', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'db_table': 'kanban_comment',
+ },
+ ),
+ migrations.CreateModel(
+ name='kanban_level',
+ fields=[
+ ('kanban_level_id', models.AutoField(primary_key=True, serialize=False)),
+ ('kanban_level_name', models.CharField(max_length=255)),
+ ('kanban_level_sort_number', models.IntegerField()),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ('date_modified', models.DateTimeField(auto_now=True)),
+ ('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
+ ('change_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='kanban_level_change_user', to=settings.AUTH_USER_MODEL)),
+ ('kanban_board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.kanban_board')),
+ ],
+ options={
+ 'db_table': 'kanban_level',
+ },
+ ),
migrations.CreateModel(
name='list_of_amount_type',
fields=[
@@ -329,6 +409,21 @@ class Migration(migrations.Migration):
'db_table': 'list_of_requirement_item_type',
},
),
+ migrations.CreateModel(
+ name='list_of_requirement_status',
+ fields=[
+ ('requirement_status_id', models.AutoField(primary_key=True, serialize=False)),
+ ('requirement_status', models.CharField(max_length=50)),
+ ('requirement_status_is_closed', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=10)),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ('date_modified', models.DateTimeField(auto_now=True)),
+ ('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
+ ('change_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='list_of_requirement_status_change_user', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'db_table': 'list_of_requirement_status',
+ },
+ ),
migrations.CreateModel(
name='list_of_requirement_type',
fields=[
@@ -347,12 +442,12 @@ class Migration(migrations.Migration):
name='list_of_taxes',
fields=[
('tax_id', models.AutoField(primary_key=True, serialize=False)),
- ('tax_amount', models.DecimalField(decimal_places=2, max_digits=4)),
+ ('tax_amount', models.DecimalField(decimal_places=4, max_digits=6)),
+ ('tax_description', models.CharField(blank=True, max_length=50, null=True)),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_modified', models.DateTimeField(auto_now=True)),
('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
('change_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='list_of_taxes_change_user', to=settings.AUTH_USER_MODEL)),
- ('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'list_of_taxes',
@@ -398,7 +493,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='opportunity_next_step',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('opportunity_next_step_id', models.AutoField(primary_key=True, serialize=False)),
('next_step_description', models.CharField(max_length=255)),
('next_step_completed', models.BooleanField(default=False)),
('date_created', models.DateTimeField(auto_now_add=True)),
@@ -484,15 +579,19 @@ class Migration(migrations.Migration):
('invoice', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('invoice_product', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('customer', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
+ ('kanban', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
+ ('kanban_card', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('opportunity', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('organisation', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('organisation_campus', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('project', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
+ ('quote', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('requirement', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('requirement_link', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('task', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0)),
('documents', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Has Permission')], default=0)),
('contact_history', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Has Permission')], default=0)),
+ ('kanban_comment', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Has Permission')], default=0)),
('project_history', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Has Permission')], default=0)),
('task_history', models.IntegerField(choices=[(0, 'No Permission'), (1, 'Has Permission')], default=0)),
('date_created', models.DateTimeField(auto_now_add=True)),
@@ -623,7 +722,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='project_tasks',
fields=[
- ('project_tasks', models.AutoField(primary_key=True, serialize=False)),
+ ('project_tasks_id', models.AutoField(primary_key=True, serialize=False)),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_modified', models.DateTimeField(auto_now=True)),
('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
@@ -662,7 +761,9 @@ class Migration(migrations.Migration):
('date_modified', models.DateTimeField(auto_now=True)),
('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
('change_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotes_change_user', to=settings.AUTH_USER_MODEL)),
+ ('customer_id', models.ForeignKey(blank=True, db_column='customer_id', null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.customers')),
('opportunity_id', models.ForeignKey(blank=True, db_column='opportunity_id', null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.opportunity')),
+ ('organisation_id', models.ForeignKey(blank=True, db_column='organisations_id', null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.organisations')),
('project_id', models.ForeignKey(blank=True, db_column='project_id', null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.project')),
('quote_stage_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.list_of_quote_stages')),
],
@@ -753,6 +854,7 @@ class Migration(migrations.Migration):
('date_modified', models.DateTimeField(auto_now=True)),
('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
('change_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requirements_change_user', to=settings.AUTH_USER_MODEL)),
+ ('requirement_status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.list_of_requirement_status')),
('requirement_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.list_of_requirement_type')),
],
options={
@@ -829,7 +931,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='tasks_groups',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('tasks_groups_id', models.AutoField(primary_key=True, serialize=False)),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_modified', models.DateTimeField(auto_now=True)),
('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
@@ -876,7 +978,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='user_groups',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('user_groups_id', models.AutoField(primary_key=True, serialize=False)),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_modified', models.DateTimeField(auto_now=True)),
('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
@@ -942,13 +1044,43 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='opportunity',
name='organisations_id',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.organisations'),
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.organisations'),
),
migrations.AddField(
model_name='opportunity',
name='user_id',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
+ migrations.AddField(
+ model_name='kanban_card',
+ name='kanban_column',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.kanban_column'),
+ ),
+ migrations.AddField(
+ model_name='kanban_card',
+ name='kanban_level',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.kanban_level'),
+ ),
+ migrations.AddField(
+ model_name='kanban_card',
+ name='project',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.project'),
+ ),
+ migrations.AddField(
+ model_name='kanban_card',
+ name='requirements',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.requirements'),
+ ),
+ migrations.AddField(
+ model_name='kanban_card',
+ name='tasks',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.tasks'),
+ ),
+ migrations.AddField(
+ model_name='kanban_board',
+ name='requirements',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.requirements'),
+ ),
migrations.AddField(
model_name='group_permissions',
name='groups',
@@ -994,6 +1126,16 @@ class Migration(migrations.Migration):
name='project_id',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.project'),
),
+ migrations.AddField(
+ model_name='document_permissions',
+ name='requirement_item',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.requirement_item'),
+ ),
+ migrations.AddField(
+ model_name='document_permissions',
+ name='requirements',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.requirements'),
+ ),
migrations.AddField(
model_name='document_permissions',
name='task_id',
@@ -1027,7 +1169,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='customers',
name='organisations_id',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='NearBeach.organisations'),
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.organisations'),
),
migrations.AddField(
model_name='costs',
diff --git a/migrations/0002_initialise_data.py b/migrations/0002_initialise_data.py
index 00d612236..fb6d466bc 100644
--- a/migrations/0002_initialise_data.py
+++ b/migrations/0002_initialise_data.py
@@ -87,8 +87,8 @@ def initialise_data(apps, schema_editor):
list_of_opportunity_stage(list_order="6", opportunity_stage_description="Perception Analysis",probability_success="60"),
list_of_opportunity_stage(list_order="7", opportunity_stage_description="Proposal/Price Quote",probability_success="70"),
list_of_opportunity_stage(list_order="8", opportunity_stage_description="Negotiation/Review",probability_success="80"),
- list_of_opportunity_stage(list_order="9", opportunity_stage_description="Closed Won",probability_success="100",opportunity_closed='TRUE'),
- list_of_opportunity_stage(list_order="10", opportunity_stage_description="Closed Lost",probability_success="0",opportunity_closed='TRUE'),
+ list_of_opportunity_stage(list_order="9", opportunity_stage_description="Closed Won",probability_success="100"),
+ list_of_opportunity_stage(list_order="10", opportunity_stage_description="Closed Lost",probability_success="0"),
])
#List of requirement items status
@@ -119,6 +119,15 @@ def initialise_data(apps, schema_editor):
])
+
+ #List of requirement status
+ list_of_requirement_status = apps.get_model("NearBeach","list_of_requirement_status")
+ list_of_requirement_status.objects.using(db_alias).bulk_create([
+ list_of_requirement_status(requirement_status="New Requirement"),
+ list_of_requirement_status(requirement_status="In Progress"),
+ list_of_requirement_status(requirement_status="Completed",requirement_status_is_closed='TRUE'),
+ ])
+
#List of requirement type
list_of_requirement_type = apps.get_model("NearBeach","list_of_requirement_type")
list_of_requirement_type.objects.using(db_alias).bulk_create([
diff --git a/migrations/0003_auto_20180205_2035.py b/migrations/0003_auto_20180205_2035.py
deleted file mode 100644
index b872d10f7..000000000
--- a/migrations/0003_auto_20180205_2035.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11 on 2018-02-05 09:35
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('NearBeach', '0002_initialise_data'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='list_of_taxes',
- name='tax_description',
- field=models.CharField(blank=True, max_length=50, null=True),
- ),
- migrations.AlterField(
- model_name='list_of_taxes',
- name='tax_amount',
- field=models.DecimalField(decimal_places=4, max_digits=6),
- ),
- ]
diff --git a/migrations/0003_auto_20180321_1725.py b/migrations/0003_auto_20180321_1725.py
new file mode 100644
index 000000000..13fb56549
--- /dev/null
+++ b/migrations/0003_auto_20180321_1725.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2018-03-21 06:25
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('NearBeach', '0002_initialise_data'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='to_do',
+ fields=[
+ ('to_do_id', models.AutoField(primary_key=True, serialize=False)),
+ ('to_do', models.CharField(max_length=255)),
+ ('to_do_completed', models.BooleanField(default=False)),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ('date_modified', models.DateTimeField(auto_now=True)),
+ ('is_deleted', models.CharField(choices=[('TRUE', 'TRUE'), ('FALSE', 'FALSE')], default='FALSE', max_length=5)),
+ ('change_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_do_change_user', to=settings.AUTH_USER_MODEL)),
+ ('project', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.project')),
+ ('tasks', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.tasks')),
+ ],
+ options={
+ 'db_table': 'to_do',
+ },
+ ),
+ migrations.AddField(
+ model_name='organisations_campus',
+ name='campus_latitude',
+ field=models.DecimalField(blank=True, decimal_places=13, max_digits=16, null=True),
+ ),
+ migrations.AddField(
+ model_name='organisations_campus',
+ name='campus_longitude',
+ field=models.DecimalField(blank=True, decimal_places=13, max_digits=16, null=True),
+ ),
+ ]
diff --git a/migrations/0004_remove_list_of_taxes_user_id.py b/migrations/0004_remove_list_of_taxes_user_id.py
deleted file mode 100644
index 881506464..000000000
--- a/migrations/0004_remove_list_of_taxes_user_id.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11 on 2018-02-05 09:41
-from __future__ import unicode_literals
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('NearBeach', '0003_auto_20180205_2035'),
- ]
-
- operations = [
- migrations.RemoveField(
- model_name='list_of_taxes',
- name='user_id',
- ),
- ]
diff --git a/migrations/0005_permission_set_quote.py b/migrations/0005_permission_set_quote.py
deleted file mode 100644
index 00df4985a..000000000
--- a/migrations/0005_permission_set_quote.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11 on 2018-02-12 10:19
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('NearBeach', '0004_remove_list_of_taxes_user_id'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='permission_set',
- name='quote',
- field=models.IntegerField(choices=[(0, 'No Permission'), (1, 'Read Only'), (2, 'Edit Only'), (3, 'Add and Edit'), (4, 'Full Permission')], default=0),
- ),
- ]
diff --git a/migrations/0006_auto_20180217_2321.py b/migrations/0006_auto_20180217_2321.py
deleted file mode 100644
index ab1f02381..000000000
--- a/migrations/0006_auto_20180217_2321.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.7 on 2018-02-17 12:21
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('NearBeach', '0005_permission_set_quote'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='quotes',
- name='customer_id',
- field=models.ForeignKey(blank=True, db_column='customer_id', null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.customers'),
- ),
- migrations.AddField(
- model_name='quotes',
- name='organisation_id',
- field=models.ForeignKey(blank=True, db_column='organisations_id', null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.organisations'),
- ),
- ]
diff --git a/migrations/0007_auto_20180217_2341.py b/migrations/0007_auto_20180217_2341.py
deleted file mode 100644
index 6142da417..000000000
--- a/migrations/0007_auto_20180217_2341.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.7 on 2018-02-17 12:41
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('NearBeach', '0006_auto_20180217_2321'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='customers',
- name='organisations_id',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.organisations'),
- ),
- ]
diff --git a/migrations/0008_auto_20180220_0838.py b/migrations/0008_auto_20180220_0838.py
deleted file mode 100644
index dc522ec87..000000000
--- a/migrations/0008_auto_20180220_0838.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.7 on 2018-02-19 21:38
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('NearBeach', '0007_auto_20180217_2341'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='opportunity',
- name='organisations_id',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='NearBeach.organisations'),
- ),
- ]
diff --git a/models.py b/models.py
index cde51e185..060d1e40a 100755
--- a/models.py
+++ b/models.py
@@ -49,6 +49,7 @@
#List of tables - in alphabetical order
class assigned_users(models.Model):
+ assigned_users_id=models.AutoField(primary_key=True)
user_id=models.ForeignKey(
User,
on_delete=models.CASCADE,
@@ -285,6 +286,7 @@ def __str__(self):
class documents_folder(models.Model):
+ documents_folder_id=models.AutoField(primary_key=True)
document_key=models.ForeignKey(
'documents',
on_delete=models.CASCADE,
@@ -313,6 +315,7 @@ class Meta:
class document_permissions(models.Model):
+ document_permisssions_id=models.AutoField(primary_key=True)
document_key=models.ForeignKey(
'documents',
on_delete=models.CASCADE,
@@ -347,6 +350,18 @@ class document_permissions(models.Model):
null=True,
on_delete=models.CASCADE,
)
+ requirements=models.ForeignKey(
+ 'requirements',
+ blank=True,
+ null=True,
+ on_delete=models.CASCADE,
+ )
+ requirement_item=models.ForeignKey(
+ 'requirement_item',
+ blank=True,
+ null=True,
+ on_delete=models.CASCADE,
+ )
user_id=models.ForeignKey(
User,
on_delete=models.CASCADE,
@@ -471,14 +486,195 @@ class Meta:
db_table="group_permissions"
+class kanban_board(models.Model):
+ kanban_board_id=models.AutoField(primary_key=True)
+ kanban_board_name=models.CharField(max_length=255)
+ requirements=models.ForeignKey(
+ 'requirements',
+ null=True,
+ blank=True,
+ on_delete=models.CASCADE,
+ )
+ date_created = models.DateTimeField(auto_now_add=True)
+ date_modified = models.DateTimeField(auto_now=True)
+ change_user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ related_name='%(class)s_change_user'
+ )
+ is_deleted = models.CharField(
+ max_length=5,
+ choices=IS_DELETED_CHOICE,
+ default='FALSE',
+ )
+
+ class Meta:
+ db_table = "kanban_board"
+
+ def __str__(self):
+ return self.kanban_board_name
+
+
+class kanban_card(models.Model):
+ kanban_card_id=models.AutoField(primary_key=True)
+ kanban_card_text=models.CharField(max_length=255)
+ kanban_card_sort_number=models.IntegerField()
+ kanban_level=models.ForeignKey(
+ 'kanban_level',
+ on_delete=models.CASCADE,
+ )
+ kanban_column=models.ForeignKey(
+ 'kanban_column',
+ on_delete=models.CASCADE,
+ )
+ kanban_board=models.ForeignKey(
+ 'kanban_board',
+ on_delete=models.CASCADE,
+ )
+ project=models.ForeignKey(
+ 'project',
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ )
+ tasks=models.ForeignKey(
+ 'tasks',
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ )
+ requirements=models.ForeignKey(
+ 'requirements',
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ )
+ date_created = models.DateTimeField(auto_now_add=True)
+ date_modified = models.DateTimeField(auto_now=True)
+ change_user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ related_name='%(class)s_change_user'
+ )
+ is_deleted = models.CharField(
+ max_length=5,
+ choices=IS_DELETED_CHOICE,
+ default='FALSE',
+ )
+
+ class Meta:
+ db_table = "kanban_card"
+
+ def __str__(self):
+ return self.kanban_card_text
+
+
+class kanban_column(models.Model):
+ kanban_column_id=models.AutoField(primary_key=True)
+ kanban_column_name=models.CharField(max_length=255)
+ kanban_column_sort_number=models.IntegerField()
+ kanban_board=models.ForeignKey(
+ 'kanban_board',
+ on_delete=models.CASCADE,
+ )
+ date_created = models.DateTimeField(auto_now_add=True)
+ date_modified = models.DateTimeField(auto_now=True)
+ change_user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ related_name='%(class)s_change_user'
+ )
+ is_deleted = models.CharField(
+ max_length=5,
+ choices=IS_DELETED_CHOICE,
+ default='FALSE',
+ )
+
+ class Meta:
+ db_table = "kanban_column"
+
+ def __str__(self):
+ return self.kanban_column_name
+
+
+class kanban_comment(models.Model):
+ kanban_comment_id=models.AutoField(primary_key=True)
+ kanban_comment=models.TextField()
+ kanban_board=models.ForeignKey(
+ 'kanban_board',
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ )
+ kanban_card=models.ForeignKey(
+ 'kanban_card',
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ )
+ user_id=models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ null=True
+ )
+ user_infomation = models.CharField(max_length=255)
+ date_created = models.DateTimeField(auto_now_add=True)
+ date_modified = models.DateTimeField(auto_now=True)
+ change_user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ related_name='%(class)s_change_user'
+ )
+ is_deleted = models.CharField(
+ max_length=5,
+ choices=IS_DELETED_CHOICE,
+ default='FALSE',
+ )
+
+ class Meta:
+ db_table = "kanban_comment"
+
+ def __str__(self):
+ return self.kanban_comment
+
+
+class kanban_level(models.Model):
+ kanban_level_id=models.AutoField(primary_key=True)
+ kanban_level_name=models.CharField(max_length=255)
+ kanban_level_sort_number=models.IntegerField()
+ kanban_board=models.ForeignKey(
+ 'kanban_board',
+ on_delete=models.CASCADE,
+ )
+ date_created = models.DateTimeField(auto_now_add=True)
+ date_modified = models.DateTimeField(auto_now=True)
+ change_user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ related_name='%(class)s_change_user'
+ )
+ is_deleted = models.CharField(
+ max_length=5,
+ choices=IS_DELETED_CHOICE,
+ default='FALSE',
+ )
+
+ class Meta:
+ db_table = "kanban_level"
+
+ def __str__(self):
+ return self.kanban_level_name
+
+
+
+
+
class list_of_amount_type(models.Model):
amount_type_id=models.AutoField(primary_key=True)
amount_type_description=models.CharField(max_length=20)
list_order=models.IntegerField(unique=True)
date_created=models.DateTimeField(auto_now_add=True)
date_modified=models.DateTimeField(auto_now=True)
- date_created=models.DateTimeField(auto_now_add=True)
- date_modified=models.DateTimeField(auto_now=True)
change_user=models.ForeignKey(
User,
on_delete=models.CASCADE,
@@ -728,7 +924,6 @@ class Meta:
-
class list_of_requirement_item_status(models.Model):
requirement_item_status_id=models.AutoField(primary_key=True)
requirement_item_status = models.CharField(
@@ -783,6 +978,42 @@ class Meta:
db_table = "list_of_requirement_item_type"
+
+class list_of_requirement_status(models.Model):
+ requirement_status_id=models.AutoField(primary_key=True)
+ requirement_status=models.CharField(
+ max_length=50,
+ )
+ requirement_status_is_closed=models.CharField(
+ max_length=10,
+ choices=IS_DELETED_CHOICE,
+ default='FALSE'
+ )
+ date_created = models.DateTimeField(auto_now_add=True)
+ date_modified = models.DateTimeField(auto_now=True)
+ change_user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ related_name='%(class)s_change_user',
+ blank=True,
+ null=True,
+ )
+ is_deleted = models.CharField(
+ max_length=5,
+ choices=IS_DELETED_CHOICE,
+ default='FALSE'
+ )
+
+ def __str__(self):
+ return str(self.requirement_status)
+
+ class Meta:
+ db_table = "list_of_requirement_status"
+
+
+
+
+
class list_of_requirement_type(models.Model):
requirement_type_id = models.AutoField(primary_key=True)
requirement_type=models.CharField(
@@ -975,6 +1206,7 @@ class Meta:
class opportunity_next_step(models.Model):
+ opportunity_next_step_id=models.AutoField(primary_key=True)
opportunity_id=models.ForeignKey(
'opportunity',
on_delete=models.CASCADE
@@ -1069,6 +1301,18 @@ class organisations_campus(models.Model):
'list_of_countries',
on_delete=models.CASCADE,
)
+ campus_longitude=models.DecimalField(
+ decimal_places=13,
+ max_digits=16,
+ null=True, #If use has no mapping software, we want to leave this blank
+ blank=True,
+ )
+ campus_latitude=models.DecimalField(
+ decimal_places=13,
+ max_digits=16,
+ null=True, #If use has no mapping software, we want to leave this blank
+ blank=True,
+ )
date_created=models.DateTimeField(auto_now_add=True)
date_modified=models.DateTimeField(auto_now=True)
change_user=models.ForeignKey(
@@ -1103,6 +1347,8 @@ def get_by_natural_key(
customer,
invoice,
invoice_product,
+ kanban,
+ kanban_card,
opportunity,
organisation,
organisation_campus,
@@ -1113,6 +1359,7 @@ def get_by_natural_key(
task,
documents,
contact_history,
+ kanban_comment,
project_history,
task_history,
):
@@ -1128,6 +1375,8 @@ def get_by_natural_key(
customer=customer,
invoice=invoice,
invoice_product=invoice_product,
+ kanban=kanban,
+ kanban_card=kanban_card,
opportunity=opportunity,
organisation=organisation,
organisation_campus=organisation_campus,
@@ -1138,6 +1387,7 @@ def get_by_natural_key(
task=task,
documents=documents,
contact_history=contact_history,
+ kanban_comment=kanban_comment,
project_history=project_history,
task_history=task_history,
)
@@ -1187,6 +1437,14 @@ class permission_set(models.Model):
choices=PERMISSION_LEVEL,
default=0,
)
+ kanban = models.IntegerField(
+ choices=PERMISSION_LEVEL,
+ default=0,
+ )
+ kanban_card = models.IntegerField(
+ choices=PERMISSION_LEVEL,
+ default=0,
+ )
opportunity = models.IntegerField(
choices=PERMISSION_LEVEL,
default=0,
@@ -1234,6 +1492,10 @@ class permission_set(models.Model):
choices=PERMISSION_BOOLEAN,
default=0,
)
+ kanban_comment = models.IntegerField(
+ choices=PERMISSION_BOOLEAN,
+ default=0,
+ )
project_history = models.IntegerField(
choices=PERMISSION_BOOLEAN,
default=0,
@@ -1530,7 +1792,7 @@ class Meta:
class project_tasks(models.Model):
- project_tasks=models.AutoField(primary_key=True)
+ project_tasks_id=models.AutoField(primary_key=True)
project_id=models.ForeignKey(
'project',
on_delete=models.CASCADE,
@@ -1772,6 +2034,10 @@ class requirements(models.Model):
'list_of_requirement_type',
on_delete=models.CASCADE,
)
+ requirement_status=models.ForeignKey(
+ 'list_of_requirement_status',
+ on_delete=models.CASCADE,
+ )
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
change_user = models.ForeignKey(
@@ -2043,6 +2309,7 @@ class Meta:
db_table="tasks_customers"
class tasks_groups(models.Model):
+ tasks_groups_id=models.AutoField(primary_key=True)
tasks_id=models.ForeignKey(
'tasks',
on_delete=models.CASCADE,
@@ -2122,7 +2389,44 @@ class Meta:
db_table="tasks_opportunity"
+class to_do(models.Model):
+ to_do_id = models.AutoField(primary_key=True)
+ to_do = models.CharField(
+ max_length=255,
+ )
+ to_do_completed = models.BooleanField(default=False)
+ project = models.ForeignKey(
+ 'project',
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ )
+ tasks = models.ForeignKey(
+ 'tasks',
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ )
+ date_created = models.DateTimeField(auto_now_add=True)
+ date_modified = models.DateTimeField(auto_now=True)
+ change_user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ related_name='%(class)s_change_user'
+ )
+ is_deleted = models.CharField(
+ max_length=5,
+ choices=IS_DELETED_CHOICE,
+ default='FALSE'
+ )
+
+ class Meta:
+ db_table = "to_do"
+
+
+
class user_groups(models.Model):
+ user_groups_id=models.AutoField(primary_key=True)
username=models.ForeignKey(
User,
on_delete=models.CASCADE,
diff --git a/static/NearBeach/css/kanban_board.css b/static/NearBeach/css/kanban_board.css
new file mode 100644
index 000000000..fa5f21875
--- /dev/null
+++ b/static/NearBeach/css/kanban_board.css
@@ -0,0 +1,45 @@
+.kanban_column_header {
+ background-color:#52460a;
+ text-align:center;
+ color:white;
+ padding: 2px 0px;
+ min-width: 300px;
+}
+
+.kanban_level {
+ min-height: 100px;
+}
+
+.kanban_card {
+ padding-bottom: 30px;
+}
+
+*[draggable=true]
+{
+ float:left;
+ min-width: 90%;
+ min-height: 50px;
+ border: 2px solid black;
+ margin: 10px;
+ font-weight:bold;
+ min-height:60px;
+ background-image: -webkit-gradient(
+ linear,
+ left top, left bottom,
+ color-stop(0.63, white),
+ color-stop(0.92, rgb(5,161,245)));
+ background-image:-moz-linear-gradient(
+ center top,
+ white 63%,
+ rgb(5,161,245) 92%
+ );
+ -webkit-background-size:1px 8px;
+ -moz-background-size:1px 8px;
+ background-size:1px 8px;
+ background-repeat:repeat;
+}
+.cardTitle
+{
+ background-color: rgb(255,255,255);
+ border:2px solid white;
+}
\ No newline at end of file
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/.gitignore b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/.gitignore
new file mode 100644
index 000000000..29a41a8c4
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/.gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+*.pyc
+node_modules
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/.jshintrc b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/.jshintrc
new file mode 100644
index 000000000..4ad82e664
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/.jshintrc
@@ -0,0 +1,81 @@
+{
+ "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
+ "camelcase" : true, // true: Identifiers must be in camelCase
+ "curly" : true, // true: Require {} for every new block or scope
+ "eqeqeq" : true, // true: Require triple equals (===) for comparison
+ "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
+ "immed" : true, // true: Require immediate invocations to be wrapped in parens
+ // e.g. `(function () { } ());`
+ "indent" : 4, // {int} Number of spaces to use for indentation
+ "latedef" : true, // true: Require variables/functions to be defined before being used
+ "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
+ "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
+ "noempty" : true, // true: Prohibit use of empty blocks
+ "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
+ "plusplus" : false, // true: Prohibit use of `++` & `--`
+ "quotmark" : "single", // Quotation mark consistency:
+ // false : do nothing (default)
+ // true : ensure whatever is used is consistent
+ // "single" : require single quotes
+ // "double" : require double quotes
+ "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
+ "unused" : true, // true: Require all defined variables be used
+ "strict" : true, // true: Requires all functions run in ES5 Strict Mode
+ "trailing" : true, // true: Prohibit trailing whitespaces
+ "maxparams" : false, // {int} Max number of formal params allowed per function
+ "maxdepth" : false, // {int} Max depth of nested blocks (within functions)
+ "maxstatements" : false, // {int} Max number statements per function
+ "maxcomplexity" : false, // {int} Max cyclomatic complexity per function
+ "maxlen" : false, // {int} Max number of characters per line
+
+ // Relaxing
+ "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
+ "boss" : false, // true: Tolerate assignments where comparisons would be expected
+ "debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
+ "eqnull" : false, // true: Tolerate use of `== null`
+ "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
+ "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
+ "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
+ // (ex: `for each`, multiple try/catch, function expression…)
+ "evil" : false, // true: Tolerate use of `eval` and `new Function()`
+ "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
+ "funcscope" : false, // true: Tolerate defining variables inside control statements"
+ "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
+ "iterator" : false, // true: Tolerate using the `__iterator__` property
+ "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
+ "laxbreak" : false, // true: Tolerate possibly unsafe line breakings
+ "laxcomma" : false, // true: Tolerate comma-first style coding
+ "loopfunc" : false, // true: Tolerate functions being defined in loops
+ "multistr" : false, // true: Tolerate multi-line strings
+ "proto" : false, // true: Tolerate using the `__proto__` property
+ "scripturl" : false, // true: Tolerate script-targeted URLs
+ "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
+ "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
+ "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
+ "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
+ "validthis" : false, // true: Tolerate using this in a non-constructor function
+
+ // Environments
+ "browser" : false, // Web Browser (window, document, etc)
+ "couch" : false, // CouchDB
+ "devel" : false, // Development/debugging (alert, confirm, etc)
+ "dojo" : false, // Dojo Toolkit
+ "jquery" : false, // jQuery
+ "mootools" : false, // MooTools
+ "node" : false, // Node.js
+ "nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
+ "prototypejs" : false, // Prototype and Scriptaculous
+ "rhino" : false, // Rhino
+ "worker" : false, // Web Workers
+ "wsh" : false, // Windows Scripting Host
+ "yui" : false, // Yahoo User Interface
+
+ // Legacy
+ "nomen" : true, // true: Prohibit dangling `_` in variables
+ "onevar" : true, // true: Allow only one `var` statement per function
+ "passfail" : false, // true: Stop on first error
+ "white" : true, // true: Check against strict whitespace and indentation rules
+
+ // Custom Globals
+ "globals" : {} // additional predefined global variables
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/.npmignore b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/.npmignore
new file mode 100644
index 000000000..0530f5dbd
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/.npmignore
@@ -0,0 +1,20 @@
+*
+!css/jquery.fileupload-noscript.css
+!css/jquery.fileupload-ui-noscript.css
+!css/jquery.fileupload-ui.css
+!css/jquery.fileupload.css
+!img/loading.gif
+!img/progressbar.gif
+!js/cors/jquery.postmessage-transport.js
+!js/cors/jquery.xdr-transport.js
+!js/vendor/jquery.ui.widget.js
+!js/jquery.fileupload-angular.js
+!js/jquery.fileupload-audio.js
+!js/jquery.fileupload-image.js
+!js/jquery.fileupload-jquery-ui.js
+!js/jquery.fileupload-process.js
+!js/jquery.fileupload-ui.js
+!js/jquery.fileupload-validate.js
+!js/jquery.fileupload-video.js
+!js/jquery.fileupload.js
+!js/jquery.iframe-transport.js
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/CONTRIBUTING.md b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/CONTRIBUTING.md
new file mode 100644
index 000000000..e182f9b37
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/CONTRIBUTING.md
@@ -0,0 +1,15 @@
+Please follow these pull request guidelines:
+
+1. Update your fork to the latest upstream version.
+
+2. Follow the coding conventions of the original source files (indentation, spaces, brackets layout).
+
+3. Code changes must pass JSHint validation with the `.jshintrc` settings of this project.
+
+4. Code changes must pass the QUnit tests defined in the `test` folder.
+
+5. New features should be covered by accompanying QUnit tests.
+
+6. Keep your commits as atomic as possible, i.e. create a new commit for every single bug fix or feature added.
+
+7. Always add meaningful commit messages.
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/LICENSE.txt b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/LICENSE.txt
new file mode 100644
index 000000000..87a644638
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright © 2010 Sebastian Tschan, https://blueimp.net
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/README.md b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/README.md
new file mode 100644
index 000000000..76bdf89d5
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/README.md
@@ -0,0 +1,107 @@
+# jQuery File Upload Plugin
+
+## Demo
+[Demo File Upload](https://blueimp.github.io/jQuery-File-Upload/)
+
+## Description
+File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for jQuery.
+Supports cross-domain, chunked and resumable file uploads and client-side image resizing. Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.
+
+## Setup
+* [How to setup the plugin on your website](https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
+* [How to use only the basic plugin (minimal setup guide).](https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin)
+
+## Features
+* **Multiple file upload:**
+ Allows to select multiple files at once and upload them simultaneously.
+* **Drag & Drop support:**
+ Allows to upload files by dragging them from your desktop or filemanager and dropping them on your browser window.
+* **Upload progress bar:**
+ Shows a progress bar indicating the upload progress for individual files and for all uploads combined.
+* **Cancelable uploads:**
+ Individual file uploads can be canceled to stop the upload progress.
+* **Resumable uploads:**
+ Aborted uploads can be resumed with browsers supporting the Blob API.
+* **Chunked uploads:**
+ Large files can be uploaded in smaller chunks with browsers supporting the Blob API.
+* **Client-side image resizing:**
+ Images can be automatically resized on client-side with browsers supporting the required JS APIs.
+* **Preview images, audio and video:**
+ A preview of image, audio and video files can be displayed before uploading with browsers supporting the required APIs.
+* **No browser plugins (e.g. Adobe Flash) required:**
+ The implementation is based on open standards like HTML5 and JavaScript and requires no additional browser plugins.
+* **Graceful fallback for legacy browsers:**
+ Uploads files via XMLHttpRequests if supported and uses iframes as fallback for legacy browsers.
+* **HTML file upload form fallback:**
+ Allows progressive enhancement by using a standard HTML file upload form as widget element.
+* **Cross-site file uploads:**
+ Supports uploading files to a different domain with cross-site XMLHttpRequests or iframe redirects.
+* **Multiple plugin instances:**
+ Allows to use multiple plugin instances on the same webpage.
+* **Customizable and extensible:**
+ Provides an API to set individual options and define callback methods for various upload events.
+* **Multipart and file contents stream uploads:**
+ Files can be uploaded as standard "multipart/form-data" or file contents stream (HTTP PUT file upload).
+* **Compatible with any server-side application platform:**
+ Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.
+
+## Requirements
+
+### Mandatory requirements
+* [jQuery](https://jquery.com/) v. 1.6+
+* [jQuery UI widget factory](https://api.jqueryui.com/jQuery.widget/) v. 1.9+ (included): Required for the basic File Upload plugin, but very lightweight without any other dependencies from the jQuery UI suite.
+* [jQuery Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/jquery.iframe-transport.js) (included): Required for [browsers without XHR file upload support](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support).
+
+### Optional requirements
+* [JavaScript Templates engine](https://github.com/blueimp/JavaScript-Templates) v. 2.5.4+: Used to render the selected and uploaded files for the Basic Plus UI and jQuery UI versions.
+* [JavaScript Load Image library](https://github.com/blueimp/JavaScript-Load-Image) v. 1.13.0+: Required for the image previews and resizing functionality.
+* [JavaScript Canvas to Blob polyfill](https://github.com/blueimp/JavaScript-Canvas-to-Blob) v. 2.1.1+:Required for the image previews and resizing functionality.
+* [blueimp Gallery](https://github.com/blueimp/Gallery) v. 2.15.1+: Used to display the uploaded images in a lightbox.
+* [Bootstrap](http://getbootstrap.com/) v. 3.2.0+
+* [Glyphicons](http://glyphicons.com/)
+
+The user interface of all versions, except the jQuery UI version, is built with [Bootstrap](http://getbootstrap.com/) and icons from [Glyphicons](http://glyphicons.com/).
+
+### Cross-domain requirements
+[Cross-domain File Uploads](https://github.com/blueimp/jQuery-File-Upload/wiki/Cross-domain-uploads) using the [Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/jquery.iframe-transport.js) require a redirect back to the origin server to retrieve the upload results. The [example implementation](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/main.js) makes use of [result.html](https://github.com/blueimp/jQuery-File-Upload/blob/master/cors/result.html) as a static redirect page for the origin server.
+
+The repository also includes the [jQuery XDomainRequest Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/cors/jquery.xdr-transport.js), which enables limited cross-domain AJAX requests in Microsoft Internet Explorer 8 and 9 (IE 10 supports cross-domain XHR requests).
+The XDomainRequest object allows GET and POST requests only and doesn't support file uploads. It is used on the [Demo](https://blueimp.github.io/jQuery-File-Upload/) to delete uploaded files from the cross-domain demo file upload service.
+
+### Custom Backends
+
+You can add support for various backends by adhering to the specification [outlined here](https://github.com/blueimp/jQuery-File-Upload/wiki/JSON-Response).
+
+## Browsers
+
+### Desktop browsers
+The File Upload plugin is regularly tested with the latest browser versions and supports the following minimal versions:
+
+* Google Chrome
+* Apple Safari 4.0+
+* Mozilla Firefox 3.0+
+* Opera 11.0+
+* Microsoft Internet Explorer 6.0+
+
+### Mobile browsers
+The File Upload plugin has been tested with and supports the following mobile browsers:
+
+* Apple Safari on iOS 6.0+
+* Google Chrome on iOS 6.0+
+* Google Chrome on Android 4.0+
+* Default Browser on Android 2.3+
+* Opera Mobile 12.0+
+
+### Supported features
+For a detailed overview of the features supported by each browser version, please have a look at the [Extended browser support information](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support).
+
+## Contributing
+**Bug fixes** and **new features** can be proposed using [pull requests](https://github.com/blueimp/jQuery-File-Upload/pulls).
+Please read the [contribution guidelines](https://github.com/blueimp/jQuery-File-Upload/blob/master/CONTRIBUTING.md) before submitting a pull request.
+
+## Support
+This project is actively maintained, but there is no official support channel.
+If you have a question that another developer might help you with, please post to [Stack Overflow](http://stackoverflow.com/questions/tagged/blueimp+jquery+file-upload) and tag your question with `blueimp jquery file upload`.
+
+## License
+Released under the [MIT license](https://opensource.org/licenses/MIT).
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/angularjs.html b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/angularjs.html
new file mode 100644
index 000000000..4858c8600
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/angularjs.html
@@ -0,0 +1,211 @@
+
+
+
+
+
+
+
+jQuery File Upload Demo - AngularJS version
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
jQuery File Upload Demo
+
AngularJS version
+
+
+
+ File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for AngularJS.
+ Supports cross-domain, chunked and resumable file uploads and client-side image resizing.
+ Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.
+
+
+
+
+
+
+
+
Demo Notes
+
+
+
+ The maximum file size for uploads in this demo is 999 KB (default file size is unlimited).
+ Only image files (JPG, GIF, PNG ) are allowed in this demo (by default there is no file type restriction).
+ Uploaded files will be deleted automatically after 5 minutes or less (demo files are stored in memory).
+ You can drag & drop files from your desktop on this webpage (see Browser support ).
+ Please refer to the project website and documentation for more information.
+ Built with the Bootstrap CSS framework and Icons from Glyphicons .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/basic-plus.html b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/basic-plus.html
new file mode 100644
index 000000000..9e5c2321f
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/basic-plus.html
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+jQuery File Upload Demo - Basic Plus version
+
+
+
+
+
+
+
+
+
+
+
+
+
jQuery File Upload Demo
+
Basic Plus version
+
+
+
+ File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery.
+ Supports cross-domain, chunked and resumable file uploads and client-side image resizing.
+ Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.
+
+
+
+
+
+ Add files...
+
+
+
+
+
+
+
+
+
+
+
+
+
Demo Notes
+
+
+
+ The maximum file size for uploads in this demo is 999 KB (default file size is unlimited).
+ Only image files (JPG, GIF, PNG ) are allowed in this demo (by default there is no file type restriction).
+ Uploaded files will be deleted automatically after 5 minutes or less (demo files are stored in memory).
+ You can drag & drop files from your desktop on this webpage (see Browser support ).
+ Please refer to the project website and documentation for more information.
+ Built with the Bootstrap CSS framework and Icons from Glyphicons .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/basic.html b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/basic.html
new file mode 100644
index 000000000..c0df639b4
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/basic.html
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+jQuery File Upload Demo - Basic version
+
+
+
+
+
+
+
+
+
+
+
+
+
jQuery File Upload Demo
+
Basic version
+
+
+
+ File Upload widget with multiple file selection, drag&drop support and progress bar for jQuery.
+ Supports cross-domain, chunked and resumable file uploads.
+ Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.
+
+
+
+
+
+ Select files...
+
+
+
+
+
+
+
+
+
+
+
+
+
Demo Notes
+
+
+
+ The maximum file size for uploads in this demo is 999 KB (default file size is unlimited).
+ Only image files (JPG, GIF, PNG ) are allowed in this demo (by default there is no file type restriction).
+ Uploaded files will be deleted automatically after 5 minutes or less (demo files are stored in memory).
+ You can drag & drop files from your desktop on this webpage (see Browser support ).
+ Please refer to the project website and documentation for more information.
+ Built with the Bootstrap CSS framework and Icons from Glyphicons .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/bower-version-update.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/bower-version-update.js
new file mode 100755
index 000000000..09ce3927e
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/bower-version-update.js
@@ -0,0 +1,16 @@
+#!/usr/bin/env node
+
+'use strict';
+
+var path = require('path');
+var packageJSON = require(path.join(__dirname, 'package.json'));
+var bowerFile = path.join(__dirname, 'bower.json');
+var bowerJSON = require('bower-json').parse(
+ require(bowerFile),
+ {normalize: true}
+);
+bowerJSON.version = packageJSON.version;
+require('fs').writeFileSync(
+ bowerFile,
+ JSON.stringify(bowerJSON, null, 2) + '\n'
+);
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/bower.json b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/bower.json
new file mode 100644
index 000000000..cf9ed4176
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/bower.json
@@ -0,0 +1,64 @@
+{
+ "name": "blueimp-file-upload",
+ "version": "9.19.2",
+ "title": "jQuery File Upload",
+ "description": "File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images.",
+ "keywords": [
+ "jquery",
+ "file",
+ "upload",
+ "widget",
+ "multiple",
+ "selection",
+ "drag",
+ "drop",
+ "progress",
+ "preview",
+ "cross-domain",
+ "cross-site",
+ "chunk",
+ "resume",
+ "gae",
+ "go",
+ "python",
+ "php",
+ "bootstrap"
+ ],
+ "homepage": "https://github.com/blueimp/jQuery-File-Upload",
+ "author": {
+ "name": "Sebastian Tschan",
+ "url": "https://blueimp.net"
+ },
+ "maintainers": [
+ {
+ "name": "Sebastian Tschan",
+ "url": "https://blueimp.net"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/blueimp/jQuery-File-Upload.git"
+ },
+ "bugs": "https://github.com/blueimp/jQuery-File-Upload/issues",
+ "license": "MIT",
+ "dependencies": {
+ "jquery": ">=1.6",
+ "blueimp-tmpl": ">=2.5.4",
+ "blueimp-load-image": ">=1.13.0",
+ "blueimp-canvas-to-blob": ">=2.1.1"
+ },
+ "main": [
+ "js/jquery.fileupload.js"
+ ],
+ "ignore": [
+ "/*.*",
+ "/cors",
+ "css/jquery-ui-demo-ie8.css",
+ "css/jquery-ui-demo.css",
+ "css/style.css",
+ "js/app.js",
+ "js/main.js",
+ "server",
+ "test"
+ ]
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/cors/postmessage.html b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/cors/postmessage.html
new file mode 100644
index 000000000..6db288cf9
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/cors/postmessage.html
@@ -0,0 +1,75 @@
+
+
+
+
+
+jQuery File Upload Plugin postMessage API
+
+
+
+
+
+
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/cors/result.html b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/cors/result.html
new file mode 100644
index 000000000..e3d629814
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/cors/result.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+jQuery Iframe Transport Plugin Redirect Page
+
+
+
+
+
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery-ui-demo-ie8.css b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery-ui-demo-ie8.css
new file mode 100644
index 000000000..e0e8ea9b0
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery-ui-demo-ie8.css
@@ -0,0 +1,21 @@
+@charset "UTF-8";
+/*
+ * jQuery File Upload Demo CSS Fixes for IE<9
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+.navigation {
+ list-style: none;
+ padding: 0;
+ margin: 1em 0;
+}
+.navigation li {
+ display: inline;
+ margin-right: 10px;
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery-ui-demo.css b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery-ui-demo.css
new file mode 100644
index 000000000..d7d524df5
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery-ui-demo.css
@@ -0,0 +1,67 @@
+@charset "UTF-8";
+/*
+ * jQuery File Upload Demo CSS
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+body {
+ max-width: 750px;
+ margin: 0 auto;
+ padding: 1em;
+ font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif;
+ font-size: 1em;
+ line-height: 1.4em;
+ background: #222;
+ color: #fff;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+a {
+ color: orange;
+ text-decoration: none;
+}
+img {
+ border: 0;
+ vertical-align: middle;
+}
+h1 {
+ line-height: 1em;
+}
+blockquote {
+ padding: 0 0 0 15px;
+ margin: 0 0 20px;
+ border-left: 5px solid #eee;
+}
+table {
+ width: 100%;
+ margin: 10px 0;
+}
+
+.fileupload-progress {
+ margin: 10px 0;
+}
+.fileupload-progress .progress-extended {
+ margin-top: 5px;
+}
+.error {
+ color: red;
+}
+
+@media (min-width: 481px) {
+ .navigation {
+ list-style: none;
+ padding: 0;
+ }
+ .navigation li {
+ display: inline-block;
+ }
+ .navigation li:not(:first-child):before {
+ content: "| ";
+ }
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload-noscript.css b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload-noscript.css
new file mode 100644
index 000000000..2409bfb0a
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload-noscript.css
@@ -0,0 +1,22 @@
+@charset "UTF-8";
+/*
+ * jQuery File Upload Plugin NoScript CSS
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+.fileinput-button input {
+ position: static;
+ opacity: 1;
+ filter: none;
+ font-size: inherit !important;
+ direction: inherit;
+}
+.fileinput-button span {
+ display: none;
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload-ui-noscript.css b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload-ui-noscript.css
new file mode 100644
index 000000000..30651acf0
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload-ui-noscript.css
@@ -0,0 +1,17 @@
+@charset "UTF-8";
+/*
+ * jQuery File Upload UI Plugin NoScript CSS
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+.fileinput-button i,
+.fileupload-buttonbar .delete,
+.fileupload-buttonbar .toggle {
+ display: none;
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload-ui.css b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload-ui.css
new file mode 100644
index 000000000..9e36c42c5
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload-ui.css
@@ -0,0 +1,57 @@
+@charset "UTF-8";
+/*
+ * jQuery File Upload UI Plugin CSS
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+.fileupload-buttonbar .btn,
+.fileupload-buttonbar .toggle {
+ margin-bottom: 5px;
+}
+.progress-animated .progress-bar,
+.progress-animated .bar {
+ background: url("../img/progressbar.gif") !important;
+ filter: none;
+}
+.fileupload-process {
+ float: right;
+ display: none;
+}
+.fileupload-processing .fileupload-process,
+.files .processing .preview {
+ display: block;
+ width: 32px;
+ height: 32px;
+ background: url("../img/loading.gif") center no-repeat;
+ background-size: contain;
+}
+.files audio,
+.files video {
+ max-width: 300px;
+}
+
+@media (max-width: 767px) {
+ .fileupload-buttonbar .toggle,
+ .files .toggle,
+ .files .btn span {
+ display: none;
+ }
+ .files .name {
+ width: 80px;
+ word-wrap: break-word;
+ }
+ .files audio,
+ .files video {
+ max-width: 80px;
+ }
+ .files img,
+ .files canvas {
+ max-width: 100%;
+ }
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload.css b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload.css
new file mode 100644
index 000000000..8ae3b09d4
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/jquery.fileupload.css
@@ -0,0 +1,37 @@
+@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:
+ * https://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%;
+ }
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/style.css b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/style.css
new file mode 100644
index 000000000..3aee25689
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/css/style.css
@@ -0,0 +1,15 @@
+@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:
+ * https://opensource.org/licenses/MIT
+ */
+
+body {
+ padding-top: 60px;
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/img/loading.gif b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/img/loading.gif
new file mode 100644
index 000000000..90f28cbdb
Binary files /dev/null and b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/img/loading.gif differ
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/img/progressbar.gif b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/img/progressbar.gif
new file mode 100644
index 000000000..fbcce6bc9
Binary files /dev/null and b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/img/progressbar.gif differ
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/index.html b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/index.html
new file mode 100644
index 000000000..2a8dc1521
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/index.html
@@ -0,0 +1,255 @@
+
+
+
+
+
+
+
+jQuery File Upload Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
jQuery File Upload Demo
+
Basic Plus UI version
+
+
+
+ File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for jQuery.
+ Supports cross-domain, chunked and resumable file uploads and client-side image resizing.
+ Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.
+
+
+
+
+
+
+
+
Demo Notes
+
+
+
+ The maximum file size for uploads in this demo is 999 KB (default file size is unlimited).
+ Only image files (JPG, GIF, PNG ) are allowed in this demo (by default there is no file type restriction).
+ Uploaded files will be deleted automatically after 5 minutes or less (demo files are stored in memory).
+ You can drag & drop files from your desktop on this webpage (see Browser support ).
+ Please refer to the project website and documentation for more information.
+ Built with the Bootstrap CSS framework and Icons from Glyphicons .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/jquery-ui.html b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/jquery-ui.html
new file mode 100644
index 000000000..6fb9e8a04
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/jquery-ui.html
@@ -0,0 +1,252 @@
+
+
+
+
+
+
+
+jQuery File Upload Demo - jQuery UI version
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+jQuery File Upload Demo
+jQuery UI version
+
+
+
+ File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for jQuery UI.
+ Supports cross-domain, chunked and resumable file uploads and client-side image resizing.
+ Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.
+
+
+
+
+Demo Notes
+
+ The maximum file size for uploads in this demo is 999 KB (default file size is unlimited).
+ Only image files (JPG, GIF, PNG ) are allowed in this demo (by default there is no file type restriction).
+ Uploaded files will be deleted automatically after 5 minutes or less (demo files are stored in memory).
+ You can drag & drop files from your desktop on this webpage (see Browser support ).
+ Please refer to the project website and documentation for more information.
+ Built with jQuery UI .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/app.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/app.js
new file mode 100644
index 000000000..e6b7bce3e
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/app.js
@@ -0,0 +1,101 @@
+/*
+ * jQuery File Upload Plugin Angular JS Example
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global window, angular */
+
+;(function () {
+ 'use strict';
+
+ var isOnGitHub = window.location.hostname === 'blueimp.github.io',
+ url = isOnGitHub ? '//jquery-file-upload.appspot.com/' : 'server/php/';
+
+ angular.module('demo', [
+ 'blueimp.fileupload'
+ ])
+ .config([
+ '$httpProvider', 'fileUploadProvider',
+ function ($httpProvider, fileUploadProvider) {
+ delete $httpProvider.defaults.headers.common['X-Requested-With'];
+ fileUploadProvider.defaults.redirect = window.location.href.replace(
+ /\/[^\/]*$/,
+ '/cors/result.html?%s'
+ );
+ if (isOnGitHub) {
+ // Demo settings:
+ angular.extend(fileUploadProvider.defaults, {
+ // Enable image resizing, except for Android and Opera,
+ // which actually support image resizing, but fail to
+ // send Blob objects via XHR requests:
+ disableImageResize: /Android(?!.*Chrome)|Opera/
+ .test(window.navigator.userAgent),
+ maxFileSize: 999000,
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i
+ });
+ }
+ }
+ ])
+
+ .controller('DemoFileUploadController', [
+ '$scope', '$http', '$filter', '$window',
+ function ($scope, $http) {
+ $scope.options = {
+ url: url
+ };
+ if (!isOnGitHub) {
+ $scope.loadingFiles = true;
+ $http.get(url)
+ .then(
+ function (response) {
+ $scope.loadingFiles = false;
+ $scope.queue = response.data.files || [];
+ },
+ function () {
+ $scope.loadingFiles = false;
+ }
+ );
+ }
+ }
+ ])
+
+ .controller('FileDestroyController', [
+ '$scope', '$http',
+ function ($scope, $http) {
+ var file = $scope.file,
+ state;
+ if (file.url) {
+ file.$state = function () {
+ return state;
+ };
+ file.$destroy = function () {
+ state = 'pending';
+ return $http({
+ url: file.deleteUrl,
+ method: file.deleteType
+ }).then(
+ function () {
+ state = 'resolved';
+ $scope.clear(file);
+ },
+ function () {
+ state = 'rejected';
+ }
+ );
+ };
+ } else if (!file.$cancel && !file._index) {
+ file.$cancel = function () {
+ $scope.clear(file);
+ };
+ }
+ }
+ ]);
+
+}());
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/cors/jquery.postmessage-transport.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/cors/jquery.postmessage-transport.js
new file mode 100644
index 000000000..2a0c38cb6
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/cors/jquery.postmessage-transport.js
@@ -0,0 +1,126 @@
+/*
+ * jQuery postMessage Transport Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://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';
+
+ var counter = 0,
+ names = [
+ 'accepts',
+ 'cache',
+ 'contents',
+ 'contentType',
+ 'crossDomain',
+ 'data',
+ 'dataType',
+ 'headers',
+ 'ifModified',
+ 'mimeType',
+ 'password',
+ 'processData',
+ 'timeout',
+ 'traditional',
+ 'type',
+ 'url',
+ 'username'
+ ],
+ convert = function (p) {
+ return p;
+ };
+
+ $.ajaxSetup({
+ converters: {
+ 'postmessage text': convert,
+ 'postmessage json': convert,
+ 'postmessage html': convert
+ }
+ });
+
+ $.ajaxTransport('postmessage', function (options) {
+ if (options.postMessage && window.postMessage) {
+ var iframe,
+ loc = $('').prop('href', options.postMessage)[0],
+ target = loc.protocol + '//' + loc.host,
+ xhrUpload = options.xhr().upload;
+ // IE always includes the port for the host property of a link
+ // element, but not in the location.host or origin property for the
+ // default http port 80 and https port 443, so we strip it:
+ if (/^(http:\/\/.+:80)|(https:\/\/.+:443)$/.test(target)) {
+ target = target.replace(/:(80|443)$/, '');
+ }
+ return {
+ send: function (_, completeCallback) {
+ counter += 1;
+ var message = {
+ id: 'postmessage-transport-' + counter
+ },
+ eventName = 'message.' + message.id;
+ iframe = $(
+ ''
+ ).bind('load', function () {
+ $.each(names, function (i, name) {
+ message[name] = options[name];
+ });
+ message.dataType = message.dataType.replace('postmessage ', '');
+ $(window).bind(eventName, function (e) {
+ e = e.originalEvent;
+ var data = e.data,
+ ev;
+ if (e.origin === target && data.id === message.id) {
+ if (data.type === 'progress') {
+ ev = document.createEvent('Event');
+ ev.initEvent(data.type, false, true);
+ $.extend(ev, data);
+ xhrUpload.dispatchEvent(ev);
+ } else {
+ completeCallback(
+ data.status,
+ data.statusText,
+ {postmessage: data.result},
+ data.headers
+ );
+ iframe.remove();
+ $(window).unbind(eventName);
+ }
+ }
+ });
+ iframe[0].contentWindow.postMessage(
+ message,
+ target
+ );
+ }).appendTo(document.body);
+ },
+ abort: function () {
+ if (iframe) {
+ iframe.remove();
+ }
+ }
+ };
+ }
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/cors/jquery.xdr-transport.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/cors/jquery.xdr-transport.js
new file mode 100644
index 000000000..a4e2699c6
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/cors/jquery.xdr-transport.js
@@ -0,0 +1,89 @@
+/*
+ * jQuery XDomainRequest Transport Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ *
+ * Based on Julian Aubourg's ajaxHooks xdr.js:
+ * https://github.com/jaubourg/ajaxHooks/
+ */
+
+/* global define, require, window, XDomainRequest */
+
+;(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';
+ if (window.XDomainRequest && !$.support.cors) {
+ $.ajaxTransport(function (s) {
+ if (s.crossDomain && s.async) {
+ if (s.timeout) {
+ s.xdrTimeout = s.timeout;
+ delete s.timeout;
+ }
+ var xdr;
+ return {
+ send: function (headers, completeCallback) {
+ var addParamChar = /\?/.test(s.url) ? '&' : '?';
+ function callback(status, statusText, responses, responseHeaders) {
+ xdr.onload = xdr.onerror = xdr.ontimeout = $.noop;
+ xdr = null;
+ completeCallback(status, statusText, responses, responseHeaders);
+ }
+ xdr = new XDomainRequest();
+ // XDomainRequest only supports GET and POST:
+ if (s.type === 'DELETE') {
+ s.url = s.url + addParamChar + '_method=DELETE';
+ s.type = 'POST';
+ } else if (s.type === 'PUT') {
+ s.url = s.url + addParamChar + '_method=PUT';
+ s.type = 'POST';
+ } else if (s.type === 'PATCH') {
+ s.url = s.url + addParamChar + '_method=PATCH';
+ s.type = 'POST';
+ }
+ xdr.open(s.type, s.url);
+ xdr.onload = function () {
+ callback(
+ 200,
+ 'OK',
+ {text: xdr.responseText},
+ 'Content-Type: ' + xdr.contentType
+ );
+ };
+ xdr.onerror = function () {
+ callback(404, 'Not Found');
+ };
+ if (s.xdrTimeout) {
+ xdr.ontimeout = function () {
+ callback(0, 'timeout');
+ };
+ xdr.timeout = s.xdrTimeout;
+ }
+ xdr.send((s.hasContent && s.data) || null);
+ },
+ abort: function () {
+ if (xdr) {
+ xdr.onerror = $.noop();
+ xdr.abort();
+ }
+ }
+ };
+ }
+ });
+ }
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-angular.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-angular.js
new file mode 100644
index 000000000..1c2055276
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-angular.js
@@ -0,0 +1,437 @@
+/*
+ * jQuery File Upload AngularJS Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, angular, require */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'angular',
+ './jquery.fileupload-image',
+ './jquery.fileupload-audio',
+ './jquery.fileupload-video',
+ './jquery.fileupload-validate'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('angular'),
+ require('./jquery.fileupload-image'),
+ require('./jquery.fileupload-audio'),
+ require('./jquery.fileupload-video'),
+ require('./jquery.fileupload-validate')
+ );
+ } else {
+ factory();
+ }
+}(function () {
+ 'use strict';
+
+ angular.module('blueimp.fileupload', [])
+
+ // The fileUpload service provides configuration options
+ // for the fileUpload directive and default handlers for
+ // File Upload events:
+ .provider('fileUpload', function () {
+ var scopeEvalAsync = function (expression) {
+ var scope = angular.element(this)
+ .fileupload('option', 'scope');
+ // Schedule a new $digest cycle if not already inside of one
+ // and evaluate the given expression:
+ scope.$evalAsync(expression);
+ },
+ addFileMethods = function (scope, data) {
+ var files = data.files,
+ file = files[0];
+ angular.forEach(files, function (file, index) {
+ file._index = index;
+ file.$state = function () {
+ return data.state();
+ };
+ file.$processing = function () {
+ return data.processing();
+ };
+ file.$progress = function () {
+ return data.progress();
+ };
+ file.$response = function () {
+ return data.response();
+ };
+ });
+ file.$submit = function () {
+ if (!file.error) {
+ return data.submit();
+ }
+ };
+ file.$cancel = function () {
+ return data.abort();
+ };
+ },
+ $config;
+ $config = this.defaults = {
+ handleResponse: function (e, data) {
+ var files = data.result && data.result.files;
+ if (files) {
+ data.scope.replace(data.files, files);
+ } else if (data.errorThrown ||
+ data.textStatus === 'error') {
+ data.files[0].error = data.errorThrown ||
+ data.textStatus;
+ }
+ },
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var scope = data.scope,
+ filesCopy = [];
+ angular.forEach(data.files, function (file) {
+ filesCopy.push(file);
+ });
+ scope.$parent.$applyAsync(function () {
+ addFileMethods(scope, data);
+ var method = scope.option('prependFiles') ?
+ 'unshift' : 'push';
+ Array.prototype[method].apply(scope.queue, data.files);
+ });
+ data.process(function () {
+ return scope.process(data);
+ }).always(function () {
+ scope.$parent.$applyAsync(function () {
+ addFileMethods(scope, data);
+ scope.replace(filesCopy, data.files);
+ });
+ }).then(function () {
+ if ((scope.option('autoUpload') ||
+ data.autoUpload) &&
+ data.autoUpload !== false) {
+ data.submit();
+ }
+ });
+ },
+ done: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = this;
+ data.scope.$apply(function () {
+ data.handleResponse.call(that, e, data);
+ });
+ },
+ fail: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = this,
+ scope = data.scope;
+ if (data.errorThrown === 'abort') {
+ scope.clear(data.files);
+ return;
+ }
+ scope.$apply(function () {
+ data.handleResponse.call(that, e, data);
+ });
+ },
+ stop: scopeEvalAsync,
+ processstart: scopeEvalAsync,
+ processstop: scopeEvalAsync,
+ getNumberOfFiles: function () {
+ var scope = this.scope;
+ return scope.queue.length - scope.processing();
+ },
+ dataType: 'json',
+ autoUpload: false
+ };
+ this.$get = [
+ function () {
+ return {
+ defaults: $config
+ };
+ }
+ ];
+ })
+
+ // Format byte numbers to readable presentations:
+ .provider('formatFileSizeFilter', function () {
+ var $config = {
+ // Byte units following the IEC format
+ // http://en.wikipedia.org/wiki/Kilobyte
+ units: [
+ {size: 1000000000, suffix: ' GB'},
+ {size: 1000000, suffix: ' MB'},
+ {size: 1000, suffix: ' KB'}
+ ]
+ };
+ this.defaults = $config;
+ this.$get = function () {
+ return function (bytes) {
+ if (!angular.isNumber(bytes)) {
+ return '';
+ }
+ var unit = true,
+ i = 0,
+ prefix,
+ suffix;
+ while (unit) {
+ unit = $config.units[i];
+ prefix = unit.prefix || '';
+ suffix = unit.suffix || '';
+ if (i === $config.units.length - 1 || bytes >= unit.size) {
+ return prefix + (bytes / unit.size).toFixed(2) + suffix;
+ }
+ i += 1;
+ }
+ };
+ };
+ })
+
+ // The FileUploadController initializes the fileupload widget and
+ // provides scope methods to control the File Upload functionality:
+ .controller('FileUploadController', [
+ '$scope', '$element', '$attrs', '$window', 'fileUpload','$q',
+ function ($scope, $element, $attrs, $window, fileUpload, $q) {
+ var uploadMethods = {
+ progress: function () {
+ return $element.fileupload('progress');
+ },
+ active: function () {
+ return $element.fileupload('active');
+ },
+ option: function (option, data) {
+ if (arguments.length === 1) {
+ return $element.fileupload('option', option);
+ }
+ $element.fileupload('option', option, data);
+ },
+ add: function (data) {
+ return $element.fileupload('add', data);
+ },
+ send: function (data) {
+ return $element.fileupload('send', data);
+ },
+ process: function (data) {
+ return $element.fileupload('process', data);
+ },
+ processing: function (data) {
+ return $element.fileupload('processing', data);
+ }
+ };
+ $scope.disabled = !$window.jQuery.support.fileInput;
+ $scope.queue = $scope.queue || [];
+ $scope.clear = function (files) {
+ var queue = this.queue,
+ i = queue.length,
+ file = files,
+ length = 1;
+ if (angular.isArray(files)) {
+ file = files[0];
+ length = files.length;
+ }
+ while (i) {
+ i -= 1;
+ if (queue[i] === file) {
+ return queue.splice(i, length);
+ }
+ }
+ };
+ $scope.replace = function (oldFiles, newFiles) {
+ var queue = this.queue,
+ file = oldFiles[0],
+ i,
+ j;
+ for (i = 0; i < queue.length; i += 1) {
+ if (queue[i] === file) {
+ for (j = 0; j < newFiles.length; j += 1) {
+ queue[i + j] = newFiles[j];
+ }
+ return;
+ }
+ }
+ };
+ $scope.applyOnQueue = function (method) {
+ var list = this.queue.slice(0),
+ i,
+ file,
+ promises = [];
+ for (i = 0; i < list.length; i += 1) {
+ file = list[i];
+ if (file[method]) {
+ promises.push(file[method]());
+ }
+ }
+ return $q.all(promises);
+ };
+ $scope.submit = function () {
+ return this.applyOnQueue('$submit');
+ };
+ $scope.cancel = function () {
+ return this.applyOnQueue('$cancel');
+ };
+ // Add upload methods to the scope:
+ angular.extend($scope, uploadMethods);
+ // The fileupload widget will initialize with
+ // the options provided via "data-"-parameters,
+ // as well as those given via options object:
+ $element.fileupload(angular.extend(
+ {scope: $scope},
+ fileUpload.defaults
+ )).on('fileuploadadd', function (e, data) {
+ data.scope = $scope;
+ }).on('fileuploadfail', function (e, data) {
+ if (data.errorThrown === 'abort') {
+ return;
+ }
+ if (data.dataType &&
+ data.dataType.indexOf('json') === data.dataType.length - 4) {
+ try {
+ data.result = angular.fromJson(data.jqXHR.responseText);
+ } catch (ignore) {}
+ }
+ }).on([
+ 'fileuploadadd',
+ 'fileuploadsubmit',
+ 'fileuploadsend',
+ 'fileuploaddone',
+ 'fileuploadfail',
+ 'fileuploadalways',
+ 'fileuploadprogress',
+ 'fileuploadprogressall',
+ 'fileuploadstart',
+ 'fileuploadstop',
+ 'fileuploadchange',
+ 'fileuploadpaste',
+ 'fileuploaddrop',
+ 'fileuploaddragover',
+ 'fileuploadchunksend',
+ 'fileuploadchunkdone',
+ 'fileuploadchunkfail',
+ 'fileuploadchunkalways',
+ 'fileuploadprocessstart',
+ 'fileuploadprocess',
+ 'fileuploadprocessdone',
+ 'fileuploadprocessfail',
+ 'fileuploadprocessalways',
+ 'fileuploadprocessstop'
+ ].join(' '), function (e, data) {
+ $scope.$parent.$applyAsync(function () {
+ if ($scope.$emit(e.type, data).defaultPrevented) {
+ e.preventDefault();
+ }
+ });
+ }).on('remove', function () {
+ // Remove upload methods from the scope,
+ // when the widget is removed:
+ var method;
+ for (method in uploadMethods) {
+ if (uploadMethods.hasOwnProperty(method)) {
+ delete $scope[method];
+ }
+ }
+ });
+ // Observe option changes:
+ $scope.$watch(
+ $attrs.fileUpload,
+ function (newOptions) {
+ if (newOptions) {
+ $element.fileupload('option', newOptions);
+ }
+ }
+ );
+ }
+ ])
+
+ // Provide File Upload progress feedback:
+ .controller('FileUploadProgressController', [
+ '$scope', '$attrs', '$parse',
+ function ($scope, $attrs, $parse) {
+ var fn = $parse($attrs.fileUploadProgress),
+ update = function () {
+ var progress = fn($scope);
+ if (!progress || !progress.total) {
+ return;
+ }
+ $scope.num = Math.floor(
+ progress.loaded / progress.total * 100
+ );
+ };
+ update();
+ $scope.$watch(
+ $attrs.fileUploadProgress + '.loaded',
+ function (newValue, oldValue) {
+ if (newValue !== oldValue) {
+ update();
+ }
+ }
+ );
+ }
+ ])
+
+ // Display File Upload previews:
+ .controller('FileUploadPreviewController', [
+ '$scope', '$element', '$attrs',
+ function ($scope, $element, $attrs) {
+ $scope.$watch(
+ $attrs.fileUploadPreview + '.preview',
+ function (preview) {
+ $element.empty();
+ if (preview) {
+ $element.append(preview);
+ }
+ }
+ );
+ }
+ ])
+
+ .directive('fileUpload', function () {
+ return {
+ controller: 'FileUploadController',
+ scope: true
+ };
+ })
+
+ .directive('fileUploadProgress', function () {
+ return {
+ controller: 'FileUploadProgressController',
+ scope: true
+ };
+ })
+
+ .directive('fileUploadPreview', function () {
+ return {
+ controller: 'FileUploadPreviewController'
+ };
+ })
+
+ // Enhance the HTML5 download attribute to
+ // allow drag&drop of files to the desktop:
+ .directive('download', function () {
+ return function (scope, elm) {
+ elm.on('dragstart', function (e) {
+ try {
+ e.originalEvent.dataTransfer.setData(
+ 'DownloadURL',
+ [
+ 'application/octet-stream',
+ elm.prop('download'),
+ elm.prop('href')
+ ].join(':')
+ );
+ } catch (ignore) {}
+ });
+ };
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-audio.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-audio.js
new file mode 100644
index 000000000..a25377619
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-audio.js
@@ -0,0 +1,113 @@
+/*
+ * jQuery File Upload Audio Preview Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, document */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'load-image',
+ './jquery.fileupload-process'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('blueimp-load-image/js/load-image'),
+ require('./jquery.fileupload-process')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery,
+ window.loadImage
+ );
+ }
+}(function ($, loadImage) {
+ 'use strict';
+
+ // Prepend to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
+ {
+ action: 'loadAudio',
+ // Use the action as prefix for the "@" options:
+ prefix: true,
+ fileTypes: '@',
+ maxFileSize: '@',
+ disabled: '@disableAudioPreview'
+ },
+ {
+ action: 'setAudio',
+ name: '@audioPreviewName',
+ disabled: '@disableAudioPreview'
+ }
+ );
+
+ // The File Upload Audio Preview plugin extends the fileupload widget
+ // with audio preview functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // The regular expression for the types of audio files to load,
+ // matched against the file type:
+ loadAudioFileTypes: /^audio\/.*$/
+ },
+
+ _audioElement: document.createElement('audio'),
+
+ processActions: {
+
+ // Loads the audio file given via data.files and data.index
+ // as audio element if the browser supports playing it.
+ // Accepts the options fileTypes (regular expression)
+ // and maxFileSize (integer) to limit the files to load:
+ loadAudio: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var file = data.files[data.index],
+ url,
+ audio;
+ if (this._audioElement.canPlayType &&
+ this._audioElement.canPlayType(file.type) &&
+ ($.type(options.maxFileSize) !== 'number' ||
+ file.size <= options.maxFileSize) &&
+ (!options.fileTypes ||
+ options.fileTypes.test(file.type))) {
+ url = loadImage.createObjectURL(file);
+ if (url) {
+ audio = this._audioElement.cloneNode(false);
+ audio.src = url;
+ audio.controls = true;
+ data.audio = audio;
+ return data;
+ }
+ }
+ return data;
+ },
+
+ // Sets the audio element as a property of the file object:
+ setAudio: function (data, options) {
+ if (data.audio && !options.disabled) {
+ data.files[data.index][options.name || 'preview'] = data.audio;
+ }
+ return data;
+ }
+
+ }
+
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-image.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-image.js
new file mode 100644
index 000000000..65fc6d7b8
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-image.js
@@ -0,0 +1,326 @@
+/*
+ * jQuery File Upload Image Preview & Resize Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, Blob */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'load-image',
+ 'load-image-meta',
+ 'load-image-scale',
+ 'load-image-exif',
+ 'canvas-to-blob',
+ './jquery.fileupload-process'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('blueimp-load-image/js/load-image'),
+ require('blueimp-load-image/js/load-image-meta'),
+ require('blueimp-load-image/js/load-image-scale'),
+ require('blueimp-load-image/js/load-image-exif'),
+ require('blueimp-canvas-to-blob'),
+ require('./jquery.fileupload-process')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery,
+ window.loadImage
+ );
+ }
+}(function ($, loadImage) {
+ 'use strict';
+
+ // Prepend to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
+ {
+ action: 'loadImageMetaData',
+ disableImageHead: '@',
+ disableExif: '@',
+ disableExifThumbnail: '@',
+ disableExifSub: '@',
+ disableExifGps: '@',
+ disabled: '@disableImageMetaDataLoad'
+ },
+ {
+ action: 'loadImage',
+ // Use the action as prefix for the "@" options:
+ prefix: true,
+ fileTypes: '@',
+ maxFileSize: '@',
+ noRevoke: '@',
+ disabled: '@disableImageLoad'
+ },
+ {
+ action: 'resizeImage',
+ // Use "image" as prefix for the "@" options:
+ prefix: 'image',
+ maxWidth: '@',
+ maxHeight: '@',
+ minWidth: '@',
+ minHeight: '@',
+ crop: '@',
+ orientation: '@',
+ forceResize: '@',
+ disabled: '@disableImageResize'
+ },
+ {
+ action: 'saveImage',
+ quality: '@imageQuality',
+ type: '@imageType',
+ disabled: '@disableImageResize'
+ },
+ {
+ action: 'saveImageMetaData',
+ disabled: '@disableImageMetaDataSave'
+ },
+ {
+ action: 'resizeImage',
+ // Use "preview" as prefix for the "@" options:
+ prefix: 'preview',
+ maxWidth: '@',
+ maxHeight: '@',
+ minWidth: '@',
+ minHeight: '@',
+ crop: '@',
+ orientation: '@',
+ thumbnail: '@',
+ canvas: '@',
+ disabled: '@disableImagePreview'
+ },
+ {
+ action: 'setImage',
+ name: '@imagePreviewName',
+ disabled: '@disableImagePreview'
+ },
+ {
+ action: 'deleteImageReferences',
+ disabled: '@disableImageReferencesDeletion'
+ }
+ );
+
+ // The File Upload Resize plugin extends the fileupload widget
+ // with image resize functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // The regular expression for the types of images to load:
+ // matched against the file type:
+ loadImageFileTypes: /^image\/(gif|jpeg|png|svg\+xml)$/,
+ // The maximum file size of images to load:
+ loadImageMaxFileSize: 10000000, // 10MB
+ // The maximum width of resized images:
+ imageMaxWidth: 1920,
+ // The maximum height of resized images:
+ imageMaxHeight: 1080,
+ // Defines the image orientation (1-8) or takes the orientation
+ // value from Exif data if set to true:
+ imageOrientation: false,
+ // Define if resized images should be cropped or only scaled:
+ imageCrop: false,
+ // Disable the resize image functionality by default:
+ disableImageResize: true,
+ // The maximum width of the preview images:
+ previewMaxWidth: 80,
+ // The maximum height of the preview images:
+ previewMaxHeight: 80,
+ // Defines the preview orientation (1-8) or takes the orientation
+ // value from Exif data if set to true:
+ previewOrientation: true,
+ // Create the preview using the Exif data thumbnail:
+ previewThumbnail: true,
+ // Define if preview images should be cropped or only scaled:
+ previewCrop: false,
+ // Define if preview images should be resized as canvas elements:
+ previewCanvas: true
+ },
+
+ processActions: {
+
+ // Loads the image given via data.files and data.index
+ // as img element, if the browser supports the File API.
+ // Accepts the options fileTypes (regular expression)
+ // and maxFileSize (integer) to limit the files to load:
+ loadImage: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var that = this,
+ file = data.files[data.index],
+ dfd = $.Deferred();
+ if (($.type(options.maxFileSize) === 'number' &&
+ file.size > options.maxFileSize) ||
+ (options.fileTypes &&
+ !options.fileTypes.test(file.type)) ||
+ !loadImage(
+ file,
+ function (img) {
+ if (img.src) {
+ data.img = img;
+ }
+ dfd.resolveWith(that, [data]);
+ },
+ options
+ )) {
+ return data;
+ }
+ return dfd.promise();
+ },
+
+ // Resizes the image given as data.canvas or data.img
+ // and updates data.canvas or data.img with the resized image.
+ // Also stores the resized image as preview property.
+ // Accepts the options maxWidth, maxHeight, minWidth,
+ // minHeight, canvas and crop:
+ resizeImage: function (data, options) {
+ if (options.disabled || !(data.canvas || data.img)) {
+ return data;
+ }
+ options = $.extend({canvas: true}, options);
+ var that = this,
+ dfd = $.Deferred(),
+ img = (options.canvas && data.canvas) || data.img,
+ resolve = function (newImg) {
+ if (newImg && (newImg.width !== img.width ||
+ newImg.height !== img.height ||
+ options.forceResize)) {
+ data[newImg.getContext ? 'canvas' : 'img'] = newImg;
+ }
+ data.preview = newImg;
+ dfd.resolveWith(that, [data]);
+ },
+ thumbnail;
+ if (data.exif) {
+ if (options.orientation === true) {
+ options.orientation = data.exif.get('Orientation');
+ }
+ if (options.thumbnail) {
+ thumbnail = data.exif.get('Thumbnail');
+ if (thumbnail) {
+ loadImage(thumbnail, resolve, options);
+ return dfd.promise();
+ }
+ }
+ // Prevent orienting the same image twice:
+ if (data.orientation) {
+ delete options.orientation;
+ } else {
+ data.orientation = options.orientation;
+ }
+ }
+ if (img) {
+ resolve(loadImage.scale(img, options));
+ return dfd.promise();
+ }
+ return data;
+ },
+
+ // Saves the processed image given as data.canvas
+ // inplace at data.index of data.files:
+ saveImage: function (data, options) {
+ if (!data.canvas || options.disabled) {
+ return data;
+ }
+ var that = this,
+ file = data.files[data.index],
+ dfd = $.Deferred();
+ if (data.canvas.toBlob) {
+ data.canvas.toBlob(
+ function (blob) {
+ if (!blob.name) {
+ if (file.type === blob.type) {
+ blob.name = file.name;
+ } else if (file.name) {
+ blob.name = file.name.replace(
+ /\.\w+$/,
+ '.' + blob.type.substr(6)
+ );
+ }
+ }
+ // Don't restore invalid meta data:
+ if (file.type !== blob.type) {
+ delete data.imageHead;
+ }
+ // Store the created blob at the position
+ // of the original file in the files list:
+ data.files[data.index] = blob;
+ dfd.resolveWith(that, [data]);
+ },
+ options.type || file.type,
+ options.quality
+ );
+ } else {
+ return data;
+ }
+ return dfd.promise();
+ },
+
+ loadImageMetaData: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var that = this,
+ dfd = $.Deferred();
+ loadImage.parseMetaData(data.files[data.index], function (result) {
+ $.extend(data, result);
+ dfd.resolveWith(that, [data]);
+ }, options);
+ return dfd.promise();
+ },
+
+ saveImageMetaData: function (data, options) {
+ if (!(data.imageHead && data.canvas &&
+ data.canvas.toBlob && !options.disabled)) {
+ return data;
+ }
+ var file = data.files[data.index],
+ blob = new Blob([
+ data.imageHead,
+ // Resized images always have a head size of 20 bytes,
+ // including the JPEG marker and a minimal JFIF header:
+ this._blobSlice.call(file, 20)
+ ], {type: file.type});
+ blob.name = file.name;
+ data.files[data.index] = blob;
+ return data;
+ },
+
+ // Sets the resized version of the image as a property of the
+ // file object, must be called after "saveImage":
+ setImage: function (data, options) {
+ if (data.preview && !options.disabled) {
+ data.files[data.index][options.name || 'preview'] = data.preview;
+ }
+ return data;
+ },
+
+ deleteImageReferences: function (data, options) {
+ if (!options.disabled) {
+ delete data.img;
+ delete data.canvas;
+ delete data.preview;
+ delete data.imageHead;
+ }
+ return data;
+ }
+
+ }
+
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-jquery-ui.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-jquery-ui.js
new file mode 100644
index 000000000..7b136b379
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-jquery-ui.js
@@ -0,0 +1,161 @@
+/*
+ * jQuery File Upload jQuery UI Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ './jquery.fileupload-ui'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('./jquery.fileupload-ui')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ processdone: function (e, data) {
+ data.context.find('.start').button('enable');
+ },
+ progress: function (e, data) {
+ if (data.context) {
+ data.context.find('.progress').progressbar(
+ 'option',
+ 'value',
+ parseInt(data.loaded / data.total * 100, 10)
+ );
+ }
+ },
+ progressall: function (e, data) {
+ var $this = $(this);
+ $this.find('.fileupload-progress')
+ .find('.progress').progressbar(
+ 'option',
+ 'value',
+ parseInt(data.loaded / data.total * 100, 10)
+ ).end()
+ .find('.progress-extended').each(function () {
+ $(this).html(
+ ($this.data('blueimp-fileupload') ||
+ $this.data('fileupload'))
+ ._renderExtendedProgress(data)
+ );
+ });
+ }
+ },
+
+ _renderUpload: function (func, files) {
+ var node = this._super(func, files),
+ showIconText = $(window).width() > 480;
+ node.find('.progress').empty().progressbar();
+ node.find('.start').button({
+ icons: {primary: 'ui-icon-circle-arrow-e'},
+ text: showIconText
+ });
+ node.find('.cancel').button({
+ icons: {primary: 'ui-icon-cancel'},
+ text: showIconText
+ });
+ if (node.hasClass('fade')) {
+ node.hide();
+ }
+ return node;
+ },
+
+ _renderDownload: function (func, files) {
+ var node = this._super(func, files),
+ showIconText = $(window).width() > 480;
+ node.find('.delete').button({
+ icons: {primary: 'ui-icon-trash'},
+ text: showIconText
+ });
+ if (node.hasClass('fade')) {
+ node.hide();
+ }
+ return node;
+ },
+
+ _startHandler: function (e) {
+ $(e.currentTarget).button('disable');
+ this._super(e);
+ },
+
+ _transition: function (node) {
+ var deferred = $.Deferred();
+ if (node.hasClass('fade')) {
+ node.fadeToggle(
+ this.options.transitionDuration,
+ this.options.transitionEasing,
+ function () {
+ deferred.resolveWith(node);
+ }
+ );
+ } else {
+ deferred.resolveWith(node);
+ }
+ return deferred;
+ },
+
+ _create: function () {
+ this._super();
+ this.element
+ .find('.fileupload-buttonbar')
+ .find('.fileinput-button').each(function () {
+ var input = $(this).find('input:file').detach();
+ $(this)
+ .button({icons: {primary: 'ui-icon-plusthick'}})
+ .append(input);
+ })
+ .end().find('.start')
+ .button({icons: {primary: 'ui-icon-circle-arrow-e'}})
+ .end().find('.cancel')
+ .button({icons: {primary: 'ui-icon-cancel'}})
+ .end().find('.delete')
+ .button({icons: {primary: 'ui-icon-trash'}})
+ .end().find('.progress').progressbar();
+ },
+
+ _destroy: function () {
+ this.element
+ .find('.fileupload-buttonbar')
+ .find('.fileinput-button').each(function () {
+ var input = $(this).find('input:file').detach();
+ $(this)
+ .button('destroy')
+ .append(input);
+ })
+ .end().find('.start')
+ .button('destroy')
+ .end().find('.cancel')
+ .button('destroy')
+ .end().find('.delete')
+ .button('destroy')
+ .end().find('.progress').progressbar('destroy');
+ this._super();
+ }
+
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-process.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-process.js
new file mode 100644
index 000000000..638f0d26b
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-process.js
@@ -0,0 +1,178 @@
+/*
+ * jQuery File Upload Processing Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ './jquery.fileupload'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('./jquery.fileupload')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery
+ );
+ }
+}(function ($) {
+ 'use strict';
+
+ var originalAdd = $.blueimp.fileupload.prototype.options.add;
+
+ // The File Upload Processing plugin extends the fileupload widget
+ // with file processing functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // The list of processing actions:
+ processQueue: [
+ /*
+ {
+ action: 'log',
+ type: 'debug'
+ }
+ */
+ ],
+ add: function (e, data) {
+ var $this = $(this);
+ data.process(function () {
+ return $this.fileupload('process', data);
+ });
+ originalAdd.call(this, e, data);
+ }
+ },
+
+ processActions: {
+ /*
+ log: function (data, options) {
+ console[options.type](
+ 'Processing "' + data.files[data.index].name + '"'
+ );
+ }
+ */
+ },
+
+ _processFile: function (data, originalData) {
+ var that = this,
+ dfd = $.Deferred().resolveWith(that, [data]),
+ chain = dfd.promise();
+ this._trigger('process', null, data);
+ $.each(data.processQueue, function (i, settings) {
+ var func = function (data) {
+ if (originalData.errorThrown) {
+ return $.Deferred()
+ .rejectWith(that, [originalData]).promise();
+ }
+ return that.processActions[settings.action].call(
+ that,
+ data,
+ settings
+ );
+ };
+ chain = chain.then(func, settings.always && func);
+ });
+ chain
+ .done(function () {
+ that._trigger('processdone', null, data);
+ that._trigger('processalways', null, data);
+ })
+ .fail(function () {
+ that._trigger('processfail', null, data);
+ that._trigger('processalways', null, data);
+ });
+ return chain;
+ },
+
+ // Replaces the settings of each processQueue item that
+ // are strings starting with an "@", using the remaining
+ // substring as key for the option map,
+ // e.g. "@autoUpload" is replaced with options.autoUpload:
+ _transformProcessQueue: function (options) {
+ var processQueue = [];
+ $.each(options.processQueue, function () {
+ var settings = {},
+ action = this.action,
+ prefix = this.prefix === true ? action : this.prefix;
+ $.each(this, function (key, value) {
+ if ($.type(value) === 'string' &&
+ value.charAt(0) === '@') {
+ settings[key] = options[
+ value.slice(1) || (prefix ? prefix +
+ key.charAt(0).toUpperCase() + key.slice(1) : key)
+ ];
+ } else {
+ settings[key] = value;
+ }
+
+ });
+ processQueue.push(settings);
+ });
+ options.processQueue = processQueue;
+ },
+
+ // Returns the number of files currently in the processsing queue:
+ processing: function () {
+ return this._processing;
+ },
+
+ // Processes the files given as files property of the data parameter,
+ // returns a Promise object that allows to bind callbacks:
+ process: function (data) {
+ var that = this,
+ options = $.extend({}, this.options, data);
+ if (options.processQueue && options.processQueue.length) {
+ this._transformProcessQueue(options);
+ if (this._processing === 0) {
+ this._trigger('processstart');
+ }
+ $.each(data.files, function (index) {
+ var opts = index ? $.extend({}, options) : options,
+ func = function () {
+ if (data.errorThrown) {
+ return $.Deferred()
+ .rejectWith(that, [data]).promise();
+ }
+ return that._processFile(opts, data);
+ };
+ opts.index = index;
+ that._processing += 1;
+ that._processingQueue = that._processingQueue.then(func, func)
+ .always(function () {
+ that._processing -= 1;
+ if (that._processing === 0) {
+ that._trigger('processstop');
+ }
+ });
+ });
+ }
+ return this._processingQueue;
+ },
+
+ _create: function () {
+ this._super();
+ this._processing = 0;
+ this._processingQueue = $.Deferred().resolveWith(this)
+ .promise();
+ }
+
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-ui.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-ui.js
new file mode 100644
index 000000000..5058084b4
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-ui.js
@@ -0,0 +1,714 @@
+/*
+ * jQuery File Upload User Interface Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'blueimp-tmpl',
+ './jquery.fileupload-image',
+ './jquery.fileupload-audio',
+ './jquery.fileupload-video',
+ './jquery.fileupload-validate'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('blueimp-tmpl'),
+ require('./jquery.fileupload-image'),
+ require('./jquery.fileupload-audio'),
+ require('./jquery.fileupload-video'),
+ require('./jquery.fileupload-validate')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery,
+ window.tmpl
+ );
+ }
+}(function ($, tmpl) {
+ 'use strict';
+
+ $.blueimp.fileupload.prototype._specialOptions.push(
+ 'filesContainer',
+ 'uploadTemplateId',
+ 'downloadTemplateId'
+ );
+
+ // The UI version extends the file upload widget
+ // and adds complete user interface interaction:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // By default, files added to the widget are uploaded as soon
+ // as the user clicks on the start buttons. To enable automatic
+ // uploads, set the following option to true:
+ autoUpload: false,
+ // The ID of the upload template:
+ uploadTemplateId: 'template-upload',
+ // The ID of the download template:
+ downloadTemplateId: 'template-download',
+ // The container for the list of files. If undefined, it is set to
+ // an element with class "files" inside of the widget element:
+ filesContainer: undefined,
+ // By default, files are appended to the files container.
+ // Set the following option to true, to prepend files instead:
+ prependFiles: false,
+ // The expected data type of the upload response, sets the dataType
+ // option of the $.ajax upload requests:
+ dataType: 'json',
+
+ // Error and info messages:
+ messages: {
+ unknownError: 'Unknown error'
+ },
+
+ // Function returning the current number of files,
+ // used by the maxNumberOfFiles validation:
+ getNumberOfFiles: function () {
+ return this.filesContainer.children()
+ .not('.processing').length;
+ },
+
+ // Callback to retrieve the list of files from the server response:
+ getFilesFromResponse: function (data) {
+ if (data.result && $.isArray(data.result.files)) {
+ return data.result.files;
+ }
+ return [];
+ },
+
+ // The add callback is invoked as soon as files are added to the fileupload
+ // widget (via file input selection, drag & drop or add API call).
+ // See the basic file upload widget for more information:
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var $this = $(this),
+ that = $this.data('blueimp-fileupload') ||
+ $this.data('fileupload'),
+ options = that.options;
+ data.context = that._renderUpload(data.files)
+ .data('data', data)
+ .addClass('processing');
+ options.filesContainer[
+ options.prependFiles ? 'prepend' : 'append'
+ ](data.context);
+ that._forceReflow(data.context);
+ that._transition(data.context);
+ data.process(function () {
+ return $this.fileupload('process', data);
+ }).always(function () {
+ data.context.each(function (index) {
+ $(this).find('.size').text(
+ that._formatFileSize(data.files[index].size)
+ );
+ }).removeClass('processing');
+ that._renderPreviews(data);
+ }).done(function () {
+ data.context.find('.start').prop('disabled', false);
+ if ((that._trigger('added', e, data) !== false) &&
+ (options.autoUpload || data.autoUpload) &&
+ data.autoUpload !== false) {
+ data.submit();
+ }
+ }).fail(function () {
+ if (data.files.error) {
+ data.context.each(function (index) {
+ var error = data.files[index].error;
+ if (error) {
+ $(this).find('.error').text(error);
+ }
+ });
+ }
+ });
+ },
+ // Callback for the start of each file upload request:
+ send: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload');
+ if (data.context && data.dataType &&
+ data.dataType.substr(0, 6) === 'iframe') {
+ // Iframe Transport does not support progress events.
+ // In lack of an indeterminate progress bar, we set
+ // the progress to 100%, showing the full animated bar:
+ data.context
+ .find('.progress').addClass(
+ !$.support.transition && 'progress-animated'
+ )
+ .attr('aria-valuenow', 100)
+ .children().first().css(
+ 'width',
+ '100%'
+ );
+ }
+ return that._trigger('sent', e, data);
+ },
+ // Callback for successful uploads:
+ done: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload'),
+ getFilesFromResponse = data.getFilesFromResponse ||
+ that.options.getFilesFromResponse,
+ files = getFilesFromResponse(data),
+ template,
+ deferred;
+ if (data.context) {
+ data.context.each(function (index) {
+ var file = files[index] ||
+ {error: 'Empty file upload result'};
+ deferred = that._addFinishedDeferreds();
+ that._transition($(this)).done(
+ function () {
+ var node = $(this);
+ template = that._renderDownload([file])
+ .replaceAll(node);
+ that._forceReflow(template);
+ that._transition(template).done(
+ function () {
+ data.context = $(this);
+ that._trigger('completed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ }
+ );
+ });
+ } else {
+ template = that._renderDownload(files)[
+ that.options.prependFiles ? 'prependTo' : 'appendTo'
+ ](that.options.filesContainer);
+ that._forceReflow(template);
+ deferred = that._addFinishedDeferreds();
+ that._transition(template).done(
+ function () {
+ data.context = $(this);
+ that._trigger('completed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ }
+ },
+ // Callback for failed (abort or error) uploads:
+ fail: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload'),
+ template,
+ deferred;
+ if (data.context) {
+ data.context.each(function (index) {
+ if (data.errorThrown !== 'abort') {
+ var file = data.files[index];
+ file.error = file.error || data.errorThrown ||
+ data.i18n('unknownError');
+ deferred = that._addFinishedDeferreds();
+ that._transition($(this)).done(
+ function () {
+ var node = $(this);
+ template = that._renderDownload([file])
+ .replaceAll(node);
+ that._forceReflow(template);
+ that._transition(template).done(
+ function () {
+ data.context = $(this);
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ }
+ );
+ } else {
+ deferred = that._addFinishedDeferreds();
+ that._transition($(this)).done(
+ function () {
+ $(this).remove();
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ }
+ });
+ } else if (data.errorThrown !== 'abort') {
+ data.context = that._renderUpload(data.files)[
+ that.options.prependFiles ? 'prependTo' : 'appendTo'
+ ](that.options.filesContainer)
+ .data('data', data);
+ that._forceReflow(data.context);
+ deferred = that._addFinishedDeferreds();
+ that._transition(data.context).done(
+ function () {
+ data.context = $(this);
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ }
+ );
+ } else {
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ that._addFinishedDeferreds().resolve();
+ }
+ },
+ // Callback for upload progress events:
+ progress: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var progress = Math.floor(data.loaded / data.total * 100);
+ if (data.context) {
+ data.context.each(function () {
+ $(this).find('.progress')
+ .attr('aria-valuenow', progress)
+ .children().first().css(
+ 'width',
+ progress + '%'
+ );
+ });
+ }
+ },
+ // Callback for global upload progress events:
+ progressall: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var $this = $(this),
+ progress = Math.floor(data.loaded / data.total * 100),
+ globalProgressNode = $this.find('.fileupload-progress'),
+ extendedProgressNode = globalProgressNode
+ .find('.progress-extended');
+ if (extendedProgressNode.length) {
+ extendedProgressNode.html(
+ ($this.data('blueimp-fileupload') || $this.data('fileupload'))
+ ._renderExtendedProgress(data)
+ );
+ }
+ globalProgressNode
+ .find('.progress')
+ .attr('aria-valuenow', progress)
+ .children().first().css(
+ 'width',
+ progress + '%'
+ );
+ },
+ // Callback for uploads start, equivalent to the global ajaxStart event:
+ start: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload');
+ that._resetFinishedDeferreds();
+ that._transition($(this).find('.fileupload-progress')).done(
+ function () {
+ that._trigger('started', e);
+ }
+ );
+ },
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
+ stop: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload'),
+ deferred = that._addFinishedDeferreds();
+ $.when.apply($, that._getFinishedDeferreds())
+ .done(function () {
+ that._trigger('stopped', e);
+ });
+ that._transition($(this).find('.fileupload-progress')).done(
+ function () {
+ $(this).find('.progress')
+ .attr('aria-valuenow', '0')
+ .children().first().css('width', '0%');
+ $(this).find('.progress-extended').html(' ');
+ deferred.resolve();
+ }
+ );
+ },
+ processstart: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ $(this).addClass('fileupload-processing');
+ },
+ processstop: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ $(this).removeClass('fileupload-processing');
+ },
+ // Callback for file deletion:
+ destroy: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that = $(this).data('blueimp-fileupload') ||
+ $(this).data('fileupload'),
+ removeNode = function () {
+ that._transition(data.context).done(
+ function () {
+ $(this).remove();
+ that._trigger('destroyed', e, data);
+ }
+ );
+ };
+ if (data.url) {
+ data.dataType = data.dataType || that.options.dataType;
+ $.ajax(data).done(removeNode).fail(function () {
+ that._trigger('destroyfailed', e, data);
+ });
+ } else {
+ removeNode();
+ }
+ }
+ },
+
+ _resetFinishedDeferreds: function () {
+ this._finishedUploads = [];
+ },
+
+ _addFinishedDeferreds: function (deferred) {
+ if (!deferred) {
+ deferred = $.Deferred();
+ }
+ this._finishedUploads.push(deferred);
+ return deferred;
+ },
+
+ _getFinishedDeferreds: function () {
+ return this._finishedUploads;
+ },
+
+ // Link handler, that allows to download files
+ // by drag & drop of the links to the desktop:
+ _enableDragToDesktop: function () {
+ var link = $(this),
+ url = link.prop('href'),
+ name = link.prop('download'),
+ type = 'application/octet-stream';
+ link.bind('dragstart', function (e) {
+ try {
+ e.originalEvent.dataTransfer.setData(
+ 'DownloadURL',
+ [type, name, url].join(':')
+ );
+ } catch (ignore) {}
+ });
+ },
+
+ _formatFileSize: function (bytes) {
+ if (typeof bytes !== 'number') {
+ return '';
+ }
+ if (bytes >= 1000000000) {
+ return (bytes / 1000000000).toFixed(2) + ' GB';
+ }
+ if (bytes >= 1000000) {
+ return (bytes / 1000000).toFixed(2) + ' MB';
+ }
+ return (bytes / 1000).toFixed(2) + ' KB';
+ },
+
+ _formatBitrate: function (bits) {
+ if (typeof bits !== 'number') {
+ return '';
+ }
+ if (bits >= 1000000000) {
+ return (bits / 1000000000).toFixed(2) + ' Gbit/s';
+ }
+ if (bits >= 1000000) {
+ return (bits / 1000000).toFixed(2) + ' Mbit/s';
+ }
+ if (bits >= 1000) {
+ return (bits / 1000).toFixed(2) + ' kbit/s';
+ }
+ return bits.toFixed(2) + ' bit/s';
+ },
+
+ _formatTime: function (seconds) {
+ var date = new Date(seconds * 1000),
+ days = Math.floor(seconds / 86400);
+ days = days ? days + 'd ' : '';
+ return days +
+ ('0' + date.getUTCHours()).slice(-2) + ':' +
+ ('0' + date.getUTCMinutes()).slice(-2) + ':' +
+ ('0' + date.getUTCSeconds()).slice(-2);
+ },
+
+ _formatPercentage: function (floatValue) {
+ return (floatValue * 100).toFixed(2) + ' %';
+ },
+
+ _renderExtendedProgress: function (data) {
+ return this._formatBitrate(data.bitrate) + ' | ' +
+ this._formatTime(
+ (data.total - data.loaded) * 8 / data.bitrate
+ ) + ' | ' +
+ this._formatPercentage(
+ data.loaded / data.total
+ ) + ' | ' +
+ this._formatFileSize(data.loaded) + ' / ' +
+ this._formatFileSize(data.total);
+ },
+
+ _renderTemplate: function (func, files) {
+ if (!func) {
+ return $();
+ }
+ var result = func({
+ files: files,
+ formatFileSize: this._formatFileSize,
+ options: this.options
+ });
+ if (result instanceof $) {
+ return result;
+ }
+ return $(this.options.templatesContainer).html(result).children();
+ },
+
+ _renderPreviews: function (data) {
+ data.context.find('.preview').each(function (index, elm) {
+ $(elm).append(data.files[index].preview);
+ });
+ },
+
+ _renderUpload: function (files) {
+ return this._renderTemplate(
+ this.options.uploadTemplate,
+ files
+ );
+ },
+
+ _renderDownload: function (files) {
+ return this._renderTemplate(
+ this.options.downloadTemplate,
+ files
+ ).find('a[download]').each(this._enableDragToDesktop).end();
+ },
+
+ _startHandler: function (e) {
+ e.preventDefault();
+ var button = $(e.currentTarget),
+ template = button.closest('.template-upload'),
+ data = template.data('data');
+ button.prop('disabled', true);
+ if (data && data.submit) {
+ data.submit();
+ }
+ },
+
+ _cancelHandler: function (e) {
+ e.preventDefault();
+ var template = $(e.currentTarget)
+ .closest('.template-upload,.template-download'),
+ data = template.data('data') || {};
+ data.context = data.context || template;
+ if (data.abort) {
+ data.abort();
+ } else {
+ data.errorThrown = 'abort';
+ this._trigger('fail', e, data);
+ }
+ },
+
+ _deleteHandler: function (e) {
+ e.preventDefault();
+ var button = $(e.currentTarget);
+ this._trigger('destroy', e, $.extend({
+ context: button.closest('.template-download'),
+ type: 'DELETE'
+ }, button.data()));
+ },
+
+ _forceReflow: function (node) {
+ return $.support.transition && node.length &&
+ node[0].offsetWidth;
+ },
+
+ _transition: function (node) {
+ var dfd = $.Deferred();
+ if ($.support.transition && node.hasClass('fade') && node.is(':visible')) {
+ node.bind(
+ $.support.transition.end,
+ function (e) {
+ // Make sure we don't respond to other transitions events
+ // in the container element, e.g. from button elements:
+ if (e.target === node[0]) {
+ node.unbind($.support.transition.end);
+ dfd.resolveWith(node);
+ }
+ }
+ ).toggleClass('in');
+ } else {
+ node.toggleClass('in');
+ dfd.resolveWith(node);
+ }
+ return dfd;
+ },
+
+ _initButtonBarEventHandlers: function () {
+ var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
+ filesList = this.options.filesContainer;
+ this._on(fileUploadButtonBar.find('.start'), {
+ click: function (e) {
+ e.preventDefault();
+ filesList.find('.start').click();
+ }
+ });
+ this._on(fileUploadButtonBar.find('.cancel'), {
+ click: function (e) {
+ e.preventDefault();
+ filesList.find('.cancel').click();
+ }
+ });
+ this._on(fileUploadButtonBar.find('.delete'), {
+ click: function (e) {
+ e.preventDefault();
+ filesList.find('.toggle:checked')
+ .closest('.template-download')
+ .find('.delete').click();
+ fileUploadButtonBar.find('.toggle')
+ .prop('checked', false);
+ }
+ });
+ this._on(fileUploadButtonBar.find('.toggle'), {
+ change: function (e) {
+ filesList.find('.toggle').prop(
+ 'checked',
+ $(e.currentTarget).is(':checked')
+ );
+ }
+ });
+ },
+
+ _destroyButtonBarEventHandlers: function () {
+ this._off(
+ this.element.find('.fileupload-buttonbar')
+ .find('.start, .cancel, .delete'),
+ 'click'
+ );
+ this._off(
+ this.element.find('.fileupload-buttonbar .toggle'),
+ 'change.'
+ );
+ },
+
+ _initEventHandlers: function () {
+ this._super();
+ this._on(this.options.filesContainer, {
+ 'click .start': this._startHandler,
+ 'click .cancel': this._cancelHandler,
+ 'click .delete': this._deleteHandler
+ });
+ this._initButtonBarEventHandlers();
+ },
+
+ _destroyEventHandlers: function () {
+ this._destroyButtonBarEventHandlers();
+ this._off(this.options.filesContainer, 'click');
+ this._super();
+ },
+
+ _enableFileInputButton: function () {
+ this.element.find('.fileinput-button input')
+ .prop('disabled', false)
+ .parent().removeClass('disabled');
+ },
+
+ _disableFileInputButton: function () {
+ this.element.find('.fileinput-button input')
+ .prop('disabled', true)
+ .parent().addClass('disabled');
+ },
+
+ _initTemplates: function () {
+ var options = this.options;
+ options.templatesContainer = this.document[0].createElement(
+ options.filesContainer.prop('nodeName')
+ );
+ if (tmpl) {
+ if (options.uploadTemplateId) {
+ options.uploadTemplate = tmpl(options.uploadTemplateId);
+ }
+ if (options.downloadTemplateId) {
+ options.downloadTemplate = tmpl(options.downloadTemplateId);
+ }
+ }
+ },
+
+ _initFilesContainer: function () {
+ var options = this.options;
+ if (options.filesContainer === undefined) {
+ options.filesContainer = this.element.find('.files');
+ } else if (!(options.filesContainer instanceof $)) {
+ options.filesContainer = $(options.filesContainer);
+ }
+ },
+
+ _initSpecialOptions: function () {
+ this._super();
+ this._initFilesContainer();
+ this._initTemplates();
+ },
+
+ _create: function () {
+ this._super();
+ this._resetFinishedDeferreds();
+ if (!$.support.fileInput) {
+ this._disableFileInputButton();
+ }
+ },
+
+ enable: function () {
+ var wasDisabled = false;
+ if (this.options.disabled) {
+ wasDisabled = true;
+ }
+ this._super();
+ if (wasDisabled) {
+ this.element.find('input, button').prop('disabled', false);
+ this._enableFileInputButton();
+ }
+ },
+
+ disable: function () {
+ if (!this.options.disabled) {
+ this.element.find('input, button').prop('disabled', true);
+ this._disableFileInputButton();
+ }
+ this._super();
+ }
+
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-validate.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-validate.js
new file mode 100644
index 000000000..eebeb3733
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-validate.js
@@ -0,0 +1,125 @@
+/*
+ * jQuery File Upload Validation Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require, window */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ './jquery.fileupload-process'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('./jquery.fileupload-process')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery
+ );
+ }
+}(function ($) {
+ 'use strict';
+
+ // Append to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.push(
+ {
+ action: 'validate',
+ // Always trigger this action,
+ // even if the previous action was rejected:
+ always: true,
+ // Options taken from the global options map:
+ acceptFileTypes: '@',
+ maxFileSize: '@',
+ minFileSize: '@',
+ maxNumberOfFiles: '@',
+ disabled: '@disableValidation'
+ }
+ );
+
+ // The File Upload Validation plugin extends the fileupload widget
+ // with file validation functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ /*
+ // The regular expression for allowed file types, matches
+ // against either file type or file name:
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
+ // The maximum allowed file size in bytes:
+ maxFileSize: 10000000, // 10 MB
+ // The minimum allowed file size in bytes:
+ minFileSize: undefined, // No minimal file size
+ // The limit of files to be uploaded:
+ maxNumberOfFiles: 10,
+ */
+
+ // Function returning the current number of files,
+ // has to be overriden for maxNumberOfFiles validation:
+ getNumberOfFiles: $.noop,
+
+ // Error and info messages:
+ messages: {
+ maxNumberOfFiles: 'Maximum number of files exceeded',
+ acceptFileTypes: 'File type not allowed',
+ maxFileSize: 'File is too large',
+ minFileSize: 'File is too small'
+ }
+ },
+
+ processActions: {
+
+ validate: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var dfd = $.Deferred(),
+ settings = this.options,
+ file = data.files[data.index],
+ fileSize;
+ if (options.minFileSize || options.maxFileSize) {
+ fileSize = file.size;
+ }
+ if ($.type(options.maxNumberOfFiles) === 'number' &&
+ (settings.getNumberOfFiles() || 0) + data.files.length >
+ options.maxNumberOfFiles) {
+ file.error = settings.i18n('maxNumberOfFiles');
+ } else if (options.acceptFileTypes &&
+ !(options.acceptFileTypes.test(file.type) ||
+ options.acceptFileTypes.test(file.name))) {
+ file.error = settings.i18n('acceptFileTypes');
+ } else if (fileSize > options.maxFileSize) {
+ file.error = settings.i18n('maxFileSize');
+ } else if ($.type(fileSize) === 'number' &&
+ fileSize < options.minFileSize) {
+ file.error = settings.i18n('minFileSize');
+ } else {
+ delete file.error;
+ }
+ if (file.error || data.files.error) {
+ data.files.error = true;
+ dfd.rejectWith(this, [data]);
+ } else {
+ dfd.resolveWith(this, [data]);
+ }
+ return dfd.promise();
+ }
+
+ }
+
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-video.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-video.js
new file mode 100644
index 000000000..aedcec2ba
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload-video.js
@@ -0,0 +1,113 @@
+/*
+ * jQuery File Upload Video Preview Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, document */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'load-image',
+ './jquery.fileupload-process'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('blueimp-load-image/js/load-image'),
+ require('./jquery.fileupload-process')
+ );
+ } else {
+ // Browser globals:
+ factory(
+ window.jQuery,
+ window.loadImage
+ );
+ }
+}(function ($, loadImage) {
+ 'use strict';
+
+ // Prepend to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
+ {
+ action: 'loadVideo',
+ // Use the action as prefix for the "@" options:
+ prefix: true,
+ fileTypes: '@',
+ maxFileSize: '@',
+ disabled: '@disableVideoPreview'
+ },
+ {
+ action: 'setVideo',
+ name: '@videoPreviewName',
+ disabled: '@disableVideoPreview'
+ }
+ );
+
+ // The File Upload Video Preview plugin extends the fileupload widget
+ // with video preview functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+ options: {
+ // The regular expression for the types of video files to load,
+ // matched against the file type:
+ loadVideoFileTypes: /^video\/.*$/
+ },
+
+ _videoElement: document.createElement('video'),
+
+ processActions: {
+
+ // Loads the video file given via data.files and data.index
+ // as video element if the browser supports playing it.
+ // Accepts the options fileTypes (regular expression)
+ // and maxFileSize (integer) to limit the files to load:
+ loadVideo: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var file = data.files[data.index],
+ url,
+ video;
+ if (this._videoElement.canPlayType &&
+ this._videoElement.canPlayType(file.type) &&
+ ($.type(options.maxFileSize) !== 'number' ||
+ file.size <= options.maxFileSize) &&
+ (!options.fileTypes ||
+ options.fileTypes.test(file.type))) {
+ url = loadImage.createObjectURL(file);
+ if (url) {
+ video = this._videoElement.cloneNode(false);
+ video.src = url;
+ video.controls = true;
+ data.video = video;
+ return data;
+ }
+ }
+ return data;
+ },
+
+ // Sets the video element as a property of the file object:
+ setVideo: function (data, options) {
+ if (data.video && !options.disabled) {
+ data.files[data.index][options.name || 'preview'] = data.video;
+ }
+ return data;
+ }
+
+ }
+
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload.js
new file mode 100644
index 000000000..85c961879
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.fileupload.js
@@ -0,0 +1,1486 @@
+/*
+ * jQuery File Upload Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://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/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:
+ $(' ').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.uploadName || 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.uploadName || 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 = $(' ').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 || ($.type(mcs) === 'function' ? mcs(options) : 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 + ($.type(mcs) === 'function' ? mcs(o) : 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;
+ $('').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);
+ }
+
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.iframe-transport.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.iframe-transport.js
new file mode 100644
index 000000000..8d25c4641
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/jquery.iframe-transport.js
@@ -0,0 +1,224 @@
+/*
+ * jQuery Iframe Transport Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require, window, document, JSON */
+
+;(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,
+ jsonAPI = $,
+ jsonParse = 'parseJSON';
+
+ if ('JSON' in window && 'parse' in JSON) {
+ jsonAPI = JSON;
+ jsonParse = 'parse';
+ }
+
+ // 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.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 = $(
+ ''
+ ).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):
+ $('')
+ .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) {
+ $(' ')
+ .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 && jsonAPI[jsonParse]($(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());
+ }
+ }
+ });
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/main.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/main.js
new file mode 100644
index 000000000..0403682e7
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/main.js
@@ -0,0 +1,75 @@
+/*
+ * jQuery File Upload Plugin JS Example
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global $, window */
+
+$(function () {
+ 'use strict';
+
+ // Initialize the jQuery File Upload widget:
+ $('#fileupload').fileupload({
+ // Uncomment the following to send cross-domain cookies:
+ //xhrFields: {withCredentials: true},
+ url: 'server/php/'
+ });
+
+ // Enable iframe cross-domain access via redirect option:
+ $('#fileupload').fileupload(
+ 'option',
+ 'redirect',
+ window.location.href.replace(
+ /\/[^\/]*$/,
+ '/cors/result.html?%s'
+ )
+ );
+
+ if (window.location.hostname === 'blueimp.github.io') {
+ // Demo settings:
+ $('#fileupload').fileupload('option', {
+ url: '//jquery-file-upload.appspot.com/',
+ // Enable image resizing, except for Android and Opera,
+ // which actually support image resizing, but fail to
+ // send Blob objects via XHR requests:
+ disableImageResize: /Android(?!.*Chrome)|Opera/
+ .test(window.navigator.userAgent),
+ maxFileSize: 999000,
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i
+ });
+ // Upload server status check for browsers with CORS support:
+ if ($.support.cors) {
+ $.ajax({
+ url: '//jquery-file-upload.appspot.com/',
+ type: 'HEAD'
+ }).fail(function () {
+ $('
')
+ .text('Upload server currently unavailable - ' +
+ new Date())
+ .appendTo('#fileupload');
+ });
+ }
+ } else {
+ // Load existing files:
+ $('#fileupload').addClass('fileupload-processing');
+ $.ajax({
+ // Uncomment the following to send cross-domain cookies:
+ //xhrFields: {withCredentials: true},
+ url: $('#fileupload').fileupload('option', 'url'),
+ dataType: 'json',
+ context: $('#fileupload')[0]
+ }).always(function () {
+ $(this).removeClass('fileupload-processing');
+ }).done(function (result) {
+ $(this).fileupload('option', 'done')
+ .call(this, $.Event('done'), {result: result});
+ });
+ }
+
+});
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/vendor/jquery.ui.widget.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/vendor/jquery.ui.widget.js
new file mode 100644
index 000000000..e08df3fd0
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/js/vendor/jquery.ui.widget.js
@@ -0,0 +1,572 @@
+/*! jQuery UI - v1.11.4+CommonJS - 2015-08-28
+* http://jqueryui.com
+* Includes: widget.js
+* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
+
+(function( factory ) {
+ if ( typeof define === "function" && define.amd ) {
+
+ // AMD. Register as an anonymous module.
+ define([ "jquery" ], factory );
+
+ } else if ( typeof exports === "object" ) {
+
+ // Node/CommonJS
+ factory( require( "jquery" ) );
+
+ } else {
+
+ // Browser globals
+ factory( jQuery );
+ }
+}(function( $ ) {
+/*!
+ * jQuery UI Widget 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/jQuery.widget/
+ */
+
+
+var widget_uuid = 0,
+ widget_slice = Array.prototype.slice;
+
+$.cleanData = (function( orig ) {
+ return function( elems ) {
+ var events, elem, i;
+ for ( i = 0; (elem = elems[i]) != null; i++ ) {
+ try {
+
+ // Only trigger remove when necessary to save time
+ events = $._data( elem, "events" );
+ if ( events && events.remove ) {
+ $( elem ).triggerHandler( "remove" );
+ }
+
+ // http://bugs.jquery.com/ticket/8235
+ } catch ( e ) {}
+ }
+ orig( elems );
+ };
+})( $.cleanData );
+
+$.widget = function( name, base, prototype ) {
+ var fullName, existingConstructor, constructor, basePrototype,
+ // proxiedPrototype allows the provided prototype to remain unmodified
+ // so that it can be used as a mixin for multiple widgets (#8876)
+ proxiedPrototype = {},
+ namespace = name.split( "." )[ 0 ];
+
+ name = name.split( "." )[ 1 ];
+ fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ // create selector for plugin
+ $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+ return !!$.data( elem, fullName );
+ };
+
+ $[ namespace ] = $[ namespace ] || {};
+ existingConstructor = $[ namespace ][ name ];
+ constructor = $[ namespace ][ name ] = function( options, element ) {
+ // allow instantiation without "new" keyword
+ if ( !this._createWidget ) {
+ return new constructor( options, element );
+ }
+
+ // allow instantiation without initializing for simple inheritance
+ // must use "new" keyword (the code above always passes args)
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+ };
+ // extend with the existing constructor to carry over any static properties
+ $.extend( constructor, existingConstructor, {
+ version: prototype.version,
+ // copy the object used to create the prototype in case we need to
+ // redefine the widget later
+ _proto: $.extend( {}, prototype ),
+ // track widgets that inherit from this widget in case this widget is
+ // redefined after a widget inherits from it
+ _childConstructors: []
+ });
+
+ basePrototype = new base();
+ // we need to make the options hash a property directly on the new instance
+ // otherwise we'll modify the options hash on the prototype that we're
+ // inheriting from
+ basePrototype.options = $.widget.extend( {}, basePrototype.options );
+ $.each( prototype, function( prop, value ) {
+ if ( !$.isFunction( value ) ) {
+ proxiedPrototype[ prop ] = value;
+ return;
+ }
+ proxiedPrototype[ prop ] = (function() {
+ var _super = function() {
+ return base.prototype[ prop ].apply( this, arguments );
+ },
+ _superApply = function( args ) {
+ return base.prototype[ prop ].apply( this, args );
+ };
+ return function() {
+ var __super = this._super,
+ __superApply = this._superApply,
+ returnValue;
+
+ this._super = _super;
+ this._superApply = _superApply;
+
+ returnValue = value.apply( this, arguments );
+
+ this._super = __super;
+ this._superApply = __superApply;
+
+ return returnValue;
+ };
+ })();
+ });
+ constructor.prototype = $.widget.extend( basePrototype, {
+ // TODO: remove support for widgetEventPrefix
+ // always use the name + a colon as the prefix, e.g., draggable:start
+ // don't prefix for widgets that aren't DOM-based
+ widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
+ }, proxiedPrototype, {
+ constructor: constructor,
+ namespace: namespace,
+ widgetName: name,
+ widgetFullName: fullName
+ });
+
+ // If this widget is being redefined then we need to find all widgets that
+ // are inheriting from it and redefine all of them so that they inherit from
+ // the new version of this widget. We're essentially trying to replace one
+ // level in the prototype chain.
+ if ( existingConstructor ) {
+ $.each( existingConstructor._childConstructors, function( i, child ) {
+ var childPrototype = child.prototype;
+
+ // redefine the child widget using the same prototype that was
+ // originally used, but inherit from the new version of the base
+ $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+ });
+ // remove the list of existing child constructors from the old constructor
+ // so the old child constructors can be garbage collected
+ delete existingConstructor._childConstructors;
+ } else {
+ base._childConstructors.push( constructor );
+ }
+
+ $.widget.bridge( name, constructor );
+
+ return constructor;
+};
+
+$.widget.extend = function( target ) {
+ var input = widget_slice.call( arguments, 1 ),
+ inputIndex = 0,
+ inputLength = input.length,
+ key,
+ value;
+ for ( ; inputIndex < inputLength; inputIndex++ ) {
+ for ( key in input[ inputIndex ] ) {
+ value = input[ inputIndex ][ key ];
+ if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+ // Clone objects
+ if ( $.isPlainObject( value ) ) {
+ target[ key ] = $.isPlainObject( target[ key ] ) ?
+ $.widget.extend( {}, target[ key ], value ) :
+ // Don't extend strings, arrays, etc. with objects
+ $.widget.extend( {}, value );
+ // Copy everything else by reference
+ } else {
+ target[ key ] = value;
+ }
+ }
+ }
+ }
+ return target;
+};
+
+$.widget.bridge = function( name, object ) {
+ var fullName = object.prototype.widgetFullName || name;
+ $.fn[ name ] = function( options ) {
+ var isMethodCall = typeof options === "string",
+ args = widget_slice.call( arguments, 1 ),
+ returnValue = this;
+
+ if ( isMethodCall ) {
+ this.each(function() {
+ var methodValue,
+ instance = $.data( this, fullName );
+ if ( options === "instance" ) {
+ returnValue = instance;
+ return false;
+ }
+ if ( !instance ) {
+ return $.error( "cannot call methods on " + name + " prior to initialization; " +
+ "attempted to call method '" + options + "'" );
+ }
+ if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+ return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+ }
+ methodValue = instance[ options ].apply( instance, args );
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue && methodValue.jquery ?
+ returnValue.pushStack( methodValue.get() ) :
+ methodValue;
+ return false;
+ }
+ });
+ } else {
+
+ // Allow multiple hashes to be passed on init
+ if ( args.length ) {
+ options = $.widget.extend.apply( null, [ options ].concat(args) );
+ }
+
+ this.each(function() {
+ var instance = $.data( this, fullName );
+ if ( instance ) {
+ instance.option( options || {} );
+ if ( instance._init ) {
+ instance._init();
+ }
+ } else {
+ $.data( this, fullName, new object( options, this ) );
+ }
+ });
+ }
+
+ return returnValue;
+ };
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ defaultElement: "",
+ options: {
+ disabled: false,
+
+ // callbacks
+ create: null
+ },
+ _createWidget: function( options, element ) {
+ element = $( element || this.defaultElement || this )[ 0 ];
+ this.element = $( element );
+ this.uuid = widget_uuid++;
+ this.eventNamespace = "." + this.widgetName + this.uuid;
+
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+
+ if ( element !== this ) {
+ $.data( element, this.widgetFullName, this );
+ this._on( true, this.element, {
+ remove: function( event ) {
+ if ( event.target === element ) {
+ this.destroy();
+ }
+ }
+ });
+ this.document = $( element.style ?
+ // element within the document
+ element.ownerDocument :
+ // element is window or document
+ element.document || element );
+ this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+ }
+
+ this.options = $.widget.extend( {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ this._create();
+ this._trigger( "create", null, this._getCreateEventData() );
+ this._init();
+ },
+ _getCreateOptions: $.noop,
+ _getCreateEventData: $.noop,
+ _create: $.noop,
+ _init: $.noop,
+
+ destroy: function() {
+ this._destroy();
+ // we can probably remove the unbind calls in 2.0
+ // all event bindings should go through this._on()
+ this.element
+ .unbind( this.eventNamespace )
+ .removeData( this.widgetFullName )
+ // support: jquery <1.6.3
+ // http://bugs.jquery.com/ticket/9413
+ .removeData( $.camelCase( this.widgetFullName ) );
+ this.widget()
+ .unbind( this.eventNamespace )
+ .removeAttr( "aria-disabled" )
+ .removeClass(
+ this.widgetFullName + "-disabled " +
+ "ui-state-disabled" );
+
+ // clean up events and states
+ this.bindings.unbind( this.eventNamespace );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ },
+ _destroy: $.noop,
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key,
+ parts,
+ curOption,
+ i;
+
+ if ( arguments.length === 0 ) {
+ // don't return a reference to the internal hash
+ return $.widget.extend( {}, this.options );
+ }
+
+ if ( typeof key === "string" ) {
+ // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+ options = {};
+ parts = key.split( "." );
+ key = parts.shift();
+ if ( parts.length ) {
+ curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+ for ( i = 0; i < parts.length - 1; i++ ) {
+ curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+ curOption = curOption[ parts[ i ] ];
+ }
+ key = parts.pop();
+ if ( arguments.length === 1 ) {
+ return curOption[ key ] === undefined ? null : curOption[ key ];
+ }
+ curOption[ key ] = value;
+ } else {
+ if ( arguments.length === 1 ) {
+ return this.options[ key ] === undefined ? null : this.options[ key ];
+ }
+ options[ key ] = value;
+ }
+ }
+
+ this._setOptions( options );
+
+ return this;
+ },
+ _setOptions: function( options ) {
+ var key;
+
+ for ( key in options ) {
+ this._setOption( key, options[ key ] );
+ }
+
+ return this;
+ },
+ _setOption: function( key, value ) {
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this.widget()
+ .toggleClass( this.widgetFullName + "-disabled", !!value );
+
+ // If the widget is becoming disabled, then nothing is interactive
+ if ( value ) {
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ }
+ }
+
+ return this;
+ },
+
+ enable: function() {
+ return this._setOptions({ disabled: false });
+ },
+ disable: function() {
+ return this._setOptions({ disabled: true });
+ },
+
+ _on: function( suppressDisabledCheck, element, handlers ) {
+ var delegateElement,
+ instance = this;
+
+ // no suppressDisabledCheck flag, shuffle arguments
+ if ( typeof suppressDisabledCheck !== "boolean" ) {
+ handlers = element;
+ element = suppressDisabledCheck;
+ suppressDisabledCheck = false;
+ }
+
+ // no element argument, shuffle and use this.element
+ if ( !handlers ) {
+ handlers = element;
+ element = this.element;
+ delegateElement = this.widget();
+ } else {
+ element = delegateElement = $( element );
+ this.bindings = this.bindings.add( element );
+ }
+
+ $.each( handlers, function( event, handler ) {
+ function handlerProxy() {
+ // allow widgets to customize the disabled handling
+ // - disabled as an array instead of boolean
+ // - disabled class as method for disabling individual parts
+ if ( !suppressDisabledCheck &&
+ ( instance.options.disabled === true ||
+ $( this ).hasClass( "ui-state-disabled" ) ) ) {
+ return;
+ }
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+
+ // copy the guid so direct unbinding works
+ if ( typeof handler !== "string" ) {
+ handlerProxy.guid = handler.guid =
+ handler.guid || handlerProxy.guid || $.guid++;
+ }
+
+ var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
+ eventName = match[1] + instance.eventNamespace,
+ selector = match[2];
+ if ( selector ) {
+ delegateElement.delegate( selector, eventName, handlerProxy );
+ } else {
+ element.bind( eventName, handlerProxy );
+ }
+ });
+ },
+
+ _off: function( element, eventName ) {
+ eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +
+ this.eventNamespace;
+ element.unbind( eventName ).undelegate( eventName );
+
+ // Clear the stack to avoid memory leaks (#10056)
+ this.bindings = $( this.bindings.not( element ).get() );
+ this.focusable = $( this.focusable.not( element ).get() );
+ this.hoverable = $( this.hoverable.not( element ).get() );
+ },
+
+ _delay: function( handler, delay ) {
+ function handlerProxy() {
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+ var instance = this;
+ return setTimeout( handlerProxy, delay || 0 );
+ },
+
+ _hoverable: function( element ) {
+ this.hoverable = this.hoverable.add( element );
+ this._on( element, {
+ mouseenter: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-hover" );
+ },
+ mouseleave: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-hover" );
+ }
+ });
+ },
+
+ _focusable: function( element ) {
+ this.focusable = this.focusable.add( element );
+ this._on( element, {
+ focusin: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-focus" );
+ },
+ focusout: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-focus" );
+ }
+ });
+ },
+
+ _trigger: function( type, event, data ) {
+ var prop, orig,
+ callback = this.options[ type ];
+
+ data = data || {};
+ event = $.Event( event );
+ event.type = ( type === this.widgetEventPrefix ?
+ type :
+ this.widgetEventPrefix + type ).toLowerCase();
+ // the original event may come from any element
+ // so we need to reset the target on the new event
+ event.target = this.element[ 0 ];
+
+ // copy original event properties over to the new event
+ orig = event.originalEvent;
+ if ( orig ) {
+ for ( prop in orig ) {
+ if ( !( prop in event ) ) {
+ event[ prop ] = orig[ prop ];
+ }
+ }
+ }
+
+ this.element.trigger( event, data );
+ return !( $.isFunction( callback ) &&
+ callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+ event.isDefaultPrevented() );
+ }
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+ $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+ if ( typeof options === "string" ) {
+ options = { effect: options };
+ }
+ var hasOptions,
+ effectName = !options ?
+ method :
+ options === true || typeof options === "number" ?
+ defaultEffect :
+ options.effect || defaultEffect;
+ options = options || {};
+ if ( typeof options === "number" ) {
+ options = { duration: options };
+ }
+ hasOptions = !$.isEmptyObject( options );
+ options.complete = callback;
+ if ( options.delay ) {
+ element.delay( options.delay );
+ }
+ if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
+ element[ method ]( options );
+ } else if ( effectName !== method && element[ effectName ] ) {
+ element[ effectName ]( options.duration, options.easing, callback );
+ } else {
+ element.queue(function( next ) {
+ $( this )[ method ]();
+ if ( callback ) {
+ callback.call( element[ 0 ] );
+ }
+ next();
+ });
+ }
+ };
+});
+
+var widget = $.widget;
+
+
+
+}));
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/package.json b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/package.json
new file mode 100644
index 000000000..6a217a1eb
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "blueimp-file-upload",
+ "version": "9.19.2",
+ "title": "jQuery File Upload",
+ "description": "File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.",
+ "keywords": [
+ "jquery",
+ "file",
+ "upload",
+ "widget",
+ "multiple",
+ "selection",
+ "drag",
+ "drop",
+ "progress",
+ "preview",
+ "cross-domain",
+ "cross-site",
+ "chunk",
+ "resume",
+ "gae",
+ "go",
+ "python",
+ "php",
+ "bootstrap"
+ ],
+ "homepage": "https://github.com/blueimp/jQuery-File-Upload",
+ "author": {
+ "name": "Sebastian Tschan",
+ "url": "https://blueimp.net"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/blueimp/jQuery-File-Upload.git"
+ },
+ "license": "MIT",
+ "optionalDependencies": {
+ "blueimp-canvas-to-blob": "3.5.0",
+ "blueimp-load-image": "2.12.2",
+ "blueimp-tmpl": "3.6.0"
+ },
+ "devDependencies": {
+ "bower-json": "0.8.1",
+ "jshint": "2.9.3"
+ },
+ "scripts": {
+ "bower-version-update": "./bower-version-update.js",
+ "lint": "jshint *.js js/*.js js/cors/*.js",
+ "test": "npm run lint",
+ "preversion": "npm test",
+ "version": "npm run bower-version-update && git add bower.json",
+ "postversion": "git push --tags origin master && npm publish"
+ },
+ "main": "js/jquery.fileupload.js"
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/app.yaml b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/app.yaml
new file mode 100644
index 000000000..2d09daa56
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/app.yaml
@@ -0,0 +1,12 @@
+application: jquery-file-upload
+version: 2
+runtime: go
+api_version: go1
+
+handlers:
+- url: /(favicon\.ico|robots\.txt)
+ static_files: static/\1
+ upload: static/(.*)
+ expiration: '1d'
+- url: /.*
+ script: _go_app
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/app/main.go b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/app/main.go
new file mode 100644
index 000000000..a92d128c0
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/app/main.go
@@ -0,0 +1,361 @@
+/*
+ * jQuery File Upload Plugin GAE Go Example
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+package app
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "github.com/disintegration/gift"
+ "golang.org/x/net/context"
+ "google.golang.org/appengine"
+ "google.golang.org/appengine/memcache"
+ "hash/crc32"
+ "image"
+ "image/gif"
+ "image/jpeg"
+ "image/png"
+ "io"
+ "log"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "path/filepath"
+ "regexp"
+ "strings"
+)
+
+const (
+ WEBSITE = "https://blueimp.github.io/jQuery-File-Upload/"
+ MIN_FILE_SIZE = 1 // bytes
+ // Max file size is memcache limit (1MB) minus key size minus overhead:
+ MAX_FILE_SIZE = 999000 // bytes
+ IMAGE_TYPES = "image/(gif|p?jpeg|(x-)?png)"
+ ACCEPT_FILE_TYPES = IMAGE_TYPES
+ THUMB_MAX_WIDTH = 80
+ THUMB_MAX_HEIGHT = 80
+ EXPIRATION_TIME = 300 // seconds
+ // If empty, only allow redirects to the referer protocol+host.
+ // Set to a regexp string for custom pattern matching:
+ REDIRECT_ALLOW_TARGET = ""
+)
+
+var (
+ imageTypes = regexp.MustCompile(IMAGE_TYPES)
+ acceptFileTypes = regexp.MustCompile(ACCEPT_FILE_TYPES)
+ thumbSuffix = "." + fmt.Sprint(THUMB_MAX_WIDTH) + "x" +
+ fmt.Sprint(THUMB_MAX_HEIGHT)
+)
+
+func escape(s string) string {
+ return strings.Replace(url.QueryEscape(s), "+", "%20", -1)
+}
+
+func extractKey(r *http.Request) string {
+ // Use RequestURI instead of r.URL.Path, as we need the encoded form:
+ path := strings.Split(r.RequestURI, "?")[0]
+ // Also adjust double encoded slashes:
+ return strings.Replace(path[1:], "%252F", "%2F", -1)
+}
+
+func check(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
+
+type FileInfo struct {
+ Key string `json:"-"`
+ ThumbnailKey string `json:"-"`
+ Url string `json:"url,omitempty"`
+ ThumbnailUrl string `json:"thumbnailUrl,omitempty"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Size int64 `json:"size"`
+ Error string `json:"error,omitempty"`
+ DeleteUrl string `json:"deleteUrl,omitempty"`
+ DeleteType string `json:"deleteType,omitempty"`
+}
+
+func (fi *FileInfo) ValidateType() (valid bool) {
+ if acceptFileTypes.MatchString(fi.Type) {
+ return true
+ }
+ fi.Error = "Filetype not allowed"
+ return false
+}
+
+func (fi *FileInfo) ValidateSize() (valid bool) {
+ if fi.Size < MIN_FILE_SIZE {
+ fi.Error = "File is too small"
+ } else if fi.Size > MAX_FILE_SIZE {
+ fi.Error = "File is too big"
+ } else {
+ return true
+ }
+ return false
+}
+
+func (fi *FileInfo) CreateUrls(r *http.Request, c context.Context) {
+ u := &url.URL{
+ Scheme: r.URL.Scheme,
+ Host: appengine.DefaultVersionHostname(c),
+ Path: "/",
+ }
+ uString := u.String()
+ fi.Url = uString + fi.Key
+ fi.DeleteUrl = fi.Url
+ fi.DeleteType = "DELETE"
+ if fi.ThumbnailKey != "" {
+ fi.ThumbnailUrl = uString + fi.ThumbnailKey
+ }
+}
+
+func (fi *FileInfo) SetKey(checksum uint32) {
+ fi.Key = escape(string(fi.Type)) + "/" +
+ escape(fmt.Sprint(checksum)) + "/" +
+ escape(string(fi.Name))
+}
+
+func (fi *FileInfo) createThumb(buffer *bytes.Buffer, c context.Context) {
+ if imageTypes.MatchString(fi.Type) {
+ src, _, err := image.Decode(bytes.NewReader(buffer.Bytes()))
+ check(err)
+ filter := gift.New(gift.ResizeToFit(
+ THUMB_MAX_WIDTH,
+ THUMB_MAX_HEIGHT,
+ gift.LanczosResampling,
+ ))
+ dst := image.NewNRGBA(filter.Bounds(src.Bounds()))
+ filter.Draw(dst, src)
+ buffer.Reset()
+ bWriter := bufio.NewWriter(buffer)
+ switch fi.Type {
+ case "image/jpeg", "image/pjpeg":
+ err = jpeg.Encode(bWriter, dst, nil)
+ case "image/gif":
+ err = gif.Encode(bWriter, dst, nil)
+ default:
+ err = png.Encode(bWriter, dst)
+ }
+ check(err)
+ bWriter.Flush()
+ thumbnailKey := fi.Key + thumbSuffix + filepath.Ext(fi.Name)
+ item := &memcache.Item{
+ Key: thumbnailKey,
+ Value: buffer.Bytes(),
+ }
+ err = memcache.Set(c, item)
+ check(err)
+ fi.ThumbnailKey = thumbnailKey
+ }
+}
+
+func handleUpload(r *http.Request, p *multipart.Part) (fi *FileInfo) {
+ fi = &FileInfo{
+ Name: p.FileName(),
+ Type: p.Header.Get("Content-Type"),
+ }
+ if !fi.ValidateType() {
+ return
+ }
+ defer func() {
+ if rec := recover(); rec != nil {
+ log.Println(rec)
+ fi.Error = rec.(error).Error()
+ }
+ }()
+ var buffer bytes.Buffer
+ hash := crc32.NewIEEE()
+ mw := io.MultiWriter(&buffer, hash)
+ lr := &io.LimitedReader{R: p, N: MAX_FILE_SIZE + 1}
+ _, err := io.Copy(mw, lr)
+ check(err)
+ fi.Size = MAX_FILE_SIZE + 1 - lr.N
+ if !fi.ValidateSize() {
+ return
+ }
+ fi.SetKey(hash.Sum32())
+ item := &memcache.Item{
+ Key: fi.Key,
+ Value: buffer.Bytes(),
+ }
+ context := appengine.NewContext(r)
+ err = memcache.Set(context, item)
+ check(err)
+ fi.createThumb(&buffer, context)
+ fi.CreateUrls(r, context)
+ return
+}
+
+func getFormValue(p *multipart.Part) string {
+ var b bytes.Buffer
+ io.CopyN(&b, p, int64(1<<20)) // Copy max: 1 MiB
+ return b.String()
+}
+
+func handleUploads(r *http.Request) (fileInfos []*FileInfo) {
+ fileInfos = make([]*FileInfo, 0)
+ mr, err := r.MultipartReader()
+ check(err)
+ r.Form, err = url.ParseQuery(r.URL.RawQuery)
+ check(err)
+ part, err := mr.NextPart()
+ for err == nil {
+ if name := part.FormName(); name != "" {
+ if part.FileName() != "" {
+ fileInfos = append(fileInfos, handleUpload(r, part))
+ } else {
+ r.Form[name] = append(r.Form[name], getFormValue(part))
+ }
+ }
+ part, err = mr.NextPart()
+ }
+ return
+}
+
+func validateRedirect(r *http.Request, redirect string) bool {
+ if redirect != "" {
+ var redirectAllowTarget *regexp.Regexp
+ if REDIRECT_ALLOW_TARGET != "" {
+ redirectAllowTarget = regexp.MustCompile(REDIRECT_ALLOW_TARGET)
+ } else {
+ referer := r.Referer()
+ if referer == "" {
+ return false
+ }
+ refererUrl, err := url.Parse(referer)
+ if err != nil {
+ return false
+ }
+ redirectAllowTarget = regexp.MustCompile("^" + regexp.QuoteMeta(
+ refererUrl.Scheme+"://"+refererUrl.Host+"/",
+ ))
+ }
+ return redirectAllowTarget.MatchString(redirect)
+ }
+ return false
+}
+
+func get(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/" {
+ http.Redirect(w, r, WEBSITE, http.StatusFound)
+ return
+ }
+ // Use RequestURI instead of r.URL.Path, as we need the encoded form:
+ key := extractKey(r)
+ parts := strings.Split(key, "/")
+ if len(parts) == 3 {
+ context := appengine.NewContext(r)
+ item, err := memcache.Get(context, key)
+ if err == nil {
+ w.Header().Add("X-Content-Type-Options", "nosniff")
+ contentType, _ := url.QueryUnescape(parts[0])
+ if !imageTypes.MatchString(contentType) {
+ contentType = "application/octet-stream"
+ }
+ w.Header().Add("Content-Type", contentType)
+ w.Header().Add(
+ "Cache-Control",
+ fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME),
+ )
+ w.Write(item.Value)
+ return
+ }
+ }
+ http.Error(w, "404 Not Found", http.StatusNotFound)
+}
+
+func post(w http.ResponseWriter, r *http.Request) {
+ result := make(map[string][]*FileInfo, 1)
+ result["files"] = handleUploads(r)
+ b, err := json.Marshal(result)
+ check(err)
+ if redirect := r.FormValue("redirect"); validateRedirect(r, redirect) {
+ if strings.Contains(redirect, "%s") {
+ redirect = fmt.Sprintf(
+ redirect,
+ escape(string(b)),
+ )
+ }
+ http.Redirect(w, r, redirect, http.StatusFound)
+ return
+ }
+ w.Header().Set("Cache-Control", "no-cache")
+ jsonType := "application/json"
+ if strings.Index(r.Header.Get("Accept"), jsonType) != -1 {
+ w.Header().Set("Content-Type", jsonType)
+ }
+ fmt.Fprintln(w, string(b))
+}
+
+func delete(w http.ResponseWriter, r *http.Request) {
+ key := extractKey(r)
+ parts := strings.Split(key, "/")
+ if len(parts) == 3 {
+ result := make(map[string]bool, 1)
+ context := appengine.NewContext(r)
+ err := memcache.Delete(context, key)
+ if err == nil {
+ result[key] = true
+ contentType, _ := url.QueryUnescape(parts[0])
+ if imageTypes.MatchString(contentType) {
+ thumbnailKey := key + thumbSuffix + filepath.Ext(parts[2])
+ err := memcache.Delete(context, thumbnailKey)
+ if err == nil {
+ result[thumbnailKey] = true
+ }
+ }
+ }
+ w.Header().Set("Content-Type", "application/json")
+ b, err := json.Marshal(result)
+ check(err)
+ fmt.Fprintln(w, string(b))
+ } else {
+ http.Error(w, "405 Method not allowed", http.StatusMethodNotAllowed)
+ }
+}
+
+func handle(w http.ResponseWriter, r *http.Request) {
+ params, err := url.ParseQuery(r.URL.RawQuery)
+ check(err)
+ w.Header().Add("Access-Control-Allow-Origin", "*")
+ w.Header().Add(
+ "Access-Control-Allow-Methods",
+ "OPTIONS, HEAD, GET, POST, DELETE",
+ )
+ w.Header().Add(
+ "Access-Control-Allow-Headers",
+ "Content-Type, Content-Range, Content-Disposition",
+ )
+ switch r.Method {
+ case "OPTIONS", "HEAD":
+ return
+ case "GET":
+ get(w, r)
+ case "POST":
+ if len(params["_method"]) > 0 && params["_method"][0] == "DELETE" {
+ delete(w, r)
+ } else {
+ post(w, r)
+ }
+ case "DELETE":
+ delete(w, r)
+ default:
+ http.Error(w, "501 Not Implemented", http.StatusNotImplemented)
+ }
+}
+
+func init() {
+ http.HandleFunc("/", handle)
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/static/favicon.ico b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/static/favicon.ico
new file mode 100644
index 000000000..1a71ea772
Binary files /dev/null and b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/static/favicon.ico differ
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/static/robots.txt b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/static/robots.txt
new file mode 100644
index 000000000..eb0536286
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-go/static/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/app.yaml b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/app.yaml
new file mode 100644
index 000000000..764449b74
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/app.yaml
@@ -0,0 +1,17 @@
+application: jquery-file-upload
+version: 1
+runtime: python27
+api_version: 1
+threadsafe: true
+
+libraries:
+- name: PIL
+ version: latest
+
+handlers:
+- url: /(favicon\.ico|robots\.txt)
+ static_files: static/\1
+ upload: static/(.*)
+ expiration: '1d'
+- url: /.*
+ script: main.app
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/main.py b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/main.py
new file mode 100644
index 000000000..1955ac00a
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/main.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+#
+# jQuery File Upload Plugin GAE Python Example
+# https://github.com/blueimp/jQuery-File-Upload
+#
+# Copyright 2011, Sebastian Tschan
+# https://blueimp.net
+#
+# Licensed under the MIT license:
+# https://opensource.org/licenses/MIT
+#
+
+from google.appengine.api import memcache, images
+import json
+import os
+import re
+import urllib
+import webapp2
+
+DEBUG=os.environ.get('SERVER_SOFTWARE', '').startswith('Dev')
+WEBSITE = 'https://blueimp.github.io/jQuery-File-Upload/'
+MIN_FILE_SIZE = 1 # bytes
+# Max file size is memcache limit (1MB) minus key size minus overhead:
+MAX_FILE_SIZE = 999000 # bytes
+IMAGE_TYPES = re.compile('image/(gif|p?jpeg|(x-)?png)')
+ACCEPT_FILE_TYPES = IMAGE_TYPES
+THUMB_MAX_WIDTH = 80
+THUMB_MAX_HEIGHT = 80
+THUMB_SUFFIX = '.'+str(THUMB_MAX_WIDTH)+'x'+str(THUMB_MAX_HEIGHT)+'.png'
+EXPIRATION_TIME = 300 # seconds
+# If set to None, only allow redirects to the referer protocol+host.
+# Set to a regexp for custom pattern matching against the redirect value:
+REDIRECT_ALLOW_TARGET = None
+
+class CORSHandler(webapp2.RequestHandler):
+ def cors(self):
+ headers = self.response.headers
+ headers['Access-Control-Allow-Origin'] = '*'
+ headers['Access-Control-Allow-Methods'] =\
+ 'OPTIONS, HEAD, GET, POST, DELETE'
+ headers['Access-Control-Allow-Headers'] =\
+ 'Content-Type, Content-Range, Content-Disposition'
+
+ def initialize(self, request, response):
+ super(CORSHandler, self).initialize(request, response)
+ self.cors()
+
+ def json_stringify(self, obj):
+ return json.dumps(obj, separators=(',', ':'))
+
+ def options(self, *args, **kwargs):
+ pass
+
+class UploadHandler(CORSHandler):
+ def validate(self, file):
+ if file['size'] < MIN_FILE_SIZE:
+ file['error'] = 'File is too small'
+ elif file['size'] > MAX_FILE_SIZE:
+ file['error'] = 'File is too big'
+ elif not ACCEPT_FILE_TYPES.match(file['type']):
+ file['error'] = 'Filetype not allowed'
+ else:
+ return True
+ return False
+
+ def validate_redirect(self, redirect):
+ if redirect:
+ if REDIRECT_ALLOW_TARGET:
+ return REDIRECT_ALLOW_TARGET.match(redirect)
+ referer = self.request.headers['referer']
+ if referer:
+ from urlparse import urlparse
+ parts = urlparse(referer)
+ redirect_allow_target = '^' + re.escape(
+ parts.scheme + '://' + parts.netloc + '/'
+ )
+ return re.match(redirect_allow_target, redirect)
+ return False
+
+ def get_file_size(self, file):
+ file.seek(0, 2) # Seek to the end of the file
+ size = file.tell() # Get the position of EOF
+ file.seek(0) # Reset the file position to the beginning
+ return size
+
+ def write_blob(self, data, info):
+ key = urllib.quote(info['type'].encode('utf-8'), '') +\
+ '/' + str(hash(data)) +\
+ '/' + urllib.quote(info['name'].encode('utf-8'), '')
+ try:
+ memcache.set(key, data, time=EXPIRATION_TIME)
+ except: #Failed to add to memcache
+ return (None, None)
+ thumbnail_key = None
+ if IMAGE_TYPES.match(info['type']):
+ try:
+ img = images.Image(image_data=data)
+ img.resize(
+ width=THUMB_MAX_WIDTH,
+ height=THUMB_MAX_HEIGHT
+ )
+ thumbnail_data = img.execute_transforms()
+ thumbnail_key = key + THUMB_SUFFIX
+ memcache.set(
+ thumbnail_key,
+ thumbnail_data,
+ time=EXPIRATION_TIME
+ )
+ except: #Failed to resize Image or add to memcache
+ thumbnail_key = None
+ return (key, thumbnail_key)
+
+ def handle_upload(self):
+ results = []
+ for name, fieldStorage in self.request.POST.items():
+ if type(fieldStorage) is unicode:
+ continue
+ result = {}
+ result['name'] = urllib.unquote(fieldStorage.filename)
+ result['type'] = fieldStorage.type
+ result['size'] = self.get_file_size(fieldStorage.file)
+ if self.validate(result):
+ key, thumbnail_key = self.write_blob(
+ fieldStorage.value,
+ result
+ )
+ if key is not None:
+ result['url'] = self.request.host_url + '/' + key
+ result['deleteUrl'] = result['url']
+ result['deleteType'] = 'DELETE'
+ if thumbnail_key is not None:
+ result['thumbnailUrl'] = self.request.host_url +\
+ '/' + thumbnail_key
+ else:
+ result['error'] = 'Failed to store uploaded file.'
+ results.append(result)
+ return results
+
+ def head(self):
+ pass
+
+ def get(self):
+ self.redirect(WEBSITE)
+
+ def post(self):
+ if (self.request.get('_method') == 'DELETE'):
+ return self.delete()
+ result = {'files': self.handle_upload()}
+ s = self.json_stringify(result)
+ redirect = self.request.get('redirect')
+ if self.validate_redirect(redirect):
+ return self.redirect(str(
+ redirect.replace('%s', urllib.quote(s, ''), 1)
+ ))
+ if 'application/json' in self.request.headers.get('Accept'):
+ self.response.headers['Content-Type'] = 'application/json'
+ self.response.write(s)
+
+class FileHandler(CORSHandler):
+ def normalize(self, str):
+ return urllib.quote(urllib.unquote(str), '')
+
+ def get(self, content_type, data_hash, file_name):
+ content_type = self.normalize(content_type)
+ file_name = self.normalize(file_name)
+ key = content_type + '/' + data_hash + '/' + file_name
+ data = memcache.get(key)
+ if data is None:
+ return self.error(404)
+ # Prevent browsers from MIME-sniffing the content-type:
+ self.response.headers['X-Content-Type-Options'] = 'nosniff'
+ content_type = urllib.unquote(content_type)
+ if not IMAGE_TYPES.match(content_type):
+ # Force a download dialog for non-image types:
+ content_type = 'application/octet-stream'
+ elif file_name.endswith(THUMB_SUFFIX):
+ content_type = 'image/png'
+ self.response.headers['Content-Type'] = content_type
+ # Cache for the expiration time:
+ self.response.headers['Cache-Control'] = 'public,max-age=%d' \
+ % EXPIRATION_TIME
+ self.response.write(data)
+
+ def delete(self, content_type, data_hash, file_name):
+ content_type = self.normalize(content_type)
+ file_name = self.normalize(file_name)
+ key = content_type + '/' + data_hash + '/' + file_name
+ result = {key: memcache.delete(key)}
+ content_type = urllib.unquote(content_type)
+ if IMAGE_TYPES.match(content_type):
+ thumbnail_key = key + THUMB_SUFFIX
+ result[thumbnail_key] = memcache.delete(thumbnail_key)
+ if 'application/json' in self.request.headers.get('Accept'):
+ self.response.headers['Content-Type'] = 'application/json'
+ s = self.json_stringify(result)
+ self.response.write(s)
+
+app = webapp2.WSGIApplication(
+ [
+ ('/', UploadHandler),
+ ('/(.+)/([^/]+)/([^/]+)', FileHandler)
+ ],
+ debug=DEBUG
+)
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/static/favicon.ico b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/static/favicon.ico
new file mode 100644
index 000000000..1a71ea772
Binary files /dev/null and b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/static/favicon.ico differ
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/static/robots.txt b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/static/robots.txt
new file mode 100644
index 000000000..eb0536286
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/gae-python/static/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/Dockerfile b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/Dockerfile
new file mode 100644
index 000000000..ca88d3d0d
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/Dockerfile
@@ -0,0 +1,38 @@
+FROM php:7.0-apache
+
+# Enable the Apache Headers module:
+RUN ln -s /etc/apache2/mods-available/headers.load \
+ /etc/apache2/mods-enabled/headers.load
+
+# Enable the Apache Rewrite module:
+RUN ln -s /etc/apache2/mods-available/rewrite.load \
+ /etc/apache2/mods-enabled/rewrite.load
+
+# Install GD, Imagick and ImageMagick as image conversion options:
+RUN DEBIAN_FRONTEND=noninteractive \
+ apt-get update && apt-get install -y --no-install-recommends \
+ libpng-dev \
+ libjpeg-dev \
+ libmagickwand-dev \
+ imagemagick \
+ && pecl install \
+ imagick \
+ && docker-php-ext-enable \
+ imagick \
+ && docker-php-ext-configure \
+ gd --with-jpeg-dir=/usr/include/ \
+ && docker-php-ext-install \
+ gd \
+ # Uninstall obsolete packages:
+ && apt-get autoremove -y \
+ libpng-dev \
+ libjpeg-dev \
+ libmagickwand-dev \
+ # Remove obsolete files:
+ && apt-get clean \
+ && rm -rf \
+ /tmp/* \
+ /usr/share/doc/* \
+ /var/cache/* \
+ /var/lib/apt/lists/* \
+ /var/tmp/*
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/UploadHandler.php b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/UploadHandler.php
new file mode 100755
index 000000000..285d046aa
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/UploadHandler.php
@@ -0,0 +1,1411 @@
+ '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')).'/files/',
+ 'upload_url' => $this->get_full_url().'/files/',
+ '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.
+ // Keep in mind: these image manipulations are inherited by all other image versions from this point onwards.
+ // Also note that the property 'no_cache' is not inherited, since it's not a manipulation.
+ '' => array(
+ // Automatically rotate images based on EXIF meta data:
+ 'auto_orient' => true
+ ),
+ // You can add arrays to generate different versions.
+ // The name of the key is the name of the version (example: 'medium').
+ // the array contains the options to apply.
+ /*
+ '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:
+ // 'auto_orient' => true,
+ // 'crop' => true,
+ // 'jpeg_quality' => 70,
+ // 'no_cache' => true, (there's a caching option, but this remembers thumbnail sizes from a previous action!)
+ // 'strip' => true, (this strips EXIF tags, such as geolocation)
+ 'max_width' => 80, // either specify width, or set to 0. Then width is automatically adjusted - keeping aspect ratio to a specified max_height.
+ 'max_height' => 80 // either specify height, or set to 0. Then height is automatically adjusted - keeping aspect ratio to a specified max_width.
+ )
+ ),
+ '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);
+ }
+
+ $image_resize = false;
+ $new_width = $max_width = $img_width = $image->getImageWidth();
+ $new_height = $max_height = $img_height = $image->getImageHeight();
+
+ // use isset(). User might be setting max_width = 0 (auto in regular resizing). Value 0 would be considered empty when you use empty()
+ if (isset($options['max_width'])) {
+ $image_resize = true;
+ $new_width = $max_width = $options['max_width'];
+ }
+ if (isset($options['max_height'])) {
+ $image_resize = true;
+ $new_height = $max_height = $options['max_height'];
+ }
+
+ $image_strip = (isset($options['strip']) ? $options['strip'] : false);
+
+ if ( !$image_oriented && ($max_width >= $img_width) && ($max_height >= $img_height) && !$image_strip && empty($options["jpeg_quality"]) ) {
+ if ($file_path !== $new_file_path) {
+ return copy($file_path, $new_file_path);
+ }
+ return true;
+ }
+ $crop = (isset($options['crop']) ? $options['crop'] : false);
+
+ 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 ( $image_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);
+ }
+}
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/docker-compose.yml b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/docker-compose.yml
new file mode 100644
index 000000000..691ea9caa
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/docker-compose.yml
@@ -0,0 +1,6 @@
+apache:
+ build: ./
+ ports:
+ - "80:80"
+ volumes:
+ - "../../:/var/www/html"
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/files/.gitignore b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/files/.gitignore
new file mode 100644
index 000000000..e24a60fae
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/files/.gitignore
@@ -0,0 +1,3 @@
+*
+!.gitignore
+!.htaccess
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/files/.htaccess b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/files/.htaccess
new file mode 100644
index 000000000..6f454afb9
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/files/.htaccess
@@ -0,0 +1,26 @@
+# To enable the Headers module, execute the following command and reload Apache:
+# sudo a2enmod headers
+
+# The following directives prevent the execution of script files
+# in the context of the website.
+# They also force the content-type application/octet-stream and
+# force browsers to display a download dialog for non-image files.
+SetHandler default-handler
+ForceType application/octet-stream
+Header set Content-Disposition attachment
+
+# The following unsets the forced type and Content-Disposition headers
+# for known image files:
+
+ ForceType none
+ Header unset Content-Disposition
+
+
+# The following directive prevents browsers from MIME-sniffing the content-type.
+# This is an important complement to the ForceType directive above:
+Header set X-Content-Type-Options nosniff
+
+# Uncomment the following lines to prevent unauthorized download of files:
+#AuthName "Authorization required"
+#AuthType Basic
+#require valid-user
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/index.php b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/index.php
new file mode 100644
index 000000000..6caabb710
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/server/php/index.php
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
jQuery File Upload Plugin Test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/test/test.js b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/test/test.js
new file mode 100644
index 000000000..452127567
--- /dev/null
+++ b/static/NearBeach/javascript/jQuery-File-Upload-9.19.2/test/test.js
@@ -0,0 +1,1292 @@
+/*
+ * jQuery File Upload Plugin Test
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global $, QUnit, window, document, expect, module, test, asyncTest, start, ok, strictEqual, notStrictEqual */
+
+$(function () {
+ // jshint nomen:false
+ 'use strict';
+
+ QUnit.done = function () {
+ // Delete all uploaded files:
+ var url = $('#fileupload').prop('action');
+ $.getJSON(url, function (result) {
+ $.each(result.files, function (index, file) {
+ $.ajax({
+ url: url + '?file=' + encodeURIComponent(file.name),
+ type: 'DELETE'
+ });
+ });
+ });
+ };
+
+ var lifecycle = {
+ setup: function () {
+ // Set the .fileupload method to the basic widget method:
+ $.widget('blueimp.fileupload', window.testBasicWidget, {});
+ },
+ teardown: function () {
+ // Remove all remaining event listeners:
+ $(document).unbind();
+ }
+ },
+ lifecycleUI = {
+ setup: function () {
+ // Set the .fileupload method to the UI widget method:
+ $.widget('blueimp.fileupload', window.testUIWidget, {});
+ },
+ teardown: function () {
+ // Remove all remaining event listeners:
+ $(document).unbind();
+ }
+ };
+
+ module('Initialization', lifecycle);
+
+ test('Widget initialization', function () {
+ var fu = $('#fileupload').fileupload();
+ ok(fu.data('blueimp-fileupload') || fu.data('fileupload'));
+ });
+
+ test('Data attribute options', function () {
+ $('#fileupload').attr('data-url', 'http://example.org');
+ $('#fileupload').fileupload();
+ strictEqual(
+ $('#fileupload').fileupload('option', 'url'),
+ 'http://example.org'
+ );
+ });
+
+ test('File input initialization', function () {
+ var fu = $('#fileupload').fileupload();
+ ok(
+ fu.fileupload('option', 'fileInput').length,
+ 'File input field inside of the widget'
+ );
+ ok(
+ fu.fileupload('option', 'fileInput').length,
+ 'Widget element as file input field'
+ );
+ });
+
+ test('Drop zone initialization', function () {
+ ok($('#fileupload').fileupload()
+ .fileupload('option', 'dropZone').length);
+ });
+
+ test('Paste zone initialization', function () {
+ ok($('#fileupload').fileupload({pasteZone: document})
+ .fileupload('option', 'pasteZone').length);
+ });
+
+ test('Event listeners initialization', function () {
+ expect(
+ $.support.xhrFormDataFileUpload ? 4 : 1
+ );
+ var eo = {
+ originalEvent: {
+ dataTransfer: {files: [{}], types: ['Files']},
+ clipboardData: {items: [{}]}
+ }
+ },
+ fu = $('#fileupload').fileupload({
+ pasteZone: document,
+ dragover: function () {
+ ok(true, 'Triggers dragover callback');
+ return false;
+ },
+ drop: function () {
+ ok(true, 'Triggers drop callback');
+ return false;
+ },
+ paste: function () {
+ ok(true, 'Triggers paste callback');
+ return false;
+ },
+ change: function () {
+ ok(true, 'Triggers change callback');
+ return false;
+ }
+ }),
+ fileInput = fu.fileupload('option', 'fileInput'),
+ dropZone = fu.fileupload('option', 'dropZone'),
+ pasteZone = fu.fileupload('option', 'pasteZone');
+ fileInput.trigger($.Event('change', eo));
+ dropZone.trigger($.Event('dragover', eo));
+ dropZone.trigger($.Event('drop', eo));
+ pasteZone.trigger($.Event('paste', eo));
+ });
+
+ module('API', lifecycle);
+
+ test('destroy', function () {
+ expect(4);
+ var eo = {
+ originalEvent: {
+ dataTransfer: {files: [{}], types: ['Files']},
+ clipboardData: {items: [{}]}
+ }
+ },
+ options = {
+ pasteZone: document,
+ dragover: function () {
+ ok(true, 'Triggers dragover callback');
+ return false;
+ },
+ drop: function () {
+ ok(true, 'Triggers drop callback');
+ return false;
+ },
+ paste: function () {
+ ok(true, 'Triggers paste callback');
+ return false;
+ },
+ change: function () {
+ ok(true, 'Triggers change callback');
+ return false;
+ }
+ },
+ fu = $('#fileupload').fileupload(options),
+ fileInput = fu.fileupload('option', 'fileInput'),
+ dropZone = fu.fileupload('option', 'dropZone'),
+ pasteZone = fu.fileupload('option', 'pasteZone');
+ dropZone.bind('dragover', options.dragover);
+ dropZone.bind('drop', options.drop);
+ pasteZone.bind('paste', options.paste);
+ fileInput.bind('change', options.change);
+ fu.fileupload('destroy');
+ fileInput.trigger($.Event('change', eo));
+ dropZone.trigger($.Event('dragover', eo));
+ dropZone.trigger($.Event('drop', eo));
+ pasteZone.trigger($.Event('paste', eo));
+ });
+
+ test('disable/enable', function () {
+ expect(
+ $.support.xhrFormDataFileUpload ? 4 : 1
+ );
+ var eo = {
+ originalEvent: {
+ dataTransfer: {files: [{}], types: ['Files']},
+ clipboardData: {items: [{}]}
+ }
+ },
+ fu = $('#fileupload').fileupload({
+ pasteZone: document,
+ dragover: function () {
+ ok(true, 'Triggers dragover callback');
+ return false;
+ },
+ drop: function () {
+ ok(true, 'Triggers drop callback');
+ return false;
+ },
+ paste: function () {
+ ok(true, 'Triggers paste callback');
+ return false;
+ },
+ change: function () {
+ ok(true, 'Triggers change callback');
+ return false;
+ }
+ }),
+ fileInput = fu.fileupload('option', 'fileInput'),
+ dropZone = fu.fileupload('option', 'dropZone'),
+ pasteZone = fu.fileupload('option', 'pasteZone');
+ fu.fileupload('disable');
+ fileInput.trigger($.Event('change', eo));
+ dropZone.trigger($.Event('dragover', eo));
+ dropZone.trigger($.Event('drop', eo));
+ pasteZone.trigger($.Event('paste', eo));
+ fu.fileupload('enable');
+ fileInput.trigger($.Event('change', eo));
+ dropZone.trigger($.Event('dragover', eo));
+ dropZone.trigger($.Event('drop', eo));
+ pasteZone.trigger($.Event('paste', eo));
+ });
+
+ test('option', function () {
+ expect(
+ $.support.xhrFormDataFileUpload ? 10 : 7
+ );
+ var eo = {
+ originalEvent: {
+ dataTransfer: {files: [{}], types: ['Files']},
+ clipboardData: {items: [{}]}
+ }
+ },
+ fu = $('#fileupload').fileupload({
+ pasteZone: document,
+ dragover: function () {
+ ok(true, 'Triggers dragover callback');
+ return false;
+ },
+ drop: function () {
+ ok(true, 'Triggers drop callback');
+ return false;
+ },
+ paste: function () {
+ ok(true, 'Triggers paste callback');
+ return false;
+ },
+ change: function () {
+ ok(true, 'Triggers change callback');
+ return false;
+ }
+ }),
+ fileInput = fu.fileupload('option', 'fileInput'),
+ dropZone = fu.fileupload('option', 'dropZone'),
+ pasteZone = fu.fileupload('option', 'pasteZone');
+ fu.fileupload('option', 'fileInput', null);
+ fu.fileupload('option', 'dropZone', null);
+ fu.fileupload('option', 'pasteZone', null);
+ fileInput.trigger($.Event('change', eo));
+ dropZone.trigger($.Event('dragover', eo));
+ dropZone.trigger($.Event('drop', eo));
+ pasteZone.trigger($.Event('paste', eo));
+ fu.fileupload('option', 'dropZone', 'body');
+ strictEqual(
+ fu.fileupload('option', 'dropZone')[0],
+ document.body,
+ 'Allow a query string as parameter for the dropZone option'
+ );
+ fu.fileupload('option', 'dropZone', document);
+ strictEqual(
+ fu.fileupload('option', 'dropZone')[0],
+ document,
+ 'Allow a document element as parameter for the dropZone option'
+ );
+ fu.fileupload('option', 'pasteZone', 'body');
+ strictEqual(
+ fu.fileupload('option', 'pasteZone')[0],
+ document.body,
+ 'Allow a query string as parameter for the pasteZone option'
+ );
+ fu.fileupload('option', 'pasteZone', document);
+ strictEqual(
+ fu.fileupload('option', 'pasteZone')[0],
+ document,
+ 'Allow a document element as parameter for the pasteZone option'
+ );
+ fu.fileupload('option', 'fileInput', ':file');
+ strictEqual(
+ fu.fileupload('option', 'fileInput')[0],
+ $(':file')[0],
+ 'Allow a query string as parameter for the fileInput option'
+ );
+ fu.fileupload('option', 'fileInput', $(':file')[0]);
+ strictEqual(
+ fu.fileupload('option', 'fileInput')[0],
+ $(':file')[0],
+ 'Allow a document element as parameter for the fileInput option'
+ );
+ fu.fileupload('option', 'fileInput', fileInput);
+ fu.fileupload('option', 'dropZone', dropZone);
+ fu.fileupload('option', 'pasteZone', pasteZone);
+ fileInput.trigger($.Event('change', eo));
+ dropZone.trigger($.Event('dragover', eo));
+ dropZone.trigger($.Event('drop', eo));
+ pasteZone.trigger($.Event('paste', eo));
+ });
+
+ asyncTest('add', function () {
+ expect(2);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ add: function (e, data) {
+ strictEqual(
+ data.files[0].name,
+ param.files[0].name,
+ 'Triggers add callback'
+ );
+ }
+ }).fileupload('add', param).fileupload(
+ 'option',
+ 'add',
+ function (e, data) {
+ data.submit().complete(function () {
+ ok(true, 'data.submit() Returns a jqXHR object');
+ start();
+ });
+ }
+ ).fileupload('add', param);
+ });
+
+ asyncTest('send', function () {
+ expect(3);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ send: function (e, data) {
+ strictEqual(
+ data.files[0].name,
+ 'test',
+ 'Triggers send callback'
+ );
+ }
+ }).fileupload('send', param).fail(function () {
+ ok(true, 'Allows to abort the request');
+ }).complete(function () {
+ ok(true, 'Returns a jqXHR object');
+ start();
+ }).abort();
+ });
+
+ module('Callbacks', lifecycle);
+
+ asyncTest('add', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ add: function () {
+ ok(true, 'Triggers add callback');
+ start();
+ }
+ }).fileupload('add', param);
+ });
+
+ asyncTest('submit', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ submit: function () {
+ ok(true, 'Triggers submit callback');
+ start();
+ return false;
+ }
+ }).fileupload('add', param);
+ });
+
+ asyncTest('send', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ send: function () {
+ ok(true, 'Triggers send callback');
+ start();
+ return false;
+ }
+ }).fileupload('send', param);
+ });
+
+ asyncTest('done', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ done: function () {
+ ok(true, 'Triggers done callback');
+ start();
+ }
+ }).fileupload('send', param);
+ });
+
+ asyncTest('fail', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]},
+ fu = $('#fileupload').fileupload({
+ url: '404',
+ fail: function () {
+ ok(true, 'Triggers fail callback');
+ start();
+ }
+ });
+ (fu.data('blueimp-fileupload') || fu.data('fileupload'))
+ ._isXHRUpload = function () {
+ return true;
+ };
+ fu.fileupload('send', param);
+ });
+
+ asyncTest('always', function () {
+ expect(2);
+ var param = {files: [{name: 'test'}]},
+ counter = 0,
+ fu = $('#fileupload').fileupload({
+ always: function () {
+ ok(true, 'Triggers always callback');
+ if (counter === 1) {
+ start();
+ } else {
+ counter += 1;
+ }
+ }
+ });
+ (fu.data('blueimp-fileupload') || fu.data('fileupload'))
+ ._isXHRUpload = function () {
+ return true;
+ };
+ fu.fileupload('add', param).fileupload(
+ 'option',
+ 'url',
+ '404'
+ ).fileupload('add', param);
+ });
+
+ asyncTest('progress', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]},
+ counter = 0;
+ $('#fileupload').fileupload({
+ forceIframeTransport: true,
+ progress: function () {
+ ok(true, 'Triggers progress callback');
+ if (counter === 0) {
+ start();
+ } else {
+ counter += 1;
+ }
+ }
+ }).fileupload('send', param);
+ });
+
+ asyncTest('progressall', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]},
+ counter = 0;
+ $('#fileupload').fileupload({
+ forceIframeTransport: true,
+ progressall: function () {
+ ok(true, 'Triggers progressall callback');
+ if (counter === 0) {
+ start();
+ } else {
+ counter += 1;
+ }
+ }
+ }).fileupload('send', param);
+ });
+
+ asyncTest('start', function () {
+ expect(1);
+ var param = {files: [{name: '1'}, {name: '2'}]},
+ active = 0;
+ $('#fileupload').fileupload({
+ send: function () {
+ active += 1;
+ },
+ start: function () {
+ ok(!active, 'Triggers start callback before uploads');
+ start();
+ }
+ }).fileupload('send', param);
+ });
+
+ asyncTest('stop', function () {
+ expect(1);
+ var param = {files: [{name: '1'}, {name: '2'}]},
+ active = 0;
+ $('#fileupload').fileupload({
+ send: function () {
+ active += 1;
+ },
+ always: function () {
+ active -= 1;
+ },
+ stop: function () {
+ ok(!active, 'Triggers stop callback after uploads');
+ start();
+ }
+ }).fileupload('send', param);
+ });
+
+ test('change', function () {
+ var fu = $('#fileupload').fileupload(),
+ fuo = fu.data('blueimp-fileupload') || fu.data('fileupload'),
+ fileInput = fu.fileupload('option', 'fileInput');
+ expect(2);
+ fu.fileupload({
+ change: function (e, data) {
+ ok(true, 'Triggers change callback');
+ strictEqual(
+ data.files.length,
+ 0,
+ 'Returns empty files list'
+ );
+ },
+ add: $.noop
+ });
+ fuo._onChange({
+ data: {fileupload: fuo},
+ target: fileInput[0]
+ });
+ });
+
+ test('paste', function () {
+ var fu = $('#fileupload').fileupload(),
+ fuo = fu.data('blueimp-fileupload') || fu.data('fileupload');
+ expect(1);
+ fu.fileupload({
+ paste: function () {
+ ok(true, 'Triggers paste callback');
+ },
+ add: $.noop
+ });
+ fuo._onPaste({
+ data: {fileupload: fuo},
+ originalEvent: {
+ dataTransfer: {files: [{}]},
+ clipboardData: {items: [{}]}
+ },
+ preventDefault: $.noop
+ });
+ });
+
+ test('drop', function () {
+ var fu = $('#fileupload').fileupload(),
+ fuo = fu.data('blueimp-fileupload') || fu.data('fileupload');
+ expect(1);
+ fu.fileupload({
+ drop: function () {
+ ok(true, 'Triggers drop callback');
+ },
+ add: $.noop
+ });
+ fuo._onDrop({
+ data: {fileupload: fuo},
+ originalEvent: {
+ dataTransfer: {files: [{}]},
+ clipboardData: {items: [{}]}
+ },
+ preventDefault: $.noop
+ });
+ });
+
+ test('dragover', function () {
+ var fu = $('#fileupload').fileupload(),
+ fuo = fu.data('blueimp-fileupload') || fu.data('fileupload');
+ expect(1);
+ fu.fileupload({
+ dragover: function () {
+ ok(true, 'Triggers dragover callback');
+ },
+ add: $.noop
+ });
+ fuo._onDragOver({
+ data: {fileupload: fuo},
+ originalEvent: {dataTransfer: {types: ['Files']}},
+ preventDefault: $.noop
+ });
+ });
+
+ module('Options', lifecycle);
+
+ test('paramName', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ paramName: null,
+ send: function (e, data) {
+ strictEqual(
+ data.paramName[0],
+ data.fileInput.prop('name'),
+ 'Takes paramName from file input field if not set'
+ );
+ return false;
+ }
+ }).fileupload('send', param);
+ });
+
+ test('url', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ url: null,
+ send: function (e, data) {
+ strictEqual(
+ data.url,
+ $(data.fileInput.prop('form')).prop('action'),
+ 'Takes url from form action if not set'
+ );
+ return false;
+ }
+ }).fileupload('send', param);
+ });
+
+ test('type', function () {
+ expect(2);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ type: null,
+ send: function (e, data) {
+ strictEqual(
+ data.type,
+ 'POST',
+ 'Request type is "POST" if not set to "PUT"'
+ );
+ return false;
+ }
+ }).fileupload('send', param);
+ $('#fileupload').fileupload({
+ type: 'PUT',
+ send: function (e, data) {
+ strictEqual(
+ data.type,
+ 'PUT',
+ 'Request type is "PUT" if set to "PUT"'
+ );
+ return false;
+ }
+ }).fileupload('send', param);
+ });
+
+ test('replaceFileInput', function () {
+ var fu = $('#fileupload').fileupload(),
+ fuo = fu.data('blueimp-fileupload') || fu.data('fileupload'),
+ fileInput = fu.fileupload('option', 'fileInput'),
+ fileInputElement = fileInput[0];
+ expect(2);
+ fu.fileupload({
+ replaceFileInput: false,
+ change: function () {
+ strictEqual(
+ fu.fileupload('option', 'fileInput')[0],
+ fileInputElement,
+ 'Keeps file input with replaceFileInput: false'
+ );
+ },
+ add: $.noop
+ });
+ fuo._onChange({
+ data: {fileupload: fuo},
+ target: fileInput[0]
+ });
+ fu.fileupload({
+ replaceFileInput: true,
+ change: function () {
+ notStrictEqual(
+ fu.fileupload('option', 'fileInput')[0],
+ fileInputElement,
+ 'Replaces file input with replaceFileInput: true'
+ );
+ },
+ add: $.noop
+ });
+ fuo._onChange({
+ data: {fileupload: fuo},
+ target: fileInput[0]
+ });
+ });
+
+ asyncTest('forceIframeTransport', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ forceIframeTransport: true,
+ done: function (e, data) {
+ strictEqual(
+ data.dataType.substr(0, 6),
+ 'iframe',
+ 'Iframe Transport is used'
+ );
+ start();
+ }
+ }).fileupload('send', param);
+ });
+
+ test('singleFileUploads', function () {
+ expect(3);
+ var fu = $('#fileupload').fileupload(),
+ param = {files: [{name: '1'}, {name: '2'}]},
+ index = 1;
+ (fu.data('blueimp-fileupload') || fu.data('fileupload'))
+ ._isXHRUpload = function () {
+ return true;
+ };
+ $('#fileupload').fileupload({
+ singleFileUploads: true,
+ add: function () {
+ ok(true, 'Triggers callback number ' + index.toString());
+ index += 1;
+ }
+ }).fileupload('add', param).fileupload(
+ 'option',
+ 'singleFileUploads',
+ false
+ ).fileupload('add', param);
+ });
+
+ test('limitMultiFileUploads', function () {
+ expect(3);
+ var fu = $('#fileupload').fileupload(),
+ param = {files: [
+ {name: '1'},
+ {name: '2'},
+ {name: '3'},
+ {name: '4'},
+ {name: '5'}
+ ]},
+ index = 1;
+ (fu.data('blueimp-fileupload') || fu.data('fileupload'))
+ ._isXHRUpload = function () {
+ return true;
+ };
+ $('#fileupload').fileupload({
+ singleFileUploads: false,
+ limitMultiFileUploads: 2,
+ add: function () {
+ ok(true, 'Triggers callback number ' + index.toString());
+ index += 1;
+ }
+ }).fileupload('add', param);
+ });
+
+ test('limitMultiFileUploadSize', function () {
+ expect(7);
+ var fu = $('#fileupload').fileupload(),
+ param = {files: [
+ {name: '1-1', size: 100000},
+ {name: '1-2', size: 40000},
+ {name: '2-1', size: 100000},
+ {name: '3-1', size: 50000},
+ {name: '3-2', size: 40000},
+ {name: '4-1', size: 45000} // New request due to limitMultiFileUploads
+ ]},
+ param2 = {files: [
+ {name: '5-1'},
+ {name: '5-2'},
+ {name: '6-1'},
+ {name: '6-2'},
+ {name: '7-1'}
+ ]},
+ index = 1;
+ (fu.data('blueimp-fileupload') || fu.data('fileupload'))
+ ._isXHRUpload = function () {
+ return true;
+ };
+ $('#fileupload').fileupload({
+ singleFileUploads: false,
+ limitMultiFileUploads: 2,
+ limitMultiFileUploadSize: 150000,
+ limitMultiFileUploadSizeOverhead: 5000,
+ add: function () {
+ ok(true, 'Triggers callback number ' + index.toString());
+ index += 1;
+ }
+ }).fileupload('add', param).fileupload('add', param2);
+ });
+
+ asyncTest('sequentialUploads', function () {
+ expect(6);
+ var param = {files: [
+ {name: '1'},
+ {name: '2'},
+ {name: '3'},
+ {name: '4'},
+ {name: '5'},
+ {name: '6'}
+ ]},
+ addIndex = 0,
+ sendIndex = 0,
+ loadIndex = 0,
+ fu = $('#fileupload').fileupload({
+ sequentialUploads: true,
+ add: function (e, data) {
+ addIndex += 1;
+ if (addIndex === 4) {
+ data.submit().abort();
+ } else {
+ data.submit();
+ }
+ },
+ send: function () {
+ sendIndex += 1;
+ },
+ done: function () {
+ loadIndex += 1;
+ strictEqual(sendIndex, loadIndex, 'upload in order');
+ },
+ fail: function (e, data) {
+ strictEqual(data.errorThrown, 'abort', 'upload aborted');
+ },
+ stop: function () {
+ start();
+ }
+ });
+ (fu.data('blueimp-fileupload') || fu.data('fileupload'))
+ ._isXHRUpload = function () {
+ return true;
+ };
+ fu.fileupload('add', param);
+ });
+
+ asyncTest('limitConcurrentUploads', function () {
+ expect(12);
+ var param = {files: [
+ {name: '1'},
+ {name: '2'},
+ {name: '3'},
+ {name: '4'},
+ {name: '5'},
+ {name: '6'},
+ {name: '7'},
+ {name: '8'},
+ {name: '9'},
+ {name: '10'},
+ {name: '11'},
+ {name: '12'}
+ ]},
+ addIndex = 0,
+ sendIndex = 0,
+ loadIndex = 0,
+ fu = $('#fileupload').fileupload({
+ limitConcurrentUploads: 3,
+ add: function (e, data) {
+ addIndex += 1;
+ if (addIndex === 4) {
+ data.submit().abort();
+ } else {
+ data.submit();
+ }
+ },
+ send: function () {
+ sendIndex += 1;
+ },
+ done: function () {
+ loadIndex += 1;
+ ok(sendIndex - loadIndex < 3);
+ },
+ fail: function (e, data) {
+ strictEqual(data.errorThrown, 'abort', 'upload aborted');
+ },
+ stop: function () {
+ start();
+ }
+ });
+ (fu.data('blueimp-fileupload') || fu.data('fileupload'))
+ ._isXHRUpload = function () {
+ return true;
+ };
+ fu.fileupload('add', param);
+ });
+
+ if ($.support.xhrFileUpload) {
+ asyncTest('multipart', function () {
+ expect(2);
+ var param = {files: [{
+ name: 'test.png',
+ size: 123,
+ type: 'image/png'
+ }]},
+ fu = $('#fileupload').fileupload({
+ multipart: false,
+ always: function (e, data) {
+ strictEqual(
+ data.contentType,
+ param.files[0].type,
+ 'non-multipart upload sets file type as contentType'
+ );
+ strictEqual(
+ data.headers['Content-Disposition'],
+ 'attachment; filename="' + param.files[0].name + '"',
+ 'non-multipart upload sets Content-Disposition header'
+ );
+ start();
+ }
+ });
+ fu.fileupload('send', param);
+ });
+ }
+
+ module('UI Initialization', lifecycleUI);
+
+ test('Widget initialization', function () {
+ var fu = $('#fileupload').fileupload();
+ ok(fu.data('blueimp-fileupload') || fu.data('fileupload'));
+ ok(
+ $('#fileupload').fileupload('option', 'uploadTemplate').length,
+ 'Initialized upload template'
+ );
+ ok(
+ $('#fileupload').fileupload('option', 'downloadTemplate').length,
+ 'Initialized download template'
+ );
+ });
+
+ test('Buttonbar event listeners', function () {
+ var buttonbar = $('#fileupload .fileupload-buttonbar'),
+ files = [{name: 'test'}];
+ expect(4);
+ $('#fileupload').fileupload({
+ send: function () {
+ ok(true, 'Started file upload via global start button');
+ },
+ fail: function (e, data) {
+ ok(true, 'Canceled file upload via global cancel button');
+ data.context.remove();
+ },
+ destroy: function () {
+ ok(true, 'Delete action called via global delete button');
+ }
+ });
+ $('#fileupload').fileupload('add', {files: files});
+ buttonbar.find('.cancel').click();
+ $('#fileupload').fileupload('add', {files: files});
+ buttonbar.find('.start').click();
+ buttonbar.find('.cancel').click();
+ files[0].deleteUrl = 'http://example.org/banana.jpg';
+ ($('#fileupload').data('blueimp-fileupload') ||
+ $('#fileupload').data('fileupload'))
+ ._renderDownload(files)
+ .appendTo($('#fileupload .files')).show()
+ .find('.toggle').click();
+ buttonbar.find('.delete').click();
+ });
+
+ module('UI API', lifecycleUI);
+
+ test('destroy', function () {
+ var buttonbar = $('#fileupload .fileupload-buttonbar'),
+ files = [{name: 'test'}];
+ expect(1);
+ $('#fileupload').fileupload({
+ send: function () {
+ ok(true, 'This test should not run');
+ return false;
+ }
+ })
+ .fileupload('add', {files: files})
+ .fileupload('destroy');
+ buttonbar.find('.start').click(function () {
+ ok(true, 'Clicked global start button');
+ return false;
+ }).click();
+ });
+
+ test('disable/enable', function () {
+ var buttonbar = $('#fileupload .fileupload-buttonbar');
+ $('#fileupload').fileupload();
+ $('#fileupload').fileupload('disable');
+ strictEqual(
+ buttonbar.find('input[type=file], button').not(':disabled').length,
+ 0,
+ 'Disables the buttonbar buttons'
+ );
+ $('#fileupload').fileupload('enable');
+ strictEqual(
+ buttonbar.find('input[type=file], button').not(':disabled').length,
+ 4,
+ 'Enables the buttonbar buttons'
+ );
+ });
+
+ module('UI Callbacks', lifecycleUI);
+
+ test('destroy', function () {
+ expect(3);
+ $('#fileupload').fileupload({
+ destroy: function (e, data) {
+ ok(true, 'Triggers destroy callback');
+ strictEqual(
+ data.url,
+ 'test',
+ 'Passes over deletion url parameter'
+ );
+ strictEqual(
+ data.type,
+ 'DELETE',
+ 'Passes over deletion request type parameter'
+ );
+ }
+ });
+ ($('#fileupload').data('blueimp-fileupload') ||
+ $('#fileupload').data('fileupload'))
+ ._renderDownload([{
+ name: 'test',
+ deleteUrl: 'test',
+ deleteType: 'DELETE'
+ }])
+ .appendTo($('#fileupload .files'))
+ .show()
+ .find('.toggle').click();
+ $('#fileupload .fileupload-buttonbar .delete').click();
+ });
+
+ asyncTest('added', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ added: function (e, data) {
+ start();
+ strictEqual(
+ data.files[0].name,
+ param.files[0].name,
+ 'Triggers added callback'
+ );
+ },
+ send: function () {
+ return false;
+ }
+ }).fileupload('add', param);
+ });
+
+ asyncTest('started', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ started: function () {
+ start();
+ ok('Triggers started callback');
+ return false;
+ },
+ sent: function () {
+ return false;
+ }
+ }).fileupload('send', param);
+ });
+
+ asyncTest('sent', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ sent: function (e, data) {
+ start();
+ strictEqual(
+ data.files[0].name,
+ param.files[0].name,
+ 'Triggers sent callback'
+ );
+ return false;
+ }
+ }).fileupload('send', param);
+ });
+
+ asyncTest('completed', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ completed: function () {
+ start();
+ ok('Triggers completed callback');
+ return false;
+ }
+ }).fileupload('send', param);
+ });
+
+ asyncTest('failed', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ failed: function () {
+ start();
+ ok('Triggers failed callback');
+ return false;
+ }
+ }).fileupload('send', param).abort();
+ });
+
+ asyncTest('stopped', function () {
+ expect(1);
+ var param = {files: [{name: 'test'}]};
+ $('#fileupload').fileupload({
+ stopped: function () {
+ start();
+ ok('Triggers stopped callback');
+ return false;
+ }
+ }).fileupload('send', param);
+ });
+
+ asyncTest('destroyed', function () {
+ expect(1);
+ $('#fileupload').fileupload({
+ dataType: 'html',
+ destroyed: function () {
+ start();
+ ok(true, 'Triggers destroyed callback');
+ }
+ });
+ ($('#fileupload').data('blueimp-fileupload') ||
+ $('#fileupload').data('fileupload'))
+ ._renderDownload([{
+ name: 'test',
+ deleteUrl: '.',
+ deleteType: 'GET'
+ }])
+ .appendTo($('#fileupload .files'))
+ .show()
+ .find('.toggle').click();
+ $('#fileupload .fileupload-buttonbar .delete').click();
+ });
+
+ module('UI Options', lifecycleUI);
+
+ test('autoUpload', function () {
+ expect(1);
+ $('#fileupload')
+ .fileupload({
+ autoUpload: true,
+ send: function () {
+ ok(true, 'Started file upload automatically');
+ return false;
+ }
+ })
+ .fileupload('add', {files: [{name: 'test'}]})
+ .fileupload('option', 'autoUpload', false)
+ .fileupload('add', {files: [{name: 'test'}]});
+ });
+
+ test('maxNumberOfFiles', function () {
+ expect(3);
+ var addIndex = 0,
+ sendIndex = 0;
+ $('#fileupload')
+ .fileupload({
+ autoUpload: true,
+ maxNumberOfFiles: 3,
+ singleFileUploads: false,
+ send: function () {
+ strictEqual(
+ sendIndex += 1,
+ addIndex
+ );
+ },
+ progress: $.noop,
+ progressall: $.noop,
+ done: $.noop,
+ stop: $.noop
+ })
+ .fileupload('add', {files: [{name: (addIndex += 1)}]})
+ .fileupload('add', {files: [{name: (addIndex += 1)}]})
+ .fileupload('add', {files: [{name: (addIndex += 1)}]})
+ .fileupload('add', {files: [{name: 'test'}]});
+ });
+
+ test('maxFileSize', function () {
+ expect(2);
+ var addIndex = 0,
+ sendIndex = 0;
+ $('#fileupload')
+ .fileupload({
+ autoUpload: true,
+ maxFileSize: 1000,
+ send: function () {
+ strictEqual(
+ sendIndex += 1,
+ addIndex
+ );
+ return false;
+ }
+ })
+ .fileupload('add', {files: [{
+ name: (addIndex += 1)
+ }]})
+ .fileupload('add', {files: [{
+ name: (addIndex += 1),
+ size: 999
+ }]})
+ .fileupload('add', {files: [{
+ name: 'test',
+ size: 1001
+ }]})
+ .fileupload({
+ send: function (e, data) {
+ ok(
+ !$.blueimp.fileupload.prototype.options
+ .send.call(this, e, data)
+ );
+ return false;
+ }
+ });
+ });
+
+ test('minFileSize', function () {
+ expect(2);
+ var addIndex = 0,
+ sendIndex = 0;
+ $('#fileupload')
+ .fileupload({
+ autoUpload: true,
+ minFileSize: 1000,
+ send: function () {
+ strictEqual(
+ sendIndex += 1,
+ addIndex
+ );
+ return false;
+ }
+ })
+ .fileupload('add', {files: [{
+ name: (addIndex += 1)
+ }]})
+ .fileupload('add', {files: [{
+ name: (addIndex += 1),
+ size: 1001
+ }]})
+ .fileupload('add', {files: [{
+ name: 'test',
+ size: 999
+ }]})
+ .fileupload({
+ send: function (e, data) {
+ ok(
+ !$.blueimp.fileupload.prototype.options
+ .send.call(this, e, data)
+ );
+ return false;
+ }
+ });
+ });
+
+ test('acceptFileTypes', function () {
+ expect(2);
+ var addIndex = 0,
+ sendIndex = 0;
+ $('#fileupload')
+ .fileupload({
+ autoUpload: true,
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
+ disableImageMetaDataLoad: true,
+ send: function () {
+ strictEqual(
+ sendIndex += 1,
+ addIndex
+ );
+ return false;
+ }
+ })
+ .fileupload('add', {files: [{
+ name: (addIndex += 1) + '.jpg'
+ }]})
+ .fileupload('add', {files: [{
+ name: (addIndex += 1),
+ type: 'image/jpeg'
+ }]})
+ .fileupload('add', {files: [{
+ name: 'test.txt',
+ type: 'text/plain'
+ }]})
+ .fileupload({
+ send: function (e, data) {
+ ok(
+ !$.blueimp.fileupload.prototype.options
+ .send.call(this, e, data)
+ );
+ return false;
+ }
+ });
+ });
+
+ test('acceptFileTypes as HTML5 data attribute', function () {
+ expect(2);
+ var regExp = /(\.|\/)(gif|jpe?g|png)$/i;
+ $('#fileupload')
+ .attr('data-accept-file-types', regExp.toString())
+ .fileupload();
+ strictEqual(
+ $.type($('#fileupload').fileupload('option', 'acceptFileTypes')),
+ $.type(regExp)
+ );
+ strictEqual(
+ $('#fileupload').fileupload('option', 'acceptFileTypes').toString(),
+ regExp.toString()
+ );
+ });
+
+});
diff --git a/templates/NearBeach/campus_information.html b/templates/NearBeach/campus_information.html
index cdd69167f..c22941f40 100644
--- a/templates/NearBeach/campus_information.html
+++ b/templates/NearBeach/campus_information.html
@@ -76,100 +76,6 @@
Campus Contacts
Map