text: /************************************************/
  /* IPB3 Javascript								*/
  /* -------------------------------------------- */
  /* ips.attach.js - Attachment manager			*/
  /* (c) IPS, Inc 2008							*/
  /* -------------------------------------------- */
  /* Author: Rikki Tissier						*/
  /************************************************/
  /* -TRUE- MULTIPLE ATTACHMENTS!!!				*/
  /* -------------------------------------------- */
  
  var _attach = window.IPBoard;
  
  var IPS_UPLOADER_INIT_DONE = false;
  
  _attach.prototype.attach = {
  	uploaders: [],
  	template: '',
  	_doneJson: false,
  	optionsCache: {},
  	
  	init: function()
  	{
  		Debug.write("Initializing ips.attach.js");
  		
  		IPS_UPLOADER_INIT_DONE = true;
  		
  		document.observe("dom:loaded", function(){
  			
  			//ipb.attach.initUploaders();
  		});
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Registers an upload object
  	 * 
  	 * @param	{int}		id			The uploader ID
  	 * @param	{object}	options		Options to pass to object
  	*/
  	registerUploader: function( id, type, wrapper, options )
  	{		
  		if( Object.isUndefined( id ) || id == null )
  		{
  			Debug.error("ips.attach.js: Attachment manager already has that ID");
  			return;
  		}
  		// Already exists?
  		if( ipb.attach.uploaders[ id ] )
  		{
  			Debug.error("ips.attach.js: This uploader has already been registered");
  		}
  		
  		//-----------------------------------------
  		// Make object
  		
  		if( type == 'swf' ){
  			if( options.file_size_limit )
  			{
  				options.file_size_limit = options.file_size_limit + " B";
  			}
  			uploader = new ipb.attachSWF( id, options, wrapper, ipb.attach.template );
  		} else {
  			uploader = new ipb.attachTraditional( id, options, wrapper, ipb.attach.template );
  		}
  		
  		if( uploader )
  		{
  			ipb.attach.uploaders[ id ] = uploader;
  			
  			if( $( 'nojs_' + id + '_1' ) ){
  				$( 'nojs_' + id + '_1' ).hide();
  			}
  			if( $( 'nojs_' + id + '_2' ) ){
  				$( 'nojs_' + id + '_2' ).hide();
  			}
  		}
  		
  		ipb.attach.optionsCache    = options;
  		ipb.attach.optionsCache.id = id;
  		
  		Debug.write("Uploader registered");
  	},
  	
  	switchUploadType: function( mode )
  	{
  		var url  = ipb.vars['base_url'] + "app=core&module=ajax&section=attach&do=setPref&pref=" + mode + '&secure_key=' + ipb.vars['secure_hash'];
  		var wLoc = window.location.toString();
  
  		new Ajax.Request( url,
  						{
  							method: 'post',
  							evalJSON: 'force',
  							hideLoader: true,
  							onSuccess: function(t)
  							{
  								if( Object.isUndefined( t.responseJSON ) ){ alert( ipb.lang['action_failed'] ); return; }
  								
  								if ( t.responseJSON['status'] == 'ok' )
  								{
  									/* Do some jazzy stuffs */
  									if ( $('postingform') )
  									{
  										/* Preserve poll
  											@link	http://community.invisionpower.com/tracker/issue-32853-poll-data-missing-after-change-the-uploader
  										*/
  										if( $('poll_wrap') && $('add_poll') )
  										{
  											try {
  												$('poll_wrap').hide();
  												$('add_poll').insert({ after: $('poll_wrap') });
  											} catch(err){ }
  										}
  
  										$('postingform').action += '&preview=1&uploadPref=' + mode;
  										$('postingform').submit();
  									}
  									else if( $('msgForm') )
  									{
  										var _key = 'preview';
  										
  										if( $('msgForm').action.match( /do=sendReply/ ) )
  										{
  											_key	= 'previewReply';
  										}
  										
  										$('msgForm').action += '&' + _key + '=1&uploadPref=' + mode;
  										$('msgForm').submit();
  									}
  									else
  									{
  										window.location = wLoc.replace("#", "").replace( new RegExp( /&uploadPref=\S+?(&|$)/ ), '' ) + '&uploadPref=' + mode + '&preview=1';
  									}
  								}
  								else
  								{
  									alert( ipb.lang['action_failed'] );
  									return;
  								}
  							}
  						});
  	},
  	
  	removeUpload: function(e)
  	{
  		elem = Event.findElement( e, 'li' );
  		elemid = elem.id.replace('cur_', '');
  		elemid = elemid.match( /^ali_(.+?)_([0-9]+)$/ );
  		
  		if( !elemid[1] ){ return; }
  		
  		obj = ipb.attach.uploaders[ elemid[1] ];
  		attachid = elem.readAttribute('attachid').replace('cur_', '');
  		
  		//Debug.write( this.boxes );
  		
  		// Send request to remove upload
  		new Ajax.Request( ipb.vars['base_url'] + "app=core&module=attach&section=attach&do=attach_upload_remove&attach_rel_module=" +
  		 				   obj.options['attach_rel_module'] + " &attach_rel_id=" + obj.options['attach_rel_id'] + " &attach_post_key=" + 
  						  obj.options['attach_post_key'] + "&forum_id=" + obj.options['forum_id'] + "&attach_id=" + attachid + '&secure_key=' + ipb.vars['secure_hash'],
  						{
  							method: 'post',
  							evalJSON: 'force',
  							onSuccess: function(t)
  							{
  								if( Object.isUndefined( t.responseJSON ) ){ alert( ipb.lang['action_failed'] ); return; }
  								
  								if( t.responseJSON.msg == 'attach_removed' )
  								{
  									ipb.attach.uploaders[ elemid[1] ].removeUpload( elem.readAttribute('attachid'), elemid[2], t.responseJSON );
  								}
  								else
  								{
  									alert( ipb.lang['action_failed'] );
  									return;
  								}
  							}
  						});
  		
  		Event.stop(e);
  	},
  	
  	startedUploading: function(handler)
  	{
  		
  	},
  	
  	finishedUploading: function( attachid, fileindex )
  	{
  		if( !ipb.attach.uploaders[ attachid ] ){ return; }
  		if( !ipb.attach.uploaders[ attachid ].boxes[ fileindex ] ){ return; }
  		
  		var row = ipb.attach.uploaders[ attachid ].boxes[ fileindex ];
  		
  		if( $( row ) )
  		{
  			var link = $( row ).down('.add_to_post');
  			if( $(link) )
  			{
  				$( link ).writeAttribute( 'fileindex', fileindex ).writeAttribute( 'attachid', attachid );
  				$( link ).observe('click', ipb.attach.insertIntoPost );
  			}
  		}		
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Fetches an uploader
  	 * 
  	 * @param	{int}	id		ID of uploader to get
  	 * @return	mixed			Object if exists, false if not
  	*/
  	getUploader: function( id )
  	{
  		if( ipb.attach.uploaders[ id ] )
  		{
  			return ipb.attach.uploaders[ id ];
  		}
  		else
  		{
  			return false;
  		}
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Returns a human-readable string for errors
  	 * 
  	 * @param	{string}	msg		The msg code
  	 * @return	{string}			The human-readable message
  	*/
  	_determineServerError: function( msg )
  	{
  		if( msg.blank() ){ return ipb.lang['silly_server']; }
  		
  		if( !Object.isUndefined( ipb.lang[ msg ] ) )
  		{
  			return ipb.lang[ msg ];
  		}
  		else
  		{
  			return ipb.lang['silly_server'];
  		}
  	},
  	
  	_jsonPass: function( id, json )
  	{
  		if( ipb.attach._doneJson ){ return; }
  
  		ipb.attach.uploaders[ ipb.attach.optionsCache.id ].json = json;
  		ipb.attach.uploaders[ ipb.attach.optionsCache.id ].isReady();
  		
  		// #33731: Opera would constantly call this function, causing the attachment
  		// list to break. As a workaround, we set this to true so that it only
  		// runs once.
  		// Sigh... this caused a regression.
  		//ipb.attach._doneJson = true;
  		
  		Debug.write( "ips.attach.js: Got json back from iframe id " + id );
  		
  	},
  	
  	/**
  	* Builds boxes
  	*/
  	_buildBoxes: function( currentItems )
  	{
  		for( var i in currentItems )
  		{
  			Debug.write( "Templating item: " + currentItems[i][1] );
  			
  			id    = i;
  			index = 'cur_' + currentItems[i][0];
  			name  = currentItems[i][1];
  			size  = currentItems[i][2];
  			temp = uploader.template.gsub(/\[id\]/, uploader.id + '_' + index).gsub(/\[name\]/, name);
  
  			$( uploader.wrapper ).insert( temp );
  
  			$( 'ali_' + uploader.id + '_' + index ).select('.progress_bar')[0].hide();
  
  			new Effect.Appear( $( 'ali_' + uploader.id + '_' + index ), { duration: 0.3 } );
  			
  			$( 'ali_' + uploader.id + '_' + index ).select('.info')[0].update( ipb.global.convertSize( size ) );
  			// Add attachID
  			$( 'ali_' + uploader.id + '_' + index ).writeAttribute( 'attachid', index );
  			// Add event handlers
  			$( 'ali_' + uploader.id + '_' + index ).select('.delete')[0].observe( 'click', ipb.attach.removeUpload );
  			
  			// Remove old statuses
  			['complete', 'in_progress', 'error'].each( function( cName ){ $( 'ali_' + uploader.id + '_' + index ).removeClassName( cName ); }.bind( uploader ) );
  
  			$( 'ali_' + uploader.id + '_' + index ).addClassName( 'complete' );
  			
  			if( currentItems[ i ][ 3 ] == 1 )
  			{
  				tmp = currentItems[ i ];
  
  				var width = tmp[5];
  				var height = tmp[6];
  
  				if( ( tmp[5] && tmp[5] > 30 ) )
  				{
  					width = 30;
  					factor = ( 30 / tmp[5] );
  					height = tmp[6] * factor;
  				}
  
  				if( ( tmp[6] && tmp[6] > 30 ) )
  				{
  					height = 30;
  					factor = ( 30 / tmp[5] );
  					width = tmp[5] * factor;
  				}
  
  				thumb = new Element('img', { src: ipb.vars['upload_url'] + '/' + tmp[4], 'width': width, 'height': height, 'class': 'thumb_img' } ).hide();
  
  				$( 'ali_' + uploader.id + '_' + index ).select('.img_holder')[0].insert( thumb );
  				new Effect.Appear( $( thumb ), { duration: 0.4 } );
  			}
  			
  			uploader.boxes[ index ] = 'ali_' + uploader.id + '_' + index;
  			
  			// Fire finishedUpload
  			ipb.attach.finishedUploading( uploader.id, index );
  		}
  	},
  	
  	insertIntoPost: function(e)
  	{
  		Event.stop(e);
  		var elem = Event.element(e);
  		
  		if( !elem.hasClassName('add_to_post') )
  		{
  			elem = Event.findElement(e, 'add_to_post');
  		}
  		
  		elem = elem.up('.attach_row');
  		
  		var fileindex = elem.readAttribute('attachid');
  		var filename = elem.down('.attach_name').innerHTML;
  		
  		fileindex = fileindex.toString().replace('cur_', '');
  		
  		if( fileindex && filename )
  		{
  			ipb.editorInsert( "[attachment=" + fileindex + ":" + filename.replace( /\[/, '' ).replace( /\]/, '' ) + "]" );
  		}
  		else if( fileindex )
  		{
  			ipb.editorInsert( "[attachment=" + fileindex + "]" );
  		}
  	}	
  };
  ipb.attach.init();
  
  //==============================================================
  
  _attach.prototype.attachTraditional = Class.create({
  	options: [],
  	boxes: [],
  	
  	initialize: function( id, options, wrapper, template )
  	{
  		this.id = id;
  		this.wrapper = wrapper;
  		this.template = template;
  		this.options = options;
  		
  		this.options.prefix = ( typeof( this.options.prefix ) != 'undefined' ) ? this.options.prefix : '';
  		
  		/* Build iframe */
  		this.iframe = new Element('iframe', { 	id: 'iframeAttach_' + this.options['attach_rel_id'],
  		 										name: 'iframeAttach_' + this.options['attach_rel_id'],
  												scrolling: 'no',
  												frameBorder: 'no',
  												border: '0',
  												className: 'clearfix',
  												allowTransparency: true,
  												src: this.options.upload_url,
  												tabindex: '1'
  											}).setStyle({
  												width: '500px',
  												zIndex: 30000,
  												height: '25px',
  												overflow: 'hidden',
  												backgroundColor: 'transparent'
  											});
  											
  		$('add_files_' + this.id ).insert( { before: this.iframe } );
  		$( wrapper ).addClassName('traditional');
  		
  		Debug.write("Init traditional");
  		
  		$('add_files_' + this.id ).observe('click', this.processUpload.bindAsEventListener( this ) );		
  	},
  	
  	_createFrame: function()
  	{
  		
  	},
  	
  	removeUpload: function( attachid, fileindex, fileinfo )
  	{
  		/*Debug.write( fileindex );
  		Debug.dir( this.boxes );*/
  		
  		if( attachid.startsWith('cur_') ){
  			fileindex = 'cur_' + fileindex;
  		}
  		
  		// Remove box
  		new Effect.Fade( $( this.boxes[ fileindex ] ), { duration: 0.4 } );
  		
  		// Remove reference
  		this.boxes[ fileindex ] = null;
  		
  		// Update totals
  		if( $('space_info_' + this.id ) && ipb.lang['used_space'] )
  		{
  			$('space_info_' + this.id ).update( ipb.lang['used_space'].gsub(/\[used\]/, fileinfo['attach_stats']['space_used_human']).gsub(/\[total\]/, fileinfo['attach_stats']['total_space_allowed_human']).gsub(/\[left\]/, fileinfo['attach_stats']['space_left_human']) );
  		}
  		
  		Debug.write( "ips.attach.js: (ID " + this.id + ", removeUpload) Attach ID: " + attachid + ", File index: " + fileindex );
  	},
  	
  	/**
  	* Processes upload
  	*/
  	processUpload: function( e )
  	{
  		$('attach_error_box').hide();
  		var iFrameBox  = window.frames[ 'iframeAttach_' + this.options['attach_rel_id'] ].document.getElementById('iframeUploadBox');
  		var iFrameForm = window.frames[ 'iframeAttach_' + this.options['attach_rel_id'] ].document.getElementById('iframeUploadForm');
  		var box        = $('attach_' + this.options['attach_rel_id'] );
  		
  		window.frames[ 'iframeAttach_' + this.options['attach_rel_id'] ].document.getElementById('waitImg').style.display = 'inline-block';
  		
  		Debug.write( ipb.vars['base_url'] + "app=core&module=attach&section=attach&do=attachUploadiFrame&attach_rel_module=" +
  		 				   	this.options['attach_rel_module'] + "&attach_rel_id=" + this.options['attach_rel_id'] + "&attach_post_key=" + 
  						  	this.options['attach_post_key'] + "&forum_id=" + this.options['forum_id'] + '&fetch_all=1' );
  		iFrameForm.action = ipb.vars['base_url'] + "app=core&module=attach&section=attach&do=attachUploadiFrame&attach_rel_module=" +
  		 				   	this.options['attach_rel_module'] + "&attach_rel_id=" + this.options['attach_rel_id'] + "&attach_post_key=" + 
  						  	this.options['attach_post_key'] + "&forum_id=" + this.options['forum_id'] + '&fetch_all=1';
  		
  		iFrameForm.submit();
  	},
  	
  	/**
  	* Iframe is ready
  	*/
  	isReady: function()
  	{
  		if ( this.json )
  		{
  			if ( this.json['is_error'] )
  			{
  				$('attach_error_box').update( ipb.lang['error'] + " <strong>" + ipb.attach._determineServerError( this.json['msg'] ) + "</strong>" );
  				$('attach_error_box').show();
  			}
  			
  			if ( this.json['current_items'] )
  			{
  				$( this.wrapper ).update();
  				ipb.attach._buildBoxes( this.json['current_items'] );
  			}
  			
  			if ( typeof( this.json['attach_rel_id']) !='undefined' )
  			{
  				if( $('space_info_attach_' + this.json['attach_rel_id'] ) )
  				{
  					$('space_info_attach_' + this.json['attach_rel_id'] ).update( ipb.lang['used_space'].gsub(/\[used\]/, this.json['attach_stats']['space_used_human']).gsub(/\[total\]/, this.json['attach_stats']['total_space_allowed_human']).gsub(/\[left\]/, this.json['attach_stats']['space_left_human']) );
  				}
  			}
  		}
  		
  		Debug.write( "ips.attach.js: iFrame is ready" );
  	},
  	
  	_setJSON: function( json )
  	{
  		Debug.write( "ips.attach.js: Got JSON from the iFrame" );
  	}
  });
  
  //==============================================================
  
  _attach.prototype.attachSWF = Class.create({
  	options: [],
  	boxes: [], /* simple array to hold file index => list id relationship, because im lazy :) */
  	
  	initialize: function( id, options, wrapper, template )
  	{
  		this.id = id;
  		this.wrapper = wrapper;
  		this.template = template;
  
  		this.options = Object.extend({
  			upload_url: 				'',
  			file_post_name: 			'FILE_UPLOAD',
  			file_types:					'*.*',
  			file_types_description:		ipb.lang['att_select_files'],
  			file_size_limit: 			"10 MB",
  			file_upload_limit: 			0,
  			file_queue_limit: 			10,
  			flash_color: 				ipb.vars['swf_bgcolor'] || '#FFFFFF',
  			custom_settings: 			{},
  			post_params: 				{ 's': ipb.vars['session_id'] }
  		}, arguments[1] || {});
  		
  		if( this.options.upload_url.blank() ){ Debug.error( "(ID " + id + ") No upload URL" ); return false; }
  		
  		// Update the text of the button to indicate more than one can be attached
  		try {
  			$('add_files_' + this.id).value = ipb.lang['click_to_attach'];
  		} catch(err) {
  			Debug.write( err );
  		}
  		
  		// Set up SWFU
  		try {
  			var swfu;
  			
  				var settings = {
  					upload_url: 			this.options.upload_url,
  					flash_url: 				ipb.vars['swfupload_swf'],
  					file_post_name: 		this.options.file_post_name,
  					file_types: 			this.options.file_types,
  					file_types_description: this.options.file_types_description,
  					file_size_limit: 		this.options.file_size_limit,
  					file_upload_limit:  	this.options.file_upload_limit,
  					file_queue_limit: 		this.options.file_queue_limit,
  					custom_settings: 		this.options.custom_settings,
  					post_params: 			this.options.post_params,
  					debug: 					ipb.vars['swfupload_debug'],
  					
  					// ---- BUTTON SETTINGS ----
  					button_placeholder_id: 			'buttonPlaceholder',
  					button_width: 					$('add_files_' + this.id).getWidth(),
  					button_height: 					30,
  					button_window_mode: 			SWFUpload.WINDOW_MODE.TRANSPARENT,
  					button_cursor: 					SWFUpload.CURSOR.HAND,
  				
  					// ---- EVENTS ---- 
  					upload_error_handler: 			this._uploadError.bind(this),
  					upload_start_handler: 			this._uploadStart.bind(this),
  					upload_success_handler: 		this._uploadSuccess.bind(this),
  					upload_complete_handler: 		this._uploadComplete.bind(this),
  					upload_progress_handler: 		this._uploadProgress.bind(this),
  					file_dialog_complete_handler: 	this._fileDialogComplete.bind(this),
  					file_queue_error_handler: 		this._fileQueueError.bind(this),
  					queue_complete_handler: 		this._queueComplete.bind(this),
  					file_queued_handler: 			this._fileQueued.bind(this)
  				};
  				
  				swfu = new SWFUpload( settings );
  			
  			// Add events
  			/*if( $('add_files_' + this.id ) )
  			{
  				$('add_files_' + this.id ).observe('click', this.showFilesDialog.bindAsEventListener( this ) );
  			}*/
  			
  			this.obj = swfu;
  			
  			// Now we have to get existing files
  			var getExisting	=	ipb.vars['base_url'] + "app=core&module=attach&section=attach&do=attach_upload_show&attach_rel_module=" +
   				   				options['attach_rel_module'] + "&attach_rel_id=" + options['attach_rel_id'] + "&attach_post_key=" + 
  				  				options['attach_post_key'] + "&forum_id=" + options['forum_id'] + "&attach_id=" + id + '&secure_key=' + ipb.vars['secure_hash'] + '&fetch_all=1';
  
  			// Send request to get the uploads
  			new Ajax.Request( getExisting,
  							{
  								method: 'get',
  								evalJSON: 'force',
  								hideLoader: true,
  								onSuccess: function(t)
  								{
  									if( Object.isUndefined( t.responseJSON ) ){ alert( ipb.lang['action_failed'] ); return; }
  
  									if( t.responseJSON.current_items )
  									{
  										ipb.attach._buildBoxes( t.responseJSON.current_items );
  									}
  								}
  							});
  
  			this.obj.onmouseover	= $('SWFUpload_0').focus();
  			$('SWFUpload_0').writeAttribute("tabindex", "-1");
  			Debug.write( "ips.attach.js: (ID " + this.id + ") Created uploader");
  			return true;
  		}
  		catch(e)
  		{
  			Debug.error( "ips.attach.js: (ID " + this.id + ") " + e );
  			return false;
  		}
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Removes an upload from the list (note: actual removing of file is done in the wrapper object
  	 * 
  	 * @param	{int}	attachid		Server ID of the attachment
  	 * @param	{int}	fileindex		The file index that SWFU is using
  	*/
  	removeUpload: function( attachid, fileindex, fileinfo )
  	{
  		if( attachid.startsWith('cur_') ){
  			fileindex = 'cur_' + fileindex;
  		}
  		
  		// Remove box
  		new Effect.Fade( $( this.boxes[ fileindex ] ), { duration: 0.4 } );
  		
  		// Remove reference
  		this.boxes[ fileindex ] = null;
  		
  		if( $('space_info_' + this.id ) && ipb.lang['used_space'] )
  		{
  			$('space_info_' + this.id ).update( ipb.lang['used_space'].gsub(/\[used\]/, fileinfo['attach_stats']['space_used_human']).gsub(/\[total\]/, fileinfo['attach_stats']['total_space_allowed_human']).gsub(/\[left\]/, fileinfo['attach_stats']['space_left_human']) );
  		}
  		
  		Debug.write( "ips.attach.js: (ID " + this.id + ", removeUpload) Attach ID: " + attachid + ", File index: " + fileindex );
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Updates the info string for an upload
  	 * 
  	 * @param	{object}	file		The file object from SWFU
  	 * @param	{string}	msg			The message to set
  	*/
  	_updateInfo: function( file, msg )
  	{
  		$( this.boxes[ file ] ).select('.info')[0].update( msg );
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Sets a CSS class on the box depending on status
  	 * 
  	 * @param	{object}	file		The file object from SWFU
  	 * @param	{string}	type		Status to set
  	*/
  	_setStatus: function( file, type )
  	{
  		// Remove old statuses
  		['complete', 'in_progress', 'error'].each( function( cName ){ $( this.boxes[ file ] ).removeClassName( cName ); }.bind( this ) );
  		
  		$( this.boxes[ file ] ).addClassName( type );
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Returns a human-readable string for errors
  	 * 
  	 * @param	{string}	msg		The msg code
  	 * @return	{string}			The human-readable message
  	*/
  	_determineServerError: function( msg )
  	{
  		if( msg.blank() ){ return ipb.lang['silly_server']; }
  		
  		if( !Object.isUndefined( ipb.lang[ msg ] ) )
  		{
  			return ipb.lang[ msg ];
  		}
  		else
  		{
  			return ipb.lang['silly_server'];
  		}
  		
  		/*switch( msg )
  		{
  			case 'upload_no_file':
  				return ipb.lang['upload_no_file'];
  				break;
  			case 'invalid_mime_type':
  				return ipb.lang['invalid_mime_type'];
  				break;
  			case 'upload_too_big':
  				return ipb.lang['upload_too_big'];
  				break;
  			case 'upload_failed':
  				return ipb.lang['upload_failed'];
  				break;
  			default:
  				return ipb.lang['silly_server'] + " " + msg;
  				break;
  		}*/
  		
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Builds the list row for each upload
  	 * 
  	 * @param	{object}	file	The file object passed from SWFU
  	*/
  	_buildBox: function( file )
  	{
  		temp = this.template.gsub(/\[id\]/, this.id + '_' + file.index).gsub(/\[name\]/, file.name);
  		this.boxes[ file.index ] = 'ali_' + this.id + '_' + file.index;
  		
  		$( this.wrapper ).insert( temp );
  		
  		new Effect.Appear( $( this.boxes[ file.index ] ), { duration: 0.3 } );
  		this._updateInfo( file.index, ipb.global.convertSize( file.size ) + "bytes" );
  	},
  	
  	_queueComplete: function( numFiles )
  	{
  		Debug.write( "ips.attach.js: (ID " + this.id + ", " + numFiles + " finished uploading");
  	},
  	
  	_fileQueued: function( file )
  	{
  		this._buildBox( file );
  		$( this.boxes[ file.index ] ).addClassName('in_progress');
  		this._updateInfo( file.index, ipb.lang['pending'] );
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Event handler for fileQueueError
  	 * 
  	 * @param	{object}	file			The file object
  	 * @param	{int}		errorCode		Error code
  	 * @param	{string}	message			Message from SWFUpload
  	*/
  	_fileQueueError: function( file, errorCode, message )
  	{
  		var msg;
  		
  		try {
  			if( errorCode === SWFUpload.QUEUE_ERROR.QUEUE_LIMIT_EXCEEDED ){
  				alert( ipb.lang['upload_queue'] + ' ' + message );
  				return false;
  			}
  		
  			switch (errorCode) {
  				case SWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT:
  					msg = ipb.lang['upload_too_big'];
  					break;
  				case SWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE:
  					msg = ipb.lang['upload_no_file'];
  					break;
  				case SWFUpload.QUEUE_ERROR.INVALID_FILETYPE:
  					msg = ipb.lang['invalid_mime_type'];
  					break;
  				default:
  					if( file !== null ) {
  						msg = ipb.lang['upload_failed'] + " " + errorCode;
  					}
  					break;
  			}
  
  			// Have to manually build box
  			this._buildBox( file );
  			this._setStatus( file.index, 'error' );
  			this._updateInfo( file.index, ipb.lang['upload_skipped'] + " (" + msg + ")" );
  			$( this.boxes[ file.index ] ).select('.progress_bar')[0].hide();
  			$( this.boxes[ file.index ] ).select('.links')[0].hide();
  
  			
  			Debug.write( "ips.attach.js: (ID " + this.id + ", fileQueueError) " + errorCode + ": " + message );
  		}
  		catch( err )
  		{
  			Debug.write( "ips.attach.js: (ID " + this.id + ", fileQueueError) " + errorCode + ": " + message );
  		}
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Event handler for uploadError
  	 * 
  	 * @param	{object}	file			The file object
  	 * @param	{int}		errorCode		The error code returned
  	 * @param	{string}	message			The message returned
  	*/
  	_uploadError: function( file, errorCode, message )
  	{
  		var msg;
  
  		switch( errorCode )
  		{
  			case SWFUpload.UPLOAD_ERROR.HTTP_ERROR:
  				msg = ipb.lang['error'] + message;
  				break;
  			case SWFUpload.UPLOAD_ERROR.UPLOAD_FAILED:
  				msg = message;
  				break;
  			case SWFUpload.UPLOAD_ERROR.IO_ERROR:
  				msg = ipb.lang['error'] + " IO";
  				break;
  			case SWFUpload.UPLOAD_ERROR.SECURITY_ERROR:
  				msg = ipb.lang['error_security'];
  				break;
  			case SWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED:
  				msg = ipb.lang['upload_limit_hit'];
  				break;
  			case SWFUpload.UPLOAD_ERROR.FILE_VALIDATION_FAILED:
  				msg = ipb.lang['invalid_mime_type'];
  				break;
  			/*case SWFUpload.UPLOAD_ERROR.FILE_CANCELLED:
  				// If there aren't any files left (they were all cancelled) disable the cancel button
  				if (this.getStats().files_queued === 0) {
  					document.getElementById(this.customSettings.cancelButtonId).disabled = true;
  				}
  				progress.setStatus("Cancelled");
  				progress.setCancelled();
  				break;
  			case SWFUpload.UPLOAD_ERROR.UPLOAD_STOPPED:
  				progress.setStatus("Stopped");
  				break;*/
  			default:
  				msg = ipb.lang['error'] + ": " + errorCode;
  				break;
  		}
  		
  		this._setStatus( file.index, 'error' );
  		this._updateInfo( file.index, ipb.lang['upload_skipped'] + " (" + msg + ")" );
  		
  		Debug.write( "ips.attach.js: (ID " + this.id + ", uploadError) " + errorCode + ": " + message );
  		return false;
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Event handler for uploadStart
  	 * 
  	 * @param	{object}	file			The file object
  	*/
  	_uploadStart: function( file )
  	{
  		ipb.attach.startedUploading( this.id );
  		
  		Debug.write( "ips.attach.js: (ID " + this.id + ", uploadStart) " );
  		/*this._buildBox( file );
  		
  		$( this.boxes[ file.index ] ).addClassName('in_progress');
  		
  		this._updateInfo( file.index, ipb.lang['uploading'] );*/
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Event handler for uploadSuccess
  	 * 
  	 * @param	{object}	file			The file object
  	 * @param	{string}	serverData		Data from the server (should be JSON)
  	*/
  	_uploadSuccess: function( file, serverData )
  	{
  		/* Mark W's fix for invisible header chars */
  		while ( serverData.substr( 0, 1 ) != '{' )
  		{
  			serverData = serverData.substr( 1 );
  		}
  
  		if( !serverData.isJSON() ){ this._setStatus( file.index, 'error' ); this._updateInfo( file.index, ipb.lang['silly_server'] ); }
  		returnedObj = serverData.evalJSON();
  		
  		if( Object.isUndefined( returnedObj ) ){ this._setStatus( file.index, 'error' ); this._updateInfo( file.index, ipb.lang['silly_server'] ); }
  		
  		// Error?
  		if( returnedObj.is_error == 1 )
  		{
  			msg = this._determineServerError( returnedObj.msg );
  			this._setStatus( file.index, 'error' );
  			this._updateInfo( file.index, msg );
  			return false;
  		}
  			
  		if( $('space_info_' + this.id ) && ipb.lang['used_space'] )
  		{
  			//$('space_info_' + this.id ).update( "Used <strong>" + returnedObj.attach_stats.space_used_human + "</strong> of <strong>" + returnedObj.attach_stats.total_space_allowed_human + "</strong>" );
  			$('space_info_' + this.id ).update( ipb.lang['used_space'].gsub(/\[used\]/, returnedObj.attach_stats.space_used_human).gsub(/\[total\]/, returnedObj.attach_stats.total_space_allowed_human).gsub(/\[left\]/, returnedObj.attach_stats.space_left_human) );
  		}
  		
  		// IMAGE RESIZING
  		if( returnedObj.current_items[ returnedObj.insert_id ][ 3 ] == 1 )
  		{
  			tmp = returnedObj.current_items[ returnedObj.insert_id ];
  			
  			var width = tmp[5];
  			var height = tmp[6];
  			
  			if( ( tmp[5] && tmp[5] > 30 ) )
  			{
  				width = 30;
  				factor = ( 30 / tmp[5] );
  				height = tmp[6] * factor;
  			}
  			
  			if( ( tmp[6] && tmp[6] > 30 ) )
  			{
  				height = 30;
  				factor = ( 30 / tmp[5] );
  				width = tmp[5] * factor;
  			}
  			
  			thumb = new Element('img', { src: ipb.vars['upload_url'] + '/' + tmp[4], 'width': width, 'height': height, 'class': 'thumb_img' } ).hide();
  			
  			$( this.boxes[ file.index ] ).select('.img_holder')[0].insert( thumb );
  			new Effect.Appear( $( thumb ), { duration: 0.4 } );
  		}
  		
  		// SET STATUS & INFO
  		this._setStatus( file.index, 'complete' );
  		this._updateInfo( file.index, ipb.lang['upload_done'].gsub( /\[total\]/, ipb.global.convertSize( file.size ) ) );
  		
  		// Write attachID to the object for easy retreival later
  		$( this.boxes[ file.index ] ).writeAttribute( 'attachid', returnedObj.insert_id );
  		
  		// Add event handlers
  		$( this.boxes[ file.index ] ).select('.delete')[0].observe( 'click', ipb.attach.removeUpload );
  		
  		ipb.attach.finishedUploading( this.id, file.index );
  		
  		Debug.write( "ips.attach.js: (ID " + this.id + ", uploadSuccess) " + serverData );
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Event handler for uploadComplete
  	 * 
  	 * @param	{object}	file			The file object
  	*/
  	_uploadComplete: function( file )
  	{
  		ipb.attach.finishedUploading( this.id );
  		
  		progress_bar = $( this.boxes[ file.index ] ).select('.progress_bar span')[0];
  		progress_bar.setStyle( "width: 100%" );
  		new Effect.Fade( $( this.boxes[ file.index ] ).select('.progress_bar')[0], { duration: 0.6 } );
  				
  		Debug.write( "ips.attach.js: (ID " + this.id + ", uploadComplete)" );
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Event handler for uploadProgress
  	 * 
  	 * @param	{object}	file			The file object
  	 * @param	{int}		bytesLoaded		Number of bytes loaded so far
  	 * @param	{int}		bytesTotal		Total size of file
  	*/
  	_uploadProgress: function( file, bytesLoaded, bytesTotal)
  	{
  		var percent = Math.ceil((bytesLoaded / bytesTotal) * 100);
  		
  		progress_bar = $( this.boxes[ file.index ] ).select('.progress_bar span')[0];
  		progress_bar.setStyle( "width: " + percent + "%" ).update( percent + "%" );
  		
  		this._setStatus( file.index, 'in_progress' );
  		this._updateInfo( file.index, ipb.lang['upload_progress'].gsub( /\[done\]/, ipb.global.convertSize( bytesLoaded ) ).gsub( /\[total\]/, ipb.global.convertSize( bytesTotal ) ) );
  		
  		Debug.write( "ips.attach.js: (ID " + this.id + ", uploadProgress)" );
  	},
  	
  	/* ------------------------------ */
  	/**
  	 * Event handler for fileDialogComplete (called when used finishes selecting files
  	 * 
  	 * @param	{int}		number			Number of files selected
  	 * @param	{int}		queued			Number in the queue
  	*/
  	_fileDialogComplete: function( number, queued )
  	{
  		Debug.write( "ips.attach.js: (ID " + this.id + ", fileDialogComplete) Number: " + number + ", Queued: " + queued );
  		this.obj.startUpload();
  	}
  	
  	/* ------------------------------ */
  	/**
  	 * Event handler for the Add Files button
  	 * 
  	 * @param	{event}		e		The event
  	*/
  	/*showFilesDialog: function(e)
  	{
  		Debug.write( "ips.attach.js: Adding files" );
  		this.obj.selectFile();
  	}*/
  });
  
  /*
  Copyright (c) 2007, James Auldridge
  All rights reserved.
  Code licensed under the BSD License:
    http://www.jaaulde.com/license.txt
  
  Version 1.0
  
  Change Log:
  	* 09 JAN 07 - Version 1.0 written
  
  */
  //Preparing namespace
  var jimAuld = window.jimAuld || {};
  jimAuld.utils = jimAuld.utils || {};
  jimAuld.utils.flashsniffer = {
  	lastMajorRelease: 10,
  	installed: false,
  	version: null,
  	detect: function()
  	{
  		var fp,fpd,fAX;
  		if (navigator.plugins && navigator.plugins.length)
  		{
  			fp = navigator.plugins["Shockwave Flash"];
  			if (fp)
  			{
  				jimAuld.utils.flashsniffer.installed = true;
  				if (fp.description)
  				{
  					fpd = fp.description;
  					jimAuld.utils.flashsniffer.version = fpd.substr( fpd.indexOf('.')-2, 2 ).strip();
  					Debug.write( jimAuld.utils.flashsniffer.version );
  				}
  			}
  			else
  			{
  				jimAuld.utils.flashsniffer.installed = false;
  			}
  			if (navigator.plugins["Shockwave Flash 2.0"]){
  				jimAuld.utils.flashsniffer.installed = true;
  				jimAuld.utils.flashsniffer.version = 2;
  			}
  		}
  		else if (navigator.mimeTypes && navigator.mimeTypes.length)
  		{
  			fp = navigator.mimeTypes['application/x-shockwave-flash'];
  			if (fp && fp.enabledPlugin)
  			{
  				jimAuld.utils.flashsniffer.installed = true;
  			}
  			else
  			{
  				jimAuld.utils.flashsniffer.installed = false;
  			}
  		}
  		else
  		{
  			for(var i=jimAuld.utils.flashsniffer.lastMajorRelease;i>=2;i--)
  			{
  				try
  				{
  					fAX = new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+i);
  					jimAuld.utils.flashsniffer.installed = true;
  					jimAuld.utils.flashsniffer.version = i;
  					break;
  				}
  				catch(e)
  				{
  				}
  			}
  			if(jimAuld.utils.flashsniffer.installed == null){
  				try
  				{
  					fAX = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
  					jimAuld.utils.flashsniffer.installed = true;
  					jimAuld.utils.flashsniffer.version = 2;
  				}
  				catch(e)
  				{
  				}
  			}
  			if(jimAuld.utils.flashsniffer.installed == null)
  			{
  				jimAuld.utils.flashsniffer.installed = false;
  			}
  			fAX = null;
  		}
  		
  	},
  	isVersion: function(exactVersion)
  	{
  		return (jimAuld.utils.flashsniffer.version!=null && jimAuld.utils.flashsniffer.version==exactVersion);
  	},
  	isLatestVersion: function()
  	{
  		return (jimAuld.utils.flashsniffer.version!=null && jimAuld.utils.flashsniffer.version==jimAuld.utils.flashsniffer.lastMajorRelease);
  	},
  	meetsMinVersion: function(minVersion)
  	{
  		return (jimAuld.utils.flashsniffer.version!=null && jimAuld.utils.flashsniffer.version>=minVersion);
  	}
  };
  jimAuld.utils.flashsniffer.detect();
  
  var IPS_UPLOADER_LOADED = true;
  
  