Added an API for AJAX file uploads and the monitoring thereof. This is to be used in Snapr and soon core (Special:UploadFile).
authorDan Fuhry <dan@enanocms.org>
Sat, 21 Aug 2010 23:31:36 -0400
changeset 1295 3c9c1b18567b
parent 1294 f61a263564b2
child 1296 d9d249c4dfd2
Added an API for AJAX file uploads and the monitoring thereof. This is to be used in Snapr and soon core (Special:UploadFile).
includes/clientside/css/enano-shared.css
includes/clientside/jsres.php
includes/clientside/static/upload.js
includes/functions.php
language/english/admin.json
language/english/core.json
language/english/tools.json
plugins/SpecialPageFuncs.php
--- a/includes/clientside/css/enano-shared.css	Sat Aug 21 23:30:56 2010 -0400
+++ b/includes/clientside/css/enano-shared.css	Sat Aug 21 23:31:36 2010 -0400
@@ -1056,3 +1056,33 @@
 	background-color: #ddd;
 	padding: 3px;
 }
+
+/* Totally cheating jQuery UI here. */
+div.ui-corner-left {
+	-moz-border-radius: 4px 0 0 4px;
+	-webkit-border-radius: 4px 0 0 4px;
+	border-radius: 4px 0 0 4px;
+}
+div.ui-corner-right {
+	-moz-border-radius: 0 4px 4px 0;
+	-webkit-border-radius: 0 4px 4px 0;
+	border-radius: 0 4px 4px 0;
+}
+div.ui-corner-all, div.ui-corner-left.ui-corner-right {
+	-moz-border-radius: 4px;
+	-webkit-border-radius: 4px;
+	border-radius: 4px;
+}
+div.ui-progressbar {
+	height: 2em;
+	background-color: #d0d0d0;
+}
+
+div.ui-progressbar-value {
+	height: 100%;
+	background-color: #909090;
+}
+
+div.ui-progressbar-failure div.ui-progressbar-value {
+	background-color: #E02600;
+}
--- a/includes/clientside/jsres.php	Sat Aug 21 23:30:56 2010 -0400
+++ b/includes/clientside/jsres.php	Sat Aug 21 23:31:36 2010 -0400
@@ -95,6 +95,7 @@
 	'userpage.js',
 	'template-compiler.js',
 	'toolbar.js',
+	'upload.js'
 );
 
 // Files that should NOT be compressed due to already being compressed, licensing, or invalid produced code
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/upload.js	Sat Aug 21 23:31:36 2010 -0400
@@ -0,0 +1,202 @@
+window.AjaxUpload = function(formid)
+{
+	load_component(['jquery', 'jquery-ui', 'l10n']);
+	
+	var theform = document.getElementById(formid);
+	theform.AjaxUpload = this;
+	this.form = theform;
+	
+	$(this.form).submit(function()
+		{
+			return this.AjaxUpload.presubmit();
+		});
+};
+
+window.zeropad = function(i, ndig)
+{
+	var s = String(i);
+	while ( s.length < ndig )
+		s = '0' + s;
+	return s;
+}
+
+window.humanize_time = function(secs)
+{
+	var seconds = secs % 60;
+	var minutes = (secs - seconds) / 60;
+	if ( minutes >= 60 )
+	{
+		var hours = (minutes - (minutes % 60)) / 60;
+		minutes = minutes % 60;
+		return zeropad(hours, 2) + ':' + zeropad(minutes, 2) + ':' + zeropad(seconds, 2);
+	}
+	return zeropad(minutes, 2) + ':' + zeropad(seconds, 2);
+}
+
+AjaxUpload.prototype.cancelbit = false;
+	
+AjaxUpload.prototype.status = function(state)
+	{
+		if ( state.done )
+		{
+			$('div.wait-box', this.statusbox).text($lang.get('upload_msg_processing'));
+			$('div.progress', this.statusbox).progressbar('value', 100);
+		}
+		else if ( state.cancel_upload )
+		{
+			if ( window.stop )
+				window.stop();
+			else if ( document.execCommand )
+				document.execCommand('Stop');
+			$('div.wait-box', this.statusbox).addClass('error-box').removeClass('wait-box').text($lang.get('upload_msg_cancelled'));
+			
+			$('div.progress', this.statusbox).progressbar('value', 100).addClass('ui-progressbar-failure');
+		}
+		else
+		{
+			var rawpct = state.bytes_processed / state.content_length;
+			var pct = (Math.round((rawpct) * 1000)) / 10;
+			var elapsed = state.current_time - state.start_time;
+			var rawbps = state.bytes_processed / elapsed;
+			var kbps = Math.round((rawbps) / 1024);
+			var remain_bytes = state.content_length - state.bytes_processed;
+			var remain_time = Math.round(remain_bytes / rawbps);
+			if ( pct > 0 )
+			$('div.wait-box', this.statusbox).text($lang.get('upload_msg_uploading', {
+								percent: pct,
+								elapsed: humanize_time(elapsed),
+								speed: kbps,
+								remain: humanize_time(remain_time)
+							}))
+					.append('<br /><a href="#" class="cancel"></a>');
+			else
+				$('div.wait-box', this.statusbox).text($lang.get('upload_msg_starting'))
+					.append('<br /><a href="#" class="cancel"></a>');
+			
+			var au = this;
+			$('a.cancel', this.statusbox).text($lang.get('upload_btn_cancel')).click(function()
+				{
+					au.cancel();
+					return false;
+				});
+				
+			$('div.progress', this.statusbox).progressbar('value', pct);
+		}
+	};
+	
+AjaxUpload.prototype.cancel = function()
+	{
+		this.cancelbit = true;
+	};
+
+AjaxUpload.prototype.refresh_status = function(au)
+	{
+		try
+		{
+			var cancelbit = au.cancelbit ? '&cancel=true' : '';
+			au.cancelbit = false;
+			ajaxGet(makeUrlNS('Special', 'AjaxUpload', 'form=' + au.form.id + '&uploadstatus=' + au.key + cancelbit), au._incoming_status);
+		}
+		catch (e)
+		{
+			alert(e);
+		}
+	};
+	
+AjaxUpload.prototype._incoming_status = function(ajax)
+	{
+		if ( ajax.readyState == 4 )
+		{
+			var state = parseJSON(ajax.responseText);
+			if ( state )
+			{
+				var au = document.getElementById(state.form).AjaxUpload;
+				au.status(state);
+				
+				if ( !state.done && !state.cancel_upload )
+					setTimeout(function()
+						{
+							au.refresh_status(au)
+						}, 250);
+			}
+		}
+	};
+	
+AjaxUpload.prototype.presubmit = function()
+	{
+		try
+		{
+			// create status container and target iframe
+			this.statusbox = document.createElement('div');
+			this.iframe = document.createElement('iframe');
+			this.iframe.AjaxUpload = this;
+			$(this.iframe)
+				.attr('src', 'about:blank')
+				.attr('width', '1')
+				.attr('height', '1')
+				.attr('frameborder', '0')
+				.css('visibility', 'hidden')
+				.attr('name', this.form.id + '_frame')
+				.load(this._frame_onload);
+				
+			this.form.parentNode.insertBefore(this.statusbox, this.form);
+			this.form.parentNode.insertBefore(this.iframe, this.form);
+			this.form.target = this.form.id + '_frame';
+			
+			this.upload_start();
+			
+			var have_progress_support = this.form.progress_support.value == 'true';
+			if ( have_progress_support )
+			{
+				this.key = this.form[this.form.upload_progress_name.value].value;
+				this.refresh_status(this);
+			}
+		}
+		catch ( e )
+		{
+			console.debug(e);
+			return false;
+		}
+		
+		return true;
+	};
+	
+AjaxUpload.prototype._frame_onload = function()
+	{
+		var childbody = window[this.AjaxUpload.form.id + '_frame'].document.getElementsByTagName('body')[0];
+		window[this.AjaxUpload.form.id + '_frame'].document.close();
+		this.AjaxUpload.upload_success(childbody);
+	};
+	
+AjaxUpload.prototype.upload_start = function()
+	{
+		$(this.statusbox).html('<div class="wait-box">' + $lang.get('upload_msg_starting') + '</div><div class="progress" style="margin-top: 10px;"></div>');
+		$('div.progress', this.statusbox).progressbar({ value: 0 });
+		$(this.form).hide();
+	};
+	
+AjaxUpload.prototype.upload_success = function(childbody)
+	{
+		$(this.statusbox).html('<div class="info-box">Upload complete! Result from server:' + childbody.innerHTML + '</div>');
+		$('div.info-box', this.statusbox).append('<a href="#">Reset!</a>');
+		var form_id = this.form.id;
+		$('div.info-box a', this.statusbox).click(function()
+			{
+				try
+				{
+					var au = document.getElementById(form_id).AjaxUpload;
+					au.reset();
+				}
+				catch(e) {};
+				return false;
+			});
+	};
+	
+AjaxUpload.prototype.reset = function()
+	{
+		this.iframe.parentNode.removeChild(this.iframe);
+		this.statusbox.parentNode.removeChild(this.statusbox);
+		delete(window[this.form.id + '_frame']);
+		$('form#' + this.form.id).show();
+		return false;
+	};
--- a/includes/functions.php	Sat Aug 21 23:30:56 2010 -0400
+++ b/includes/functions.php	Sat Aug 21 23:31:36 2010 -0400
@@ -309,7 +309,7 @@
 
 function have_blank_urlname_page()
 {
-	return getConfig('main_page', 'Main_Page') == '' || getConfig('main_page', getConfig('main_page', 'Main_Page')) == '';
+	return getConfig('main_page', 'Main_Page') == '' || ( getConfig('main_page_alt_enable', 0) == 1 && getConfig('main_page_alt', getConfig('main_page', 'Main_Page')) == '' );
 }
 
 /**
@@ -1950,7 +1950,7 @@
 {
 	global $db, $session, $paths, $template, $plugins; // Common objects
 	// Decide whether to quote the string or not
-	if(substr($type, 0, 7) == 'varchar' || $type == 'datetime' || $type == 'text' || $type == 'tinytext' || $type == 'smalltext' || $type == 'longtext' || substr($type, 0, 4) == 'char')
+	if(substr($type, 0, 7) == 'varchar' || $type == 'datetime' || $type == 'text' || $type == 'tinytext' || $type == 'smalltext' || $type == 'longtext' || substr($type, 0, 4) == 'char' || substr($type, 0, 4) == 'enum')
 	{
 		$str = "'" . $db->escape($input) . "'";
 	}
@@ -4222,7 +4222,7 @@
 			throw new Exception('Invalid extension of input file.');
 	}
 		
-	$magick_path = getConfig('imagemagick_path');
+	$magick_path = getConfig('imagemagick_path', '/usr/bin/convert');
 	$can_use_magick = (
 			getConfig('enable_imagemagick') == '1' &&
 			file_exists($magick_path)              &&
@@ -5408,3 +5408,39 @@
 		}
 	}
 }
+
+/**
+ * Echo out the HTML and Javascript used to make a form with files in it upload via ajax.
+ * @param string The DOM ID of the form (<form id="...">)
+ * @return null
+ */
+
+function ajax_upload_js($formid)
+{
+	$upkey = md5(mt_rand() . microtime());
+	$php_has_progress_support = @ini_get('session.upload_progress.enabled') == '1' ? 'true' : 'false';
+	
+	$field_name = ini_get("session.upload_progress.name");
+	
+	?>
+	<input type="hidden" name="upload_progress_name" value="<?php echo !empty($field_name) ? $field_name : ''; ?>" />
+	<?php if ( !empty($field_name) ): ?>
+	<input type="hidden" name="<?php echo $field_name; ?>" value="<?php echo $upkey; ?>" />
+	<?php endif; ?>
+	<input type="hidden" name="progress_support" value="<?php echo $php_has_progress_support; ?>" />
+	
+	<script type="text/javascript">
+	//<![CDATA[
+	
+	addOnloadHook(function()
+		{
+			load_component('upload');
+			var ajaxupload = new AjaxUpload('<?php echo $formid; ?>');
+			eval(setHook('<?php echo $formid; ?>_ajaxupload_init'));
+		});
+	//]]>
+	</script>
+	
+	<?php
+}
+
--- a/language/english/admin.json	Sat Aug 21 23:30:56 2010 -0400
+++ b/language/english/admin.json	Sat Aug 21 23:31:36 2010 -0400
@@ -687,7 +687,7 @@
 		},
 		acpcm: {
 			heading_main: 'Performance and caching settings',
-			intro: 'From this page you can control what information on your site is stored in cache files. Caching speeds up Enano performance by allowing data to be retrieved from the disk instead of querying the database. Sometimes the cache isn\'t updated immediately when the database is. From this page you can control what is cached, and you can clear specific caches to force them to refresh.',
+			intro: 'Some information on your site is stored in cache files. Caching speeds up Enano performance by allowing data to be retrieved from the disk instead of querying the database. Sometimes the cache isn\'t updated immediately when the database is. From this page you can control what is cached, and you can clear specific caches to force them to refresh.',
 			msg_refresh_warning: 'Some of the caches on this page will automatically refresh immediately even if you click Clear. This is because any use of the component will invoke the cache. To disable this behavior, you must disable caching site-wide.',
 			table_header: 'Cache settings',
 			lbl_enable_cache: 'Enable the cache (recommended)',
--- a/language/english/core.json	Sat Aug 21 23:30:56 2010 -0400
+++ b/language/english/core.json	Sat Aug 21 23:31:36 2010 -0400
@@ -1,4 +1,4 @@
-i/*
+/*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
  * Version 1.1.6 (Caoineag beta 1)
  * Copyright (C) 2006-2007 Dan Fuhry
--- a/language/english/tools.json	Sat Aug 21 23:30:56 2010 -0400
+++ b/language/english/tools.json	Sat Aug 21 23:31:36 2010 -0400
@@ -12,7 +12,7 @@
 
 var enano_lang = {
 	categories: [
-		'meta', 'search', 'specialpage', 'pagetools', 'log'
+		'meta', 'search', 'specialpage', 'pagetools', 'log', 'upload'
 	],
 	strings: {
 		meta: {
@@ -20,6 +20,7 @@
 			specialpage: 'Special pages',
 			pagetools: 'Userspace page-management tools',
 			log: 'Log and history display',
+			upload: 'AJAX upload control'
 		},
 		specialpage: {
 			administration: 'Administration',
@@ -41,6 +42,7 @@
 			register: 'Register',
 			preferences: 'Edit Profile',
 			autofill: 'Javascript suggestion servlet',
+			ajaxupload: 'AJAX file upload status',
 			contributions: 'User contributions',
 			change_theme: 'Change my preferred theme',
 			activate_account: 'Activate user account',
@@ -194,6 +196,13 @@
 			err_addfilter_field_empty: 'The filter was not added because you didn\'t enter a valid value in the field.',
 			
 			err_access_denied: 'You don\'t have permission to view page logs.',
+		},
+		upload: {
+			msg_processing: 'Processing upload...',
+			msg_cancelled: 'You cancelled the upload process.',
+			msg_uploading: 'Uploading... %percent%% complete, elapsed time %elapsed%, speed %speed%KB/s, remaining time %remain%',
+			btn_cancel: 'Cancel upload',
+			msg_starting: 'Starting upload...',
 		}
 	}
 };
--- a/plugins/SpecialPageFuncs.php	Sat Aug 21 23:30:56 2010 -0400
+++ b/plugins/SpecialPageFuncs.php	Sat Aug 21 23:31:36 2010 -0400
@@ -34,6 +34,7 @@
 	register_special_page('GNU_General_Public_License', 'specialpage_gnu_gpl');
 	register_special_page('TagCloud', 'specialpage_tag_cloud');
 	register_special_page('Autofill', 'specialpage_autofill', false);
+	register_special_page('AjaxUpload', 'specialpage_ajaxupload', false);
 }
 
 // function names are IMPORTANT!!! The name pattern is: page_<namespace ID>_<page URLname, without namespace>
@@ -703,4 +704,28 @@
 	echo enano_json_encode($dataset);
 }
 
+function page_Special_AjaxUpload()
+{
+	if ( isset($_GET['uploadstatus']) )
+	{
+		session_start();
+		header('Content-type: text/javascript');
+		$key = "upload_progress_{$_GET['uploadstatus']}";
+		$info = isset($_SESSION[$key]) ? $_SESSION[$key] : array();
+		if ( isset($_SESSION[$key]) && $_SESSION[$key]['done'] )
+			unset($_SESSION[$key]);
+		
+		if ( is_array($info) )
+		{
+			$info['current_time'] = time();
+			if ( !empty($_GET['cancel']) )
+				$_SESSION[$key]['cancel_upload'] = $info['cancel_upload'] = true;
+		}
+		$info['form'] = $_GET['form'];
+		
+		echo enano_json_encode($info);
+		exit;
+	}
+}
+
 ?>