Location: PHPKode > scripts > PHP Bulk SMS > php-bulk-sms/index.htm
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<script type="text/javascript">
var version = {major: 1, minor: 2, revision: 32, date: new Date("Aug 18, 2005"), extensions: {}};
</script>
<!--
TiddlyWiki 1.2.32 by Jeremy Ruston, (jeremy [at] osmosoft [dot] com)

Published under a BSD open source license
Incorporating improvements by Isao Sonobe, http://www-gauge.scphys.kyoto-u.ac.jp/~sonobe/OgreKit/OgreKitWiki.html

Copyright (c) Osmosoft Limited, 14 April 2005

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.

Neither the name of the Osmosoft Limited nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<title>php-bulksms</title>
<script type="text/javascript">

// ---------------------------------------------------------------------------------
// Configuration repository
// ---------------------------------------------------------------------------------

var config = {
	// Options that can be set in the options panel and/or cookies
	options: {
		chkRegExpSearch: false,
		chkCaseSensitiveSearch: false,
		chkAnimate: true,
		txtUserName: "YourName",
		chkSaveBackups: true,
		chkAutoSave: false,
		chkGenerateAnRssFeed: false,
		chkSaveEmptyTemplate: false,
		chkOpenInNewWindow: true,
		chkToggleLinks: false,
		chkHttpReadOnly: false,
		txtMainTab: "tabTimeline",
		txtMoreTab: "moreTabAll"
		},
	// Hashmap of notification functions to be called when certain tiddlers are changed or deleted
	notifyNamedTiddlers: {
		SiteTitle: refreshTitle,
		SiteSubtitle: refreshSubtitle,
		SideBarOptions: refreshSidebar,
		StyleSheet: refreshStyles
		},
	// List of notification functions to be called when any tiddler is changed or deleted
	notifyTiddlers: [
		refreshMenu,
		refreshStory,
		refreshTabs
		],
	// Shadow tiddlers for emergencies
	shadowTiddlers: {
		SideBarOptions: "<<search>><<closeAll>><<permaview>><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel options 'Change TiddlyWiki advanced options'>>",
		OptionsPanel: "These InterfaceOptions for customising TiddlyWiki are saved in your browser\n\nYour username for signing your edits. Write it as a WikiWord (eg JoeBloggs)\n\n<<option txtUserName>>\n<<option chkSaveBackups>> SaveBackups\n<<option chkAutoSave>> AutoSave\n<<option chkGenerateAnRssFeed>> GenerateAnRssFeed\n<<option chkRegExpSearch>> RegExpSearch\n<<option chkCaseSensitiveSearch>> CaseSensitiveSearch\n<<option chkAnimate>> EnableAnimations\n\nSee AdvancedOptions",
		AdvancedOptions: "<<option chkOpenInNewWindow>> OpenLinksInNewWindow\n<<option chkSaveEmptyTemplate>> SaveEmptyTemplate\n<<option chkToggleLinks>> Clicking on links to tiddlers that are already open causes them to close\n^^(override with Control or other modifier key)^^\n<<option chkHttpReadOnly>> HideEditingFeatures when viewed over HTTP",
		SideBarTabs: "<<tabs txtMainTab Timeline Timeline TabTimeline All 'All tiddlers' TabAll Tags 'All tags' TabTags More 'More lists' TabMore>>",
		TabTimeline: "<<timeline>>",
		TabAll: "<<list all>>",
		TabTags: "<<allTags>>",
		TabMore: "<<tabs txtMoreTab Missing 'Missing tiddlers' TabMoreMissing Orphans 'Orphaned tiddlers' TabMoreOrphans>>",
		TabMoreMissing: "<<list missing>>",
		TabMoreOrphans: "<<list orphans>>"
		},
	// Miscellaneous options
	numRssItems: 20, // Number of items in the RSS feed
	animFast: 0.12, // Speed for animations (lower == slower)
	animSlow: 0.01, // Speed for EasterEgg animations
	// Messages
	messages: {
		customConfigError: "Error in customConfig - %0",
		savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
		subtitleUnknown: "(unknown)",
		undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
		externalLinkTooltip: "External link to %0",
		noTags: "There are no tagged tiddlers",
		notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
		cantSaveError: "It's not possible to save changes using this browser. Use FireFox if you can",
		invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
		backupSaved: "Backup saved",
		backupFailed: "Failed to save backup file",
		rssSaved: "RSS feed saved",
		rssFailed: "Failed to save RSS feed file",
		emptySaved: "Empty template saved",
		emptyFailed: "Failed to save empty template file",
		mainSaved: "Main TiddlyWiki file saved",
		mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
		macroError: "Error executing macro '%0'",
		overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
		unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
		dates: {
			months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"],
			days: ["Sunday", "Monday","Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
			}
		},
	views: {
		wikified: {
			tag: {labelNoTags: "no tags", labelTags: "tags: ", tooltip: "Show tiddlers tagged with '%0'", openAllText: "Open all", openAllTooltip: "Open all of these tiddlers", popupNone: "No other tiddlers tagged with '%0'"},
			toolbarClose: {text: "close", tooltip: "Close this tiddler"},
			toolbarEdit: {text: "edit", tooltip: "Edit this tiddler"},
			toolbarPermalink: {text: "permalink", tooltip: "Permalink for this tiddler"},
			toolbarReferences: {text: "references", tooltip: "Show tiddlers that link to this one", popupNone: "No references"},
			defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it"
			},
		editor: {
			tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
			tagChooser: {text: "tags", tooltip: "Choose existing tags to add to this tiddler", popupNone: "There are no tags defined", tagTooltip: "Add the tag '%0'"},
			toolbarDone: {text: "done", tooltip: "Save changes to this tiddler"},
			toolbarCancel: {text: "cancel", tooltip: "Undo changes to this tiddler"},
			toolbarDelete: {text: "delete", tooltip: "Delete this tiddler"},
			defaultText: "Type the text for '%0'"
			}
		},
	macros: { // Each has a 'handler' member that is inserted later
		today: {},
		version: {},
		search: {label: "search", prompt: "Search this TiddlyWiki", sizeTextbox: 15, successMsg: "%0 tiddlers found matching %1", failureMsg: "No tiddlers found matching %0"},
		tiddler: {},
		tag: {},
		timeline: {dateFormat: "DD MMM YYYY"},
		allTags: {tooltip: "Show tiddlers tagged with '%0'", noTags: "There are no tagged tiddlers"},
		list: {
			all: {prompt: "All tiddlers in alphabetical order"},
			missing: {prompt: "Tiddlers that have links to them but are not defined"},
			orphans: {prompt: "Tiddlers that are not linked to from any other tiddlers"}
			},
		closeAll: {label: "close all", prompt: "Close all displayed tiddlers (except any that are being edited)"},
		permaview: {label: "permaview", prompt: "Link to an URL that retrieves all the currently displayed tiddlers"},
		saveChanges: {label: "save changes", prompt: "Save all tiddlers to create a new TiddlyWiki"},
		slider: {},
		option: {},
		newTiddler: {label: "new tiddler", prompt: "Create a new tiddler", title: "New Tiddler"},
		newJournal: {label: "new journal", prompt: "Create a new tiddler from the current date and time"},
		sparkline: {},
		tabs: {}
		}
};

// ---------------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------------

// TiddlyWiki storage
var store = new TiddlyWiki();

// Animation engine
var anim = new Animator();

var readOnly = false;

// Starting up
function main()
{
	readOnly = (document.location.toString().substr(0,7) == "http://") ? config.options.chkHttpReadOnly : false;
	setupRegexp();
	saveTest();
	loadOptionsCookie();
	var s;
	for(s in config.notifyNamedTiddlers)
		store.addNotification(s,config.notifyNamedTiddlers[s]);
	for(s=0; s<config.notifyTiddlers.length; s++)
		store.addNotification(null,config.notifyTiddlers[s]);
	store.loadFromDiv("storeArea","store");
	loadSystemConfig();
	store.notifyAll();
	var start = store.getTiddlerText("DefaultTiddlers");
	if(window.location.hash)
		displayTiddlers(null,convertUTF8ToUnicode(decodeURI(window.location.hash.substr(1))),1,null,null);
	else if(start)
		displayTiddlers(null,start,1,null,null);
}

function saveTest()
{
	var saveTest = document.getElementById("saveTest");
	if(saveTest.hasChildNodes())
		alert(config.messages.savedSnapshotError);
	saveTest.appendChild(document.createTextNode("savetest"));
}

function loadSystemConfig()
{
	var configTiddlers = store.getTaggedTiddlers("systemConfig");
	for(var t=0; t<configTiddlers.length; t++)
		{
		var ex = processConfig(configTiddlers[t].text);
		if(ex)
			displayMessage(config.messages.customConfigError.format([ex]));
		}
}

// ---------------------------------------------------------------------------------
// Macro definitions
// ---------------------------------------------------------------------------------

config.macros.today.handler = function(place)
{
	createTiddlyElement(place,"span",null,null,(new Date()).toLocaleString());
}

config.macros.version.handler = function(place)
{
	createTiddlyElement(place,"span",null,null,version.major + "." + version.minor + "." + version.revision);
}

config.macros.list.handler = function(place,macroName,params)
{
	var type = params[0] ? params[0] : "all";
	var theList = document.createElement("ul");
	place.appendChild(theList);
	if(this[type].prompt)
		createTiddlyElement(theList,"li",null,"listTitle",this[type].prompt);
	var results;
	if(this[type].handler)
		results = this[type].handler(params);
	for (t = 0; t < results.length; t++)
		{
		theListItem = document.createElement("li")
		theList.appendChild(theListItem);
		if(typeof results[t] == "string")
			createTiddlyLink(theListItem,results[t],true);
		else
			createTiddlyLink(theListItem,results[t].title,true);
		}
}

config.macros.list.all.handler = function(params)
{
	return store.reverseLookup("tags","excludeLists",false,"title");
}

config.macros.list.missing.handler = function(params)
{
	return store.getMissingLinks();
}

config.macros.list.orphans.handler = function(params)
{
	return store.getOrphans();
}

config.macros.allTags.handler = function(place,macroName,params)
{
	var tags = store.getTags();
	var theDateList = createTiddlyElement(place,"ul",null,null,null);
	if(tags.length == 0)
		createTiddlyElement(theDateList,"li",null,"listTitle",this.noTags);
	for (t=0; t<tags.length; t++)
		{
		var theListItem =createTiddlyElement(theDateList,"li",null,null,null);
		var theTag = createTiddlyButton(theListItem,tags[t][0] + " (" + tags[t][1] + ")",this.tooltip.format([tags[t][0]]),onClickTag);
		theTag.setAttribute("tag",tags[t][0]);
		}
}

config.macros.timeline.handler = function(place,macroName,params)
{
	var tiddlers = store.reverseLookup("tags","excludeLists",false,"modified");
	var lastDay = "";
	for (t=tiddlers.length-1; t>=0; t--)
		{
		var tiddler = tiddlers[t];
		var theDay = tiddler.modified.convertToYYYYMMDDHHMM().substr(0,8);
		if(theDay != lastDay)
			{
			var theDateList = document.createElement("ul");
			place.appendChild(theDateList);
			createTiddlyElement(theDateList,"li",null,"listTitle",tiddler.modified.formatString(this.dateFormat));
			lastDay = theDay;
			}
		var theDateListItem = createTiddlyElement(theDateList,"li",null,"listLink",null);
		theDateListItem.appendChild(createTiddlyLink(place,tiddler.title,true));
		}
}

config.macros.search.handler = function(place,macroName,params)
{
	var lastSearchText = "";
	var searchTimeout = null;
	var doSearch = function(txt)
		{
		closeAllTiddlers();
		var matches = store.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch,"title","excludeSearch");
		for(var t=matches.length-1; t>=0; t--)
			displayTiddler(null,matches[t].title,0,txt.value,config.options.chkCaseSensitiveSearch,false,false);
		var q = config.options.chkRegExpSearch ? "/" : "'";
		if(matches.length > 0)
			displayMessage(config.macros.search.successMsg.format([matches.length.toString(),q + txt.value + q]));
		else
			displayMessage(config.macros.search.failureMsg.format([q + txt.value + q]));
		lastSearchText = txt.value;
		};
	var clickHandler = function(e)
		{
		doSearch(this.nextSibling);
		};
	var keyHandler = function(e)
		{
		if (!e) var e = window.event;
		switch(e.keyCode)
			{
			case 27:
				this.value = "";
				clearMessage();
				break;
			}
		if((this.value.length > 2) && (this.value != lastSearchText))
			{
			if(searchTimeout)
				clearTimeout(searchTimeout);
			var txt = this;
			searchTimeout = setTimeout(function() {doSearch(txt);},200);
			}
		};
	var focusHandler = function(e)
		{
		this.select();
		};
	var btn = createTiddlyButton(place,this.label,this.prompt,clickHandler);
	var txt = createTiddlyElement(place,"input",null,null,null);
	if(params[0])
		txt.value = params[0];
	txt.onkeyup = keyHandler;
	txt.onfocus = focusHandler;
	txt.setAttribute("size",this.sizeTextbox);
	txt.setAttribute("autocomplete","off");
	if(navigator.userAgent.toLowerCase().indexOf("safari") == -1)
		txt.setAttribute("type","text");
	else
		{
		txt.setAttribute("type","search");
		txt.setAttribute("results","5");
		}
}

config.macros.tiddler.handler = function(place,macroName,params)
{
	var wrapper = createTiddlyElement(place,"span",null,params[1] ? params[1] : null,null);
	var text = store.getTiddlerText(params[0]);
	if(text)
		wikify(text,wrapper,null,null);
}

config.macros.tag.handler = function(place,macroName,params)
{
	createTagButton(place,params[0]);
}

config.macros.closeAll.handler = function(place)
{
	createTiddlyButton(place,this.label,this.prompt,closeAllTiddlers);
}

config.macros.permaview.handler = function(place)
{
	createTiddlyButton(place,this.label,this.prompt,onClickPermaView);
}

config.macros.saveChanges.handler = function(place)
{
	if(!readOnly)
		createTiddlyButton(place,this.label,this.prompt,saveChanges);
}

config.macros.slider.onClickSlider = function(e)
{
	if (!e) var e = window.event;
	var n = this.nextSibling;
	var cookie = n.getAttribute("cookie");
	var isOpen = n.style.display != "none";
	if(config.options.chkAnimate)
		anim.startAnimating(new Slider(n,!isOpen,e.shiftKey || e.altKey,"none"));
	else
		n.style.display = isOpen ? "none" : "block";
	config.options[cookie] = !isOpen;
	saveOptionCookie(cookie);
}

config.macros.slider.handler = function(place,macroName,params)
{
	var cookie = params[0] ? params[0] : "";
	var text = store.getTiddlerText(params[1]);
	var btn = createTiddlyButton(place,params[2],params[3],this.onClickSlider);
	var panel = createTiddlyElement(place,"div",null,"sliderPanel",null);
	panel.setAttribute("cookie",cookie);
	panel.style.display = config.options[cookie] ? "block" : "none";
	if(text)
		wikify(text,panel,null,null);
}

config.macros.option.onChangeOption = function(e)
{
	var opt = this.getAttribute("option");
	var elementType,valueField;
	if(opt)
		{
		switch(opt.substr(0,3))
			{
			case "txt":
				elementType = "input";
				valueField = "value";
				break;
			case "chk":
				elementType = "input";
				valueField = "checked";
				break;
			}
		config.options[opt] = this[valueField];
		saveOptionCookie(opt);
		var nodes = document.getElementsByTagName(elementType);
		for(var t=0; t<nodes.length; t++)
			{
			var optNode = nodes[t].getAttribute("option");
			if(opt == optNode)
				nodes[t][valueField] = this[valueField];
			}
		}
	return(true);
}

config.macros.option.handler = function(place,macroName,params)
{
	var opt = params[0];
	if(config.options[opt] == undefined)
		return;
	var c;
	switch(opt.substr(0,3))
		{
		case "txt":
			c = document.createElement("input");
			c.onkeyup = this.onChangeOption;
			c.setAttribute("option",opt);
			c.size = 15;
			c.value = config.options[opt];
			place.appendChild(c);
			break;
		case "chk":
			c = document.createElement("input");
			c.setAttribute("type","checkbox");
			c.onclick = this.onChangeOption;
			c.setAttribute("option",opt);
			c.checked = config.options[opt];
			place.appendChild(c);
			break;
		}
}

config.macros.newTiddler.onClick = function()
{
	displayTiddler(null,config.macros.newTiddler.title,2,null,null,false,false);
	var e = document.getElementById("editorTitle" + config.macros.newTiddler.title);
	e.focus();
	e.select();
}

config.macros.newTiddler.handler = function(place)
{
	if(!readOnly)
		createTiddlyButton(place,this.label,this.prompt,this.onClick);
}

config.macros.newJournal.handler = function(place,macroName,params)
{
	if(!readOnly)
		{
		var now = new Date();
		var title = now.formatString(params[0].trim());
		var createJournal = function() {
			displayTiddler(null,title,2,null,null,false,false);
			var tagsBox = document.getElementById("editorTags" + title);
			if(tagsBox && params[1])
				tagsBox.value += " " + String.encodeTiddlyLink(params[1]);
			};
		createTiddlyButton(place,this.label,this.prompt,createJournal);
		}
}

config.macros.sparkline.handler = function(place,macroName,params)
{
	var data = [];
	var min = 0;
	var max = 0;
	for(var t=0; t<params.length; t++)
		{
		var v = parseInt(params[t]);
		if(v < min)
			min = v;
		if(v > max)
			max = v;
		data.push(v);
		}
	if(data.length < 1)
		return;
	var box = createTiddlyElement(place,"span",null,"sparkline",String.fromCharCode(160));
	box.title = data.join(",");
	var w = box.offsetWidth;
	var h = box.offsetHeight;
	box.style.paddingRight = (data.length * 2 - w) + "px";
	box.style.position = "relative";
	for(var d=0; d<data.length; d++)
		{
		var tick = document.createElement("img");
		tick.border = 0;
		tick.className = "sparktick";
		tick.style.position = "absolute";
		tick.src = "data:image/gif,GIF89a%01%00%01%00%91%FF%00%FF%FF%FF%00%00%00%C0%C0%C0%00%00%00!%F9%04%01%00%00%02%00%2C%00%00%00%00%01%00%01%00%40%02%02T%01%00%3B";
		tick.style.left = d*2 + "px";
		tick.style.width = "2px";
		var v = Math.floor(((data[d] - min)/(max-min)) * h);
		tick.style.top = (h-v) + "px";
		tick.style.height = v + "px";
		box.appendChild(tick);
		}
}

config.macros.tabs.handler = function(place,macroName,params)
{
	var cookie = params[0];
	var numTabs = (params.length-1)/3;
	var wrapper = createTiddlyElement(place,"div",null,cookie,null);
	var tabset = createTiddlyElement(wrapper,"div",null,"tabset",null);
	tabset.setAttribute("cookie",cookie);
	var validTab = false;
	for(var t=0; t<numTabs; t++)
		{
		var label = params[t*3+1];
		var prompt = params[t*3+2];
		var content = params[t*3+3];
		var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
		tab.setAttribute("href","javascript:;");
		tab.onclick = this.onClickTab;
		tab.setAttribute("tab",label);
		tab.setAttribute("content",content);
		tab.title = prompt;
		if(config.options[cookie] == label)
			validTab = true;
		}
	if(!validTab)
		config.options[cookie] = params[1];
	this.switchTab(tabset,config.options[cookie]);
}

config.macros.tabs.onClickTab = function(e)
{
	config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
}

config.macros.tabs.switchTab = function(tabset,tab)
{
	var cookie = tabset.getAttribute("cookie");
	var theTab = null
	var nodes = tabset.childNodes;
	for(var t=0; t<nodes.length; t++)
		if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab)
			{
			theTab = nodes[t];
			theTab.className = "tab tabSelected";
			}
		else
			nodes[t].className = "tab tabUnselected"
	if(theTab)
		{
		if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
			tabset.parentNode.removeChild(tabset.nextSibling);
		var tabContent = createTiddlyElement(null,"div",null,"tabContents",null);
		tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
		wikify(store.getTiddlerText(theTab.getAttribute("content")),tabContent,null,null);
		if(cookie)
			{
			config.options[cookie] = tab;
			saveOptionCookie(cookie);
			}
		}
}

// ---------------------------------------------------------------------------------
// Config and macro stuff
// ---------------------------------------------------------------------------------

// Merge a custom configuration over the top of the current configuration
// Returns a string error message or null if it went OK
function processConfig(customConfig)
{
	try
		{
		if(customConfig && customConfig != "")
			window.eval(customConfig);
		}
	catch(e)
		{
		return(e.toString());
		}
	return null;
}

// Render a macro
function insertMacro(place,macroName,macroParams)
{
	var params = macroParams.readMacroParams();
	try
		{
		var macro = config.macros[macroName];
		if(macro && macro.handler)
			macro.handler(place,macroName,params);
		else
			createTiddlyElement(place,"span",null,"errorNoSuchMacro","<<" + macroName + ">>");
		}
	catch(e)
		{
		displayMessage(config.messages.macroError.format([macroName]));
		displayMessage(e.toString());
		}
}

// ---------------------------------------------------------------------------------
// Tiddler() object
// ---------------------------------------------------------------------------------

function Tiddler()
{
	this.title = null;
	this.text = null;
	this.modifier = null;
	this.modified = new Date();
	this.links = [];
	this.tags = [];
	return this;
}

// Load a tiddler from an HTML DIV
Tiddler.prototype.loadFromDiv = function(divRef,title)
{
	var text = Tiddler.unescapeLineBreaks(divRef.firstChild ? divRef.firstChild.nodeValue : "");
	var modifier = divRef.getAttribute("modifier");
	var modified = Date.convertFromYYYYMMDDHHMM(divRef.getAttribute("modified"));
	var tags = divRef.getAttribute("tags");
	this.set(title,text,modifier,modified,tags);
	return this;
}

// Format the text for storage in an HTML DIV
Tiddler.prototype.saveToDiv = function()
{
	return '<div tiddler="' + this.title + '" modified="' +
							this.modified.convertToYYYYMMDDHHMM() + '" modifier="' + this.modifier +
							'" tags="' + this.getTags() + '">' +
							this.escapeLineBreaks().htmlEncode() + '</div>';
}

// Format the text for storage in an RSS item
Tiddler.prototype.saveToRss = function(url)
{
	var s = [];
	s.push("<item>");
	s.push("<title>" + this.title.htmlEncode() + "</title>");
	s.push("<description>" + this.text.replace(regexpNewLine,"<br />").htmlEncode() + "</description>");
	for(var t=0; t<this.tags.length; t++)
		s.push("<category>" + this.tags[t] + "</category>");
	s.push("<link>" + url + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
	s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
	s.push("</item>");
	return(s.join("\n"));
}

// Change the text and other attributes of a tiddler
Tiddler.prototype.set = function(title,text,modifier,modified,tags)
{
	if(title != undefined)
		this.title = title;
	if(text != undefined)
		this.text = text;
	if(modifier != undefined)
		this.modifier = modifier;
	if(modified != undefined)
		this.modified = modified;
	if(tags != undefined)
		this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
	else
		this.tags = [];
	this.changed();
	return this;
}

// Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
Tiddler.prototype.getTags = function()
{
	if(this.tags)
		{
		var results = [];
		for(var t=0; t<this.tags.length; t++)
			results.push(String.encodeTiddlyLink(this.tags[t]));
		return results.join(" ");
		}
	else
		return "";
}

var regexpBackSlashEn = new RegExp("\\\\n","mg");
var regexpBackSlash = new RegExp("\\\\","mg");
var regexpBackSlashEss = new RegExp("\\\\s","mg");
var regexpNewLine = new RegExp("\n","mg");
var regexpCarriageReturn = new RegExp("\r","mg");

// Static method to Convert "\n" to newlines, "\s" to "\"
Tiddler.unescapeLineBreaks = function(text)
{
	if(text && text != "")
		return text.replace(regexpBackSlashEn,"\n").replace(regexpBackSlashEss,"\\").replace(regexpCarriageReturn,"");
	else
		return "";
}

// Convert newlines to "\n", "\" to "\s"
Tiddler.prototype.escapeLineBreaks = function()
{
	return this.text.replace(regexpBackSlash,"\\s").replace(regexpNewLine,"\\n").replace(regexpCarriageReturn,"");
}

// Updates the secondary information (like links[] array) after a change to a tiddler
Tiddler.prototype.changed = function()
{
	this.links = [];
	var nextPos = 0;
	var theLink;
	do {
		var formatMatch = wikiNameRegExp.exec(this.text);
		if(formatMatch)
			{
			if(!formatMatch[1] && formatMatch[2] && formatMatch[2] != this.title)
				this.links.pushUnique(formatMatch[2]);
			else if(formatMatch[4] && store.tiddlers[formatMatch[5]] != undefined)
				this.links.pushUnique(formatMatch[5]);
			else if(formatMatch[6] && formatMatch[6] != this.title)
				this.links.pushUnique(formatMatch[6]);
			}
	} while(formatMatch);
	return;
}

Tiddler.prototype.getSubtitle = function()
{
	var theModifier = this.modifier;
	if(!theModifier)
		theModifier = config.messages.subtitleUnknown;
	var theModified = this.modified;
	if(theModified)
		theModified = theModified.toLocaleString();
	else
		theModified = config.messages.subtitleUnknown;
	return(theModifier + ", " + theModified);
}

// ---------------------------------------------------------------------------------
// TiddlyWiki() object contains Tiddler()s
// ---------------------------------------------------------------------------------

function TiddlyWiki()
{
	this.tiddlers = {}; // Hashmap by name of tiddlers
	this.namedNotifications = {}; // Hashmap by name of array of notification functions
	this.blanketNotifications = []; // Array of blanket notifications to be invoked on any change
	this.dirty = false;
}

// Set the dirty flag
TiddlyWiki.prototype.setDirty = function(dirty)
{
	this.dirty = dirty;
}

// Invoke the notification handlers for a particular tiddler
TiddlyWiki.prototype.notify = function(title,doBlanket)
{
	var notification = this.namedNotifications[title];
	if(notification)
		for(var t=0; t<notification.length; t++)
			notification[t](title);
	if(doBlanket)
		for(var n=0; n<this.blanketNotifications.length; n++)
			this.blanketNotifications[n](title);
}

// Invoke the notification handlers for all tiddlers
TiddlyWiki.prototype.notifyAll = function()
{
	var notifyTitle;
	for(notifyTitle in this.tiddlers)
		this.notify(notifyTitle,false);
	for(notifyTitle in config.shadowTiddlers)
		if(this.tiddlers[notifyTitle] == undefined)
			this.notify(notifyTitle,false);
	for(var n=0; n<this.blanketNotifications.length; n++)
		this.blanketNotifications[n]();
}

// Add a notification handler to a tiddler
TiddlyWiki.prototype.addNotification = function(title,fn)
{
	var notification;
	if(title)
		{
		notification = this.namedNotifications[title];
		if(!notification)
			{
			notification = [];
			this.namedNotifications[title] = notification;
			}
		}
	else
		notification = this.blanketNotifications;
	notification.push(fn);
	return this;
}

// Clear a TiddlyWiki so that it contains no tiddlers
TiddlyWiki.prototype.clear = function(src)
{
	this.tiddlers = {};
	this.dirty = false;
}

TiddlyWiki.prototype.removeTiddler = function(title)
{
	var tiddler = this.tiddlers[title];
	if(tiddler)
		{
		delete this.tiddlers[title];
		this.notify(title,true);
		this.dirty = true;
		}
}

TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
{
	if(!title)
		return(defaultText);
	var tiddler = this.tiddlers[title];
	if(tiddler)
		return tiddler.text;
	else if(config.shadowTiddlers[title])
		return config.shadowTiddlers[title];
	else if(defaultText)
		return defaultText;
	else
		return null;
}

TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,ignoreList)
{
	var childIgnoreList = ignoreList ? ignoreList : {};
	childIgnoreList[title] = true;
	var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
	var text = this.getTiddlerText(title,defaultText);
	if(text == null)
		return "";
	var textOut = [];
	var lastPos = 0;
	do {
		var match = bracketRegExp.exec(text);
		if(match)
			{
			textOut.push(text.substr(lastPos,match.index-lastPos));
			if(match[1])
				{
				if(childIgnoreList[match[1]])
					textOut.push(match[1]);
				else
					{
					var subText = this.getRecursiveTiddlerText(match[1],match[1],childIgnoreList);
					textOut.push(subText);
					}
				}
			lastPos = match.index + match[1].length + 4;
			}
		else
			textOut.push(text.substr(lastPos));
	} while(match);
	delete childIgnoreList[title];
	return(textOut.join(""));
}

TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags)
{
	var tiddler = this.tiddlers[title];
	if(tiddler)
		delete this.tiddlers[title];
	else
		tiddler = new Tiddler();
	tiddler.set(newTitle,newBody,modifier,modified,tags);
	this.tiddlers[newTitle] = tiddler;
	if(title != newTitle)
		this.notify(title,true);
	this.notify(newTitle,true);
	this.dirty = true;
	return tiddler;
}

TiddlyWiki.prototype.createTiddler = function(title)
{
	tiddler = this.tiddlers[title];
	if(!tiddler)
		{
		tiddler = new Tiddler();
		this.tiddlers[title] = tiddler;
		this.dirty = true;
		}
	return tiddler;
}

// Load contents of a tiddlywiki from an HTML DIV
TiddlyWiki.prototype.loadFromDiv = function(srcID,idPrefix)
{
	if(document.normalize)
		document.normalize();
	var lenPrefix = idPrefix.length;
	var store = document.getElementById(srcID).childNodes;
	for(var t = 0; t < store.length; t++)
		{
		var e = store[t];
		var title = null;
		if(e.getAttribute)
			title = e.getAttribute("tiddler");
		if(!title && e.id && e.id.substr(0,lenPrefix) == idPrefix)
			title = e.id.substr(lenPrefix);
		if(title && title != "")
			{
			var tiddler = this.createTiddler(title);
			tiddler.loadFromDiv(e,title);
			}
		}
	this.dirty = false;
}

// Return an array of tiddlers matching a search string
TiddlyWiki.prototype.search = function(searchText,caseSensitive,useRegExp,sortField,excludeTag)
{
	if (!useRegExp)
		searchText = searchText.escapeRegExp();
	var regExp = new RegExp(searchText,caseSensitive ? "m" : "im");
	var candidates = this.reverseLookup("tags",excludeTag,false);
	var results = [];
	for(var t=0; t<candidates.length; t++)
		{
		if(regExp.test(candidates[t].title) || regExp.test(candidates[t].text))
			results.push(candidates[t]);
		}
	if(!sortField)
		sortField = "title";
	results.sort(function (a,b) {if(a[sortField] == b[sortField]) return(0); else return (a[sortField] < b[sortField]) ? -1 : +1; });
	return results;
}

// Return an array of all the tags in use. Each member of the array is another array where [0] is the name of the tag and [1] is the number of occurances
TiddlyWiki.prototype.getTags = function()
{
	var results = [];
	for(var t in this.tiddlers)
		{
		var tiddler = this.tiddlers[t];
		for(g=0; g<tiddler.tags.length; g++)
			{
			var tag = tiddler.tags[g];
			var f = false;
			for(var c=0; c<results.length; c++)
				if(results[c][0] == tag)
					{
					f = true;
					results[c][1]++;
					}
			if(!f)
				results.push([tag,1]);
			}
		}
	results.sort(function (a,b) {if(a[0] == b[0]) return(0); else return (a[0] < b[0]) ? -1 : +1; });
	return results;
}

// Return an array of the tiddlers that are tagged with a given tag
TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
{
	return this.reverseLookup("tags",tag,true,sortField);
}

// Return an array of the tiddlers that link to a given tiddler
TiddlyWiki.prototype.getReferringTiddlers = function(title,exclude,sortField)
{
	return this.reverseLookup("links",title,true,sortField);
}

// Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
// lookupMatch == true to match tiddlers, false to exclude tiddlers
TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
{
	var results = [];
	for(var t in this.tiddlers)
		{
		var tiddler = this.tiddlers[t];
		var f = !lookupMatch;
		for(var lookup=0; lookup<tiddler[lookupField].length; lookup++)
			if(tiddler[lookupField][lookup] == lookupValue)
				f = lookupMatch;
		if(f)
			results.push(tiddler);
		}
	if(!sortField)
		sortField = "title";
	results.sort(function (a,b) {if(a[sortField] == b[sortField]) return(0); else return (a[sortField] < b[sortField]) ? -1 : +1; });
	return results;
}

// Return the tiddlers as a sorted array
TiddlyWiki.prototype.getTiddlers = function(field)
{
	var results = [];
	for(var t in this.tiddlers)
		results.push(this.tiddlers[t]);
	if(field)
		results.sort(function (a,b) {if(a[field] == b[field]) return(0); else return (a[field] < b[field]) ? -1 : +1; });
	return results;
}

// Return array of names of tiddlers that are referred to but not defined
TiddlyWiki.prototype.getMissingLinks = function(sortField)
{
	var results = [];
	for(var t in this.tiddlers)
		{
		var tiddler = this.tiddlers[t];
		for(var n=0; n<tiddler.links.length;n++)
			{
			var link = tiddler.links[n];
			if(this.tiddlers[link] == null)
				results.pushUnique(link);
			}
		}
	results.sort();
	return results;
}

// Return an array of names of tiddlers that are defined but not referred to
TiddlyWiki.prototype.getOrphans = function()
{
	var results = [];
	for(var t in this.tiddlers)
		if(this.getReferringTiddlers(t).length == 0)
			results.push(t);
	results.sort();
	return results;
}

// ---------------------------------------------------------------------------------
// Tiddler functions
// ---------------------------------------------------------------------------------

// Display several tiddlers from a list of space separated titles
function displayTiddlers(src,titles,state,highlightText,highlightCaseSensitive,animate,slowly)
{
	var tiddlerNames = titles.readBracketedList();
	for(var t = tiddlerNames.length-1;t>=0;t--)
		displayTiddler(src,tiddlerNames[t],state,highlightText,highlightCaseSensitive,animate,slowly);
}

// Display a tiddler with animation and scrolling, as though a link to it has been clicked on
//	src = source element object (eg link) for animation effects and positioning
//	title = title of tiddler to display
//	state = 0 is default or current state, 1 is read only and 2 is edittable
//	highlightText = text to highlight in the displayed tiddler
//	highlightCaseSensitive = flag for whether the highlight text is case sensitive
function displayTiddler(src,title,state,highlightText,highlightCaseSensitive,animate,slowly)
{
	var place = document.getElementById("tiddlerDisplay");
	var after = findContainingTiddler(src); // Which tiddler this one will be positioned after
	var before;
	if(after == null)
		before = place.firstChild;
	else if(after.nextSibling)
		before = after.nextSibling;
	else
		before = null;
	var theTiddler = createTiddler(place,before,title,state,highlightText,highlightCaseSensitive);
	if(src)
		{
		if(config.options.chkAnimate && (animate == undefined || animate == true))
			anim.startAnimating(new Zoomer(title,src,theTiddler,slowly),new Scroller(theTiddler,slowly));
		else
			window.scrollTo(0,ensureVisible(theTiddler));
		}
}

// Create a tiddler if it doesn't exist (with no fancy animating)
//	place = parent element
//	before = node before which to create/move the tiddler
//	title = title of tiddler to display
//	state = 0 is default or current state, 1 is read only and 2 is edittable
//	highlightText = text to highlight in the displayed tiddler
//	highlightCaseSensitive = flag for whether the highlight text is case sensitive
function createTiddler(place,before,title,state,highlightText,highlightCaseSensitive)
{
	var theTiddler = createTiddlerSkeleton(place,before,title);
	createTiddlerTitle(title,highlightText,highlightCaseSensitive);
	var theViewer = document.getElementById("viewer" + title);
	var theEditor = document.getElementById("editorWrapper" + title);
	switch(state)
		{
		case 0:
			if(!theViewer && !theEditor)
				{
				createTiddlerToolbar(title,false);
				createTiddlerViewer(title,highlightText,highlightCaseSensitive);
				createTiddlerFooter(title,false);
				}
			break;
		case 1: // Viewer
			if(theViewer)
				theViewer.parentNode.removeChild(theViewer);
			if(theEditor)
				theEditor.parentNode.removeChild(theEditor);
			createTiddlerToolbar(title,false);
			createTiddlerViewer(title,highlightText,highlightCaseSensitive);
			createTiddlerFooter(title,false);
			break;
		case 2: // Editor
			if(!theEditor)
				{
				if(theViewer)
					theViewer.parentNode.removeChild(theViewer);
				createTiddlerToolbar(title,true);
				createTiddlerEditor(title);
				createTiddlerFooter(title,true);
				}
			break;
		}
	return(theTiddler);
}

function refreshTiddler(title)
{
	var theViewer = document.getElementById("viewer" + title);
	if(theViewer)
		{
		theViewer.parentNode.removeChild(theViewer);
		createTiddlerViewer(title,null,null);
		}
}

function createTiddlerSkeleton(place,before,title)
{
	var theTiddler = document.getElementById("tiddler" + title);
	if(!theTiddler)
		{
		theTiddler = createTiddlyElement(null,"div","tiddler" + title,"tiddler",null);
		theTiddler.onmouseover = onMouseOverTiddler;
		theTiddler.onmouseout = onMouseOutTiddler;
		theTiddler.ondblclick = onDblClickTiddler;
		var theInnerTiddler = createTiddlyElement(theTiddler,"div",null,"unselectedTiddler",null);
		var theToolbar = createTiddlyElement(theInnerTiddler,"div","toolbar" + title,"toolbar", null);
		var theTitle = createTiddlyElement(theInnerTiddler,"div","title" + title,"title",null);
		var theBody = createTiddlyElement(theInnerTiddler,"div","body" + title,"body",null);
		var theFooter = createTiddlyElement(theInnerTiddler,"div","footer" + title,"footer",null);
		place.insertBefore(theTiddler,before);
		}
	return(theTiddler);
}

function createTiddlerTitle(title,highlightText,highlightCaseSensitive)
{
	var theTitle = document.getElementById("title" + title);
	if(theTitle)
		{
		removeChildren(theTitle);
		if(highlightText == "")
			highlightText = null;
		var highlightRegExp,highlightMatch;
		if(highlightText)
			{
			highlightRegExp = new RegExp(highlightText,highlightCaseSensitive ? "mg" : "img");
			highlightMatch = highlightRegExp.exec(title);
			}
		highlightMatch = subWikify(theTitle,title,0,title.length,highlightRegExp,highlightMatch);
		var tiddler = store.tiddlers[title];
		if(tiddler)
			theTitle.title = tiddler.getSubtitle();
		}
}

// Create a tiddler toolbar according to whether it's an editor or not
function createTiddlerToolbar(title,isEditor)
{
	var theToolbar = document.getElementById("toolbar" + title);
	var lingo = config.views;
	if(theToolbar)
		{
		removeChildren(theToolbar);
		insertSpacer(theToolbar);
		if(isEditor)
			{
			// Editor toolbar
			lingo = lingo.editor;
			createTiddlyButton(theToolbar,lingo.toolbarDone.text,lingo.toolbarDone.tooltip,onClickToolbarSave);
			insertSpacer(theToolbar);
			createTiddlyButton(theToolbar,lingo.toolbarCancel.text,lingo.toolbarCancel.tooltip,onClickToolbarUndo);
			insertSpacer(theToolbar);
			createTiddlyButton(theToolbar,lingo.toolbarDelete.text,lingo.toolbarDelete.tooltip,onClickToolbarDelete);
			}
		else
			{
			// Viewer toolbar
			lingo = lingo.wikified;
			createTiddlyButton(theToolbar,lingo.toolbarClose.text,lingo.toolbarClose.tooltip,onClickToolbarClose);
			insertSpacer(theToolbar);
			if(!readOnly)
				{
				createTiddlyButton(theToolbar,lingo.toolbarEdit.text,lingo.toolbarEdit.tooltip,onClickToolbarEdit);
				insertSpacer(theToolbar);
				}
			createTiddlyButton(theToolbar,lingo.toolbarPermalink.text,lingo.toolbarPermalink.tooltip,onClickToolbarPermaLink);
			insertSpacer(theToolbar);
			createTiddlyButton(theToolbar,lingo.toolbarReferences.text,lingo.toolbarReferences.tooltip,onClickToolbarReferences);
			}
		insertSpacer(theToolbar);
		}
}

function createTiddlerPopup(srcElement)
{
	var popup = document.getElementById("popup");
	if(popup && popup.nextSibling == srcElement)
		{
		hideTiddlerPopup();
		return null;
		}
	if(popup)
		popup.parentNode.removeChild(popup);
	popup = createTiddlyElement(null,"div","popup",null,null);
	var leftPx = srcElement.offsetLeft;
	var topPx = srcElement.offsetTop;
	var heightPx = srcElement.offsetHeight;
	if (leftPx <= 1 && srcElement.parentNode.offsetLeft > 0)
		leftPx = srcElement.parentNode.offsetLeft;
	if (topPx <= 1 && srcElement.parentNode.offsetTop > 0)
		topPx = srcElement.parentNode.offsetTop;
	if (heightPx <= 1 && srcElement.parentNode.offsetHeight > 0)
		heightPx = srcElement.parentNode.offsetHeight;
	popup.style.left = leftPx + "px";
	popup.style.top = topPx + heightPx + "px";
	popup.style.display = "block";
	srcElement.onmouseout = onMouseOutTiddlerPopup;
	srcElement.appendChild(popup);
	return popup;
}

function scrollToTiddlerPopup(popup,slowly)
{
	if(config.options.chkAnimate)
		anim.startAnimating(new Scroller(popup,slowly));
	else
		window.scrollTo(0,ensureVisible(popup));
}

function onMouseOutTiddlerPopup(e)
{
	if (!e) var e = window.event;
	var related = (e.relatedTarget) ? e.relatedTarget : e.toElement;
	try
		{
		while (related != this && related && related.nodeName && related.nodeName.toLowerCase() != "body")
			related = related.parentNode;
		}
	catch(e)
		{
		related = null;
		}
	if(related != this)
		{
		this.onmouseout = null;
		hideTiddlerPopup();
		}
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}

function hideTiddlerPopup()
{
	var popup = document.getElementById("popup");
	if(popup)
		popup.parentNode.removeChild(popup);
}

// Create the body section of a read-only tiddler
function createTiddlerViewer(title,highlightText,highlightCaseSensitive,htmlElement)
{
	var theBody = document.getElementById("body" + title);
	if(theBody)
		{
		var tiddler = store.tiddlers[title];
		var tiddlerText = store.getTiddlerText(title);
		var theViewer = createTiddlyElement(theBody,htmlElement ? htmlElement : "div","viewer" + title,"viewer",null);
		if(tiddler)
			theViewer.setAttribute("tags",tiddler.tags.join(" "));
		if(tiddlerText == null)
			{
			tiddlerText = config.views.wikified.defaultText.format([title]);
			theViewer.style.fontStyle = "italic";
			}
		wikify(tiddlerText,theViewer,highlightText,highlightCaseSensitive);
		}
}

// Create the footer section of a tiddler
function createTiddlerFooter(title,isEditor)
{
	var theFooter = document.getElementById("footer" + title);
	var tiddler = store.tiddlers[title];
	if(theFooter && tiddler)
		{
		removeChildren(theFooter);
		insertSpacer(theFooter);
		if(isEditor)
			{
			}
		else
			{
			var lingo = config.views.wikified.tag;
			var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
			var theTags = createTiddlyElement(theFooter,"div",null,null,prompt);
			for(var t=0; t<tiddler.tags.length; t++)
				{
				var theTag = createTagButton(theTags,tiddler.tags[t],tiddler.title);
				insertSpacer(theTags);
				}
			}
		}
}

// Create a button for a tag with a popup listing all the tiddlers that it tags
function createTagButton(place,tag,excludeTiddler)
{
	var theTag = createTiddlyButton(place,tag,config.views.wikified.tag.tooltip.format([tag]),onClickTag);
	theTag.setAttribute("tag",tag);
	if(excludeTiddler)
		theTag.setAttribute("tiddler",excludeTiddler);
	return(theTag);
}

// Create the body section of an edittable tiddler
function createTiddlerEditor(title)
{
	var theBody = document.getElementById("body" + title);
	if(theBody)
		{
		var tiddlerText = store.getTiddlerText(title);
		var tiddlerExists = (tiddlerText != null);
		if(!tiddlerExists)
			tiddlerText = config.views.editor.defaultText.format([title]);
		var theEditor = createTiddlyElement(theBody,"div","editorWrapper" + title,"editor",null);
		theEditor.onkeypress = onEditKey;
		var theTitleBox = createTiddlyElement(theEditor,"input","editorTitle" + title,null,null);
		theTitleBox.setAttribute("type","text");
		theTitleBox.value = title;
		theTitleBox.setAttribute("size","40");
		var theBodyBox = createTiddlyElement(theEditor,"textarea","editorBody" + title,null,null);
		theBodyBox.value = tiddlerText;
		var rows = 10;
		var lines = tiddlerText.match(regexpNewLine);
		if(lines != null && lines.length > rows)
			rows = lines.length + 5;
		theBodyBox.setAttribute("rows",rows);
		var theTagsBox = createTiddlyElement(theEditor,"input","editorTags" + title,null,null);
		theTagsBox.setAttribute("type","text");
		var tiddler = store.tiddlers[title];
		theTagsBox.value = tiddler ? tiddler.getTags() : "";
		theTagsBox.setAttribute("size","40");
		var tagPrompt = createTiddlyElement(theEditor,"div",null,"editorFooter",config.views.editor.tagPrompt);
		insertSpacer(tagPrompt);
		var lingo = config.views.editor.tagChooser;
		var addTag = createTiddlyButton(tagPrompt,lingo.text,lingo.tooltip,onClickAddTag);
		addTag.setAttribute("tiddler",title);
		theBodyBox.focus();
		}
}

function saveTiddler(title)
{
	var titleBox = document.getElementById("editorTitle" + title);
	var newTitle = titleBox.value;
	if(store.tiddlers[newTitle])
		{
		if(newTitle != title && !confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
			{
			titleBox.focus();
			titleBox.select();
			return;
			}
		}
	var body = document.getElementById("editorBody" + title);
	var newBody = body.value;
	var newTags = document.getElementById("editorTags" + title).value;
	blurTiddler(title);
	store.saveTiddler(title,newTitle,newBody,config.options.txtUserName,new Date(),newTags);
	displayTiddler(null,newTitle,1,null,null,null,false,false);
	// Close the old tiddler if this is a rename
	if(title != newTitle)
		{
		var oldTiddler = document.getElementById("tiddler" + title);
		var newTiddler = document.getElementById("tiddler" + newTitle);
		oldTiddler.parentNode.replaceChild(newTiddler,oldTiddler);
		}
	if(config.options.chkAutoSave)
		saveChanges();
}

function selectTiddler(title)
{
	var e = document.getElementById("tiddler" + title);
	if(e != null)
		e.firstChild.className = "selectedTiddler";
}

function deselectTiddler(title)
{
	var e = document.getElementById("tiddler" + title);
	if(e != null)
		e.firstChild.className = "unselectedTiddler";
}

function blurTiddler(title)
{
	var body = document.getElementById("editorBody" + title);
	if(title)
		{
		body.focus();
		body.blur();
		}
}

function deleteTiddler(title)
{
	closeTiddler(title,false);
	store.removeTiddler(title);
	// Autosave
	if(config.options.chkAutoSave)
		saveChanges();
}

function closeTiddler(title,slowly)
{
	var tiddler = document.getElementById("tiddler" + title);
	if(tiddler != null)
		{
		scrubIds(tiddler);
		if(config.options.chkAnimate)
			anim.startAnimating(new Slider(tiddler,false,slowly,"all"));
		else
			tiddler.parentNode.removeChild(tiddler);
		}
}

function scrubIds(e)
{
	if(e.id)
		e.id = null;
	var children = e.childNodes;
	for(var t=0; t<children.length; t++)
		{
		var c = children[t];
		if(c.id)
			c.id = null;
		}
}

function closeAllTiddlers()
{
	clearMessage();
	var place = document.getElementById("tiddlerDisplay");
	var tiddler = place.firstChild;
	var nextTiddler;
	while(tiddler)
		{
		nextTiddler = tiddler.nextSibling;
		if(tiddler.id)
			if(tiddler.id.substr(0,7) == "tiddler")
				{
				var title = tiddler.id.substr(7);
				if(!document.getElementById("editorWrapper" + title))
					place.removeChild(tiddler);
				}
		tiddler = nextTiddler;
		}
}

// ---------------------------------------------------------------------------------
// Regular expression stuff
// ---------------------------------------------------------------------------------

var upperLetter = "[A-Z\u00c0-\u00de\u0150\u0170]";
var lowerLetter = "[a-z\u00df-\u00ff_0-9\\-\u0151\u0171]";
var anyLetter = "[A-Za-z\u00c0-\u00de\u00df-\u00ff_0-9\\-\u0150\u0170\u0151\u0171]";
var anyDigit = "[0-9]";
var anyNumberChar = "[0-9\\.E]";
var wikiNamePattern = "(~?)((?:" + upperLetter + "+" + lowerLetter + "+" + upperLetter + anyLetter + "*)|(?:" + upperLetter + "{2,}" + lowerLetter + "+))";
var urlPattern = "((?:http|https|mailto|ftp):[^\\s'\"]+(?:/|\\b))";
var explicitLinkPattern = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
var bracketNamePattern = "\\[\\[([^\\]]+)\\]\\]";

var wikiNamePatterns;
var wikiNameRegExp;
var structurePatterns;
var stylePatterns;
var tableRegExp;
var tableRowColRegExp;
var invalidPreWikiNamePattern;

function setupRegexp()
{
	// Table rows pattern
	var rowPattern = "^\\|([^\\n]*\\|)([fhc]?)$";
	tableRegExp = new RegExp(rowPattern,"mg");
	// Table columns pattern
	var elementPattern = "(?:(?:BGCOLOR|bgcolor)\\(([^\\)]+)\\):)?" +
		"("+
		"("+explicitLinkPattern+")?"+
		"("+bracketNamePattern+")?" +
		"[^\\|]*"+
		")\\|";
	tableRowColRegExp = new RegExp(elementPattern,"g");
	// Link patterns
	wikiNamePatterns = "(?:" + wikiNamePattern +
		")|(?:" + urlPattern +
		")|(?:" + explicitLinkPattern +
		")|(?:" + bracketNamePattern +
		")";
	wikiNameRegExp = new RegExp(wikiNamePatterns,"mg");
	invalidPreWikiNamePattern = anyLetter;
	// Structural patterns
	var breakPattern = "\\n";
	var horizontalRulePattern = "^----$\\n?";
	var headerPattern = "^!{1,5}";
	var bulletListItemPattern = "^\\*+";
	var numberedListItemPattern = "^#+";
	var tablePattern = "(?:^\\|[^\\n]*$\\n?)+";
	var blockquotePattern = "(?:^>[^\\n]*$\\n?)+";
	var blockquotePattern2 = "^<<<\\n((?:^(?!<<<)[^\\n]*\\n)+)(^<<<$\\n?)";
	var imagePattern = "\\[[Ii][Mm][Gg]\\[(?:([^\\|\\]]+)\\|)?([^\\[\\]\\|]+)\\]\\]";
	var verbatimPattern = "^\\{\\{\\{\\n((?:^[^\\n]*\\n)+?)(^\\}\\}\\}$\\n?)";
	var macroPattern = "<<([^>\\s]+)(?:\\s*)([^>]*)>>";
	structurePatterns = "(" + breakPattern +
		")|(" + horizontalRulePattern +
		")|(" + headerPattern +
		")|(" + bulletListItemPattern +
		")|(" + numberedListItemPattern +
		")|(" + tablePattern +
		")|(" + blockquotePattern +
		")|(?:" + blockquotePattern2 +
		")|(?:" + imagePattern +
		")|(?:" + verbatimPattern +
		")|(?:" + macroPattern +
		")";
	// Style patterns
	var boldPattern = "''((?:[^']+(?:'[^'])?)+)''";
	var strikePattern = "==([^=]+)==";
	var underlinePattern = "__([^_]+)__";
	var italicPattern = "//([^/]+)//";
	var supPattern = "\\^\\^([^\\^]+)\\^\\^";
	var subPattern = "~~([^~]+)~~";
	var monoPattern = "\\{\\{\\{(.*?)\\}\\}\\}";
	var colorPattern = "@@(?:color\\(([^\\)]+)\\):|bgcolor\\(([^\\)]+)\\):){0,2}([^@]+)@@";
	stylePatterns = "(?:" + boldPattern +
		")|(?:" + strikePattern +
		")|(?:" + underlinePattern +
		")|(?:" + italicPattern +
		")|(?:" + supPattern +
		")|(?:" + subPattern +
		")|(?:" + colorPattern +
		")|(?:" + monoPattern +
		")";
}

// Create child text nodes and link elements to represent a wiki-fied version of some text
function wikify(text,parent,highlightText,highlightCaseSensitive)
{
	// Prepare the regexp for the highlighted selection
	if(highlightText == "")
		highlightText = null;
	var highlightRegExp,highlightMatch;
	if(highlightText)
		{
		highlightRegExp = new RegExp(highlightText,highlightCaseSensitive ? "mg" : "img");
		highlightMatch = highlightRegExp.exec(text);
		}
	wikifyStructures(parent,text,text,0,text.length,highlightRegExp,highlightMatch);
}


function wikifyStructures(parent,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
	var body = parent;
	var structureRegExp = new RegExp(structurePatterns,"mg");
	var theList = []; // theList[0]: don't use
	var isInListMode = false;
	var isInHeaderMode = false;
	var isNewline = false;
	// The start of the fragment of the text being considered
	var nextPos = 0;
	// Loop through the bits of the body text
	do {
		// Get the next formatting match
		var formatMatch = structureRegExp.exec(targetText);
		var matchPos = formatMatch ? formatMatch.index : targetText.length;
		// Subwikify the plain text before the match
		if(nextPos < matchPos)
			{
			isNewline = false;
			highlightMatch = wikifyStyles(body,text,targetText.substring(nextPos,matchPos),startPos+nextPos,startPos+matchPos,highlightRegExp,highlightMatch);
			}
		// Dump out the formatted match
		var level;
		var theBlockquote;
		if(formatMatch)
			{
			// Dump out the link itself in the appropriate format
			if(formatMatch[1])
				{
				if(isNewline && isInListMode)
					{
					theList = [];
					body = parent;
					isInListMode = false;
					}
				else if(isInHeaderMode)
					{
					body = parent;
					isInHeaderMode = false;
					}
				else
					{
					isNewline = true;
					body.appendChild(document.createElement("br"));
					}
				}
			else if(formatMatch[2])
				{
				isNewline = false;
				body.appendChild(document.createElement("hr"));
				}
			else if(formatMatch[3])
				{
				level = formatMatch[3].length;
				isNewline = false;
				isInHeaderMode = true;
				var theHeader = document.createElement("h" + level);
				parent.appendChild(theHeader);
				body = theHeader;
				}
			else if(formatMatch[4])
				{
				level = formatMatch[4].length;
				isNewline = false;
				isInListMode = true;
				if (theList[level] == null)
					{
					theList[level] = document.createElement("ul");
					body.appendChild(theList[level]);
					}
				theList = theList.slice(0,level + 1);
				body = document.createElement("li");
				theList[level].appendChild(body);
				}
			else if(formatMatch[5])
				{
				level = formatMatch[5].length;
				isNewline = false;
				isInListMode = true;
				if (theList[level] == null)
					{
					theList[level] = document.createElement("ol");
					body.appendChild(theList[level]);
					}
				theList = theList.slice(0,level + 1);
				body = document.createElement("li");
				theList[level].appendChild(body);
				}
			else if(formatMatch[6])
				{
				isNewline = false;
				highlightMatch = wikifyTable(body,text,formatMatch[6],startPos+matchPos,startPos+structureRegExp.lastIndex,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[7])
				{
				isNewline = false;
				var quotedText = formatMatch[7].replace(new RegExp("^>(>*)","mg"),"$1");
				theBlockquote = document.createElement("blockquote");
				var newHighlightRegExp,newHighlightMatch;
				if (highlightRegExp) {
					newHighlightRegExp = new RegExp(highlightRegExp.toString(), "img");
					newHighlightMatch = newHighlightRegExp.exec(quotedText);
				}
				wikifyStructures(theBlockquote,quotedText,quotedText,0,quotedText.length,newHighlightRegExp,newHighlightMatch);
				body.appendChild(theBlockquote);
				}
			else if(formatMatch[8])
				{
				isNewline = false;
				theBlockquote = document.createElement("blockquote");
				highlightMatch = wikifyStructures(theBlockquote,text,formatMatch[8],startPos+matchPos+4,startPos+structureRegExp.lastIndex-formatMatch[9].length,highlightRegExp,highlightMatch);
				body.appendChild(theBlockquote);
				}
			else if(formatMatch[11])
				{
				isNewline = false;
				var theImage = document.createElement("img");
				theImage.alt = formatMatch[10];
				theImage.src = formatMatch[11];
				body.appendChild(theImage);
				}
			else if(formatMatch[12])
				{
				isNewline = false;
				var theVerbatim = document.createElement("pre");
				out = text.substr(startPos+matchPos+4,startPos+structureRegExp.lastIndex-formatMatch[13].length-startPos-matchPos-4);
				out = out.replace(/\n/g,"\r\n");
				theVerbatim.appendChild(document.createTextNode(out));
				body.appendChild(theVerbatim);
				}
			else if(formatMatch[14])
				{
				isNewline = false;
				insertMacro(body,formatMatch[14],formatMatch[15]);
				}
			}
		// Move the next position past the formatting match
		nextPos = structureRegExp.lastIndex;
	} while(formatMatch);
	return highlightMatch;
}

function wikifyLinks(parent,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
	// The start of the fragment of the text being considered
	var nextPos = 0;
	// Loop through the bits of the body text
	var theLink;
	do {
		// Get the next formatting match
		var formatMatch = wikiNameRegExp.exec(targetText);
		var matchPos = formatMatch ? formatMatch.index : targetText.length;
		// Subwikify the plain text before the match
		if(nextPos < matchPos)
			highlightMatch = subWikify(parent,text,startPos+nextPos,startPos+matchPos,highlightRegExp,highlightMatch);
		// Dump out the formatted match
		if(formatMatch)
			{
			// Dump out the link itself in the appropriate format
			if(formatMatch[2])
				{
				if(formatMatch[1])
					{
					theLink = parent;
					matchPos++;
					}
				else if(matchPos > 0 && new RegExp(invalidPreWikiNamePattern,"").exec(targetText.charAt(matchPos - 1)))
					theLink = parent;
				else
					theLink = createTiddlyLink(parent,formatMatch[2],false);
				highlightMatch = subWikify(theLink,text,startPos+matchPos,startPos+wikiNameRegExp.lastIndex,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[3])
				{
				theLink = createExternalLink(parent,formatMatch[3]);
				highlightMatch = subWikify(theLink,text,startPos+matchPos,startPos+wikiNameRegExp.lastIndex,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[4])
				{
				if(store.tiddlers[formatMatch[5]] != undefined)
					theLink = createTiddlyLink(parent,formatMatch[5],false);
				else
					theLink = createExternalLink(parent,formatMatch[5]);
				highlightMatch = subWikify(theLink,text,startPos+matchPos+2,startPos+matchPos+2+formatMatch[4].length,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[6])
				{
				theLink = createTiddlyLink(parent,formatMatch[6],false);
				highlightMatch = subWikify(theLink,text,startPos+matchPos+2,startPos+wikiNameRegExp.lastIndex-2,highlightRegExp,highlightMatch);
				}
			}
		// Move the next position past the formatting match
		nextPos = wikiNameRegExp.lastIndex;
	} while(formatMatch);
	return highlightMatch;
}

function wikifyStyles(parent,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
	var formatRegExp = new RegExp(stylePatterns,"mg");
	// The start of the fragment of the text being considered
	var nextPos = 0;
	// Loop through the bits of the body text
	do {
		// Get the next formatting match
		var formatMatch = formatRegExp.exec(targetText);
		var matchPos = formatMatch ? formatMatch.index : targetText.length;
		// Subwikify the plain text before the match
		if(nextPos < matchPos)
			highlightMatch = wikifyLinks(parent,text,targetText.substring(nextPos,matchPos),startPos+nextPos,startPos+matchPos,highlightRegExp,highlightMatch);
		// Dump out the formatted match
		if(formatMatch)
			{
			// Dump out the link itself in the appropriate format
			if(formatMatch[1])
				{
				var theBold = createTiddlyElement(parent,"b",null,null,null);
				highlightMatch = wikifyStyles(theBold,text,formatMatch[1],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[2])
				{
				var theStrike = createTiddlyElement(parent,"strike",null,null,null);
				highlightMatch = wikifyStyles(theStrike,text,formatMatch[2],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[3])
				{
				var theUnderline = createTiddlyElement(parent,"u",null,null,null);
				highlightMatch = wikifyStyles(theUnderline,text,formatMatch[3],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[4])
				{
				var theItalic = createTiddlyElement(parent,"i",null,null,null);
				highlightMatch = wikifyStyles(theItalic,text,formatMatch[4],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[5])
				{
				var theSup = createTiddlyElement(parent,"sup",null,null,null);
				highlightMatch = wikifyStyles(theSup,text,formatMatch[5],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[6])
				{
				var theSub = createTiddlyElement(parent,"sub",null,null,null);
				highlightMatch = wikifyStyles(theSub,text,formatMatch[6],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[9])
				{
				var theSpan;
				if ((formatMatch[7] == "" || formatMatch[7] == null) && (formatMatch[8] == "" || formatMatch[8] == null))
					{
					theSpan = createTiddlyElement(parent,"span",null,"marked",null);
					}
					else
					{
					theSpan = createTiddlyElement(parent,"span",null,null,null);
					if (formatMatch[7] != "") theSpan.style.color = formatMatch[7];
					if (formatMatch[8] != "") theSpan.style.background = formatMatch[8];
					}
				highlightMatch = wikifyStyles(theSpan,text,formatMatch[9],startPos+formatRegExp.lastIndex-2-formatMatch[9].length,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
				}
			else if(formatMatch[10])
				{
				var theCode = createTiddlyElement(parent,"code",null,null,null);
				highlightMatch = wikifyStyles(theCode,text,formatMatch[10],startPos+matchPos+3,startPos+formatRegExp.lastIndex-3,highlightRegExp,highlightMatch);
				}
			}
		// Move the next position past the formatting match
		nextPos = formatRegExp.lastIndex;
	} while(formatMatch);
	return highlightMatch;
}

// Create a table
function wikifyTable(parent,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
	// The start of the fragment of the text being considered
	var nextPos = 0;
	var theTable = document.createElement("table");
	var bodyRowLen = 0;
	var headRowLen = 0;
	var footRowLen = 0;
	var bodyRows = [];
	var headRows = [];
	var footRows = [];
	var theCaption = null;
	// Loop through the bits of the body text
	do {
		// Get the next formatting match
		var formatMatch = tableRegExp.exec(targetText);
		var matchPos = formatMatch ? formatMatch.index : targetText.length;
		// Dump out the formatted match
		if(formatMatch) {
			if (formatMatch[2] == "c") {
				var cap = formatMatch[1].substring(0,formatMatch[1].length-1);
				theCaption = document.createElement("caption");
				highlightMatch = wikifyStyles(theCaption,text,cap,startPos+matchPos+1,startPos+cap.length,highlightRegExp,highlightMatch);
				if (bodyRowLen == 0 && headRowLen == 0 && footRowLen == 0) {
					theCaption.setAttribute("align", "top");
				} else {
					theCaption.setAttribute("align", "bottom");
				}
			} else if (formatMatch[2] == "h") {
				highlightMatch = wikifyTableRow(headRows,headRowLen,text,formatMatch[1],startPos+matchPos,startPos+matchPos+formatMatch[1].length,highlightRegExp,highlightMatch);
				headRowLen++;
			} else if (formatMatch[2] == "f") {
				highlightMatch = wikifyTableRow(footRows,footRowLen,text,formatMatch[1],startPos+matchPos,startPos+matchPos+formatMatch[1].length,highlightRegExp,highlightMatch);
				footRowLen++;
			} else {
				highlightMatch = wikifyTableRow(bodyRows,bodyRowLen,text,formatMatch[1],startPos+matchPos,startPos+matchPos+formatMatch[1].length,highlightRegExp,highlightMatch);
				bodyRowLen++;
			}
		}
		nextPos = tableRegExp.lastIndex;
	} while(formatMatch);

	if (theCaption != null) {
		theTable.appendChild(theCaption);
	}

	if (headRowLen > 0) {
		var theTableHead = document.createElement("thead");
		createTableRows(headRows,theTableHead);
		theTable.appendChild(theTableHead);
	}

	if (bodyRowLen > 0) {
		var theTableBody = document.createElement("tbody");
		createTableRows(bodyRows,theTableBody);
		theTable.appendChild(theTableBody);
	}

	if (footRowLen > 0) {
		var theTableFoot = document.createElement("tfoot");
		createTableRows(footRows,theTableFoot);
		theTable.appendChild(theTableFoot);
	}

	parent.appendChild(theTable);
	return highlightMatch;
}

function wikifyTableRow(rows,rowIndex,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
	// The start of the fragment of the text being considered
	var eIndex = 0;
	var elements = [];
	// Loop through the bits of the body text
	do {
		// Get the next formatting match
		var formatMatch = tableRowColRegExp.exec(targetText);
		var matchPos = formatMatch ? formatMatch.index : targetText.length;
		if(formatMatch) {
			var eText = formatMatch[2];
			if (eText == "~" || eText == ">") {
				elements[eIndex] = eText;
			} else {
				var eTextLen = eText.length;
				var align = "";
				if (eTextLen >= 1 && eText.charAt(0) == " ") {
					if (eTextLen >= 3 && eText.charAt(eTextLen - 1) == " ") {
						align = "center";
						eText = eText.substring(1,eTextLen - 1);
						//eTextLen -= 2;
						eTextLen--;
					} else {
						align = "right";
						eText = eText.substring(1);
						eTextLen--;
					}
				} else if (eTextLen >= 2 && eText.charAt(eTextLen - 1) == " ") {
					align = "left";
					eText = eText.substring(0,eTextLen - 1);
					//eTextLen--;
				}
				var theElement;
				if (eTextLen >= 1 && eText.charAt(0) == "!") {
					theElement = document.createElement("th");
					eText = eText.substring(1);
					eTextLen--;
				} else {
					theElement = document.createElement("td");
				}
				if (align != "") {
					theElement.align = align;
				}
				if (formatMatch[1]) {
					theElement.style.background = formatMatch[1];
				}
				highlightMatch = wikifyStyles(theElement,text,eText,startPos+tableRowColRegExp.lastIndex-eTextLen,startPos+tableRowColRegExp.lastIndex-1,highlightRegExp,highlightMatch);
				elements[eIndex] = theElement;
			}
			eIndex++;
		}
	} while(formatMatch);
	rows[rowIndex] = elements;
	return highlightMatch;
}

function createTableRows(rows,parent)
{
	var i, j, k, cols;
	for (i = 0; i < rows.length; i++) {
		cols = rows[i];
		var theRow = document.createElement("tr");
		for (j = 0; j < cols.length; j++) {
			if (cols[j] == "~") continue;
			var rowspan = 1;
			for (k = i+1; k < rows.length; k++) {
				if (rows[k][j] != "~") break;
				rowspan++;
			}
			var colspan = 1;
			for (; j < cols.length - 1; j++) {
				if (cols[j] != ">") break;
				colspan++;
			}
			var theElement = cols[j];
			if (rowspan > 1) {
				theElement.setAttribute("rowSpan",rowspan);
				theElement.setAttribute("rowspan",rowspan);
				theElement.valign = "center";
			}
			if (colspan > 1) {
				theElement.setAttribute("colSpan",colspan);
				theElement.setAttribute("colspan",colspan);
			}
			theRow.appendChild(theElement);
		}
		parent.appendChild(theRow);
	}
}

// Helper for wikify that handles highlights within runs of text
function subWikify(parent,text,startPos,endPos,highlightRegExp,highlightMatch)
{
	// Check for highlights
	while(highlightMatch && (highlightRegExp.lastIndex > startPos) && (highlightMatch.index < endPos) && (startPos < endPos))
		{
		// Deal with the plain text before the highlight
		if(highlightMatch.index > startPos)
			{
			parent.appendChild(document.createTextNode(text.substring(startPos,highlightMatch.index)));
			startPos = highlightMatch.index;
			}
		// Deal with the highlight
		var highlightEnd = Math.min(highlightRegExp.lastIndex,endPos);
		var theHighlight = createTiddlyElement(parent,"span",null,"highlight",text.substring(startPos,highlightEnd));
		startPos = highlightEnd;
		// Nudge along to the next highlight if we're done with this one
		if(startPos >= highlightRegExp.lastIndex)
			highlightMatch = highlightRegExp.exec(text);
		}
	// Do the unhighlighted text left over
	if(startPos < endPos)
		{
		parent.appendChild(document.createTextNode(text.substring(startPos,endPos)));
		//startPos = endPos;
		}
	return(highlightMatch);
}

// ---------------------------------------------------------------------------------
// Message area
// ---------------------------------------------------------------------------------

function displayMessage(text,linkText)
{
	var msgArea = document.getElementById("messageArea");
	var msg;
	if(linkText)
		{
		msg = createTiddlyElement(msgArea,"div",null,null,null);
		var link = createTiddlyElement(msg,"a",null,null,text);
		link.href = linkText;
		link.target = "_blank";
		}
	else
		msg = createTiddlyElement(msgArea,"div",null,null,text);
	msgArea.style.display = "block";
}

function clearMessage()
{
	var msgArea = document.getElementById("messageArea");
	removeChildren(msgArea);
	msgArea.style.display = "none";
}

// ---------------------------------------------------------------------------------
// Menu and sidebar functions
// ---------------------------------------------------------------------------------

function refreshStory(hint)
{
	var hits = hint ? store.getReferringTiddlers(hint) : null;
	var displayNodes = document.getElementById("tiddlerDisplay").childNodes;
	for(var t=0;t<displayNodes.length;t++)
		{
		var theId = displayNodes[t].id;
		if(theId && theId.substr(0,7) == "tiddler")
			{
			var title = theId.substr(7);
			if(hint)
				{
				var f = false;
				for(var h=0; h<hits.length; h++)
					if(hits[h].title == title)
						f = true
				if(f)
					refreshTiddler(title);
				}
			else
				refreshTiddler(title);
			}
		}
}

function refreshTabs(hint)
{
	refreshSpecialItem("sidebarTabs","SideBarTabs","SideBarTabs");
}

function refreshMenu(hint)
{
	refreshSpecialItem("mainMenu","MainMenu","MainMenu");
}

function refreshTitle(title)
{
	refreshSpecialItem("siteTitle",title,"SiteTitle");
	refreshPageTitle();
}

function refreshSubtitle(title)
{
	refreshSpecialItem("siteSubtitle",title,"SiteSubtitle");
	refreshPageTitle();
}

function refreshPageTitle()
{
	document.title = getElementText("siteTitle") + " - " + getElementText("siteSubtitle");
}

function refreshSidebar(title)
{
	refreshSpecialItem("sidebarOptions",title,"SideBarOptions");
}

function refreshSpecialItem(elementID,title,defaultText)
{
	var place = document.getElementById(elementID);
	removeChildren(place);
	wikify(store.getTiddlerText(title,defaultText),place,null,null);
}

function refreshStyles(title)
{
	setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,""));
}

// ---------------------------------------------------------------------------------
// Options cookie stuff
// ---------------------------------------------------------------------------------

function loadOptionsCookie()
{
	var cookies = document.cookie.split(";");
	for(var c=0; c<cookies.length; c++)
		{
		var p = cookies[c].indexOf("=");
		if(p != -1)
			{
			var name = cookies[c].substr(0,p).trim();
			var value = cookies[c].substr(p+1).trim();
			switch(name.substr(0,3))
				{
				case "txt":
					config.options[name] = unescape(value);
					break;
				case "chk":
					config.options[name] = value == "true";
					break;
				}
			}
		}
}

function saveOptionCookie(name)
{
	var c = name + "=";
	switch(name.substr(0,3))
		{
		case "txt":
			c += escape(config.options[name].toString());
			break;
		case "chk":
			c += config.options[name] ? "true" : "false";
			break;
		}
	c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
	document.cookie = c;
}

// ---------------------------------------------------------------------------------
// Saving
// ---------------------------------------------------------------------------------

var saveUsingSafari = false;
var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
var endSaveArea = '</d' + 'iv>';

// Check if there any unsaved changes before exitting
function checkUnsavedChanges()
{
	if(store.dirty)
		{
		if(confirm(config.messages.unsavedChangesWarning))
			saveChanges();
		}
}

// Save this tiddlywiki with the pending changes
function saveChanges()
{
	clearMessage();
	// Get the URL of the document
	var originalPath = document.location.toString();
	// Check we were loaded from a file URL
	if(originalPath.substr(0,5) != "file:")
		{
		alert(config.messages.notFileUrlError);
		displayTiddler(null,"SaveChanges",0,null,null,false,false);
		return;
		}
	// Remove any location part of the URL
	var hashPos = originalPath.indexOf("#");
	if(hashPos != -1)
		originalPath = originalPath.substr(0,hashPos);
	// Convert to a native file format assuming
	// "file:///x:/path/path/path..." - pc local file --> "x:\path\path\path..."
	// "file://///server/share/path/path/path..." - FireFox pc network file --> "\\server\share\path\path\path..."
	// "file:///path/path/path..." - mac/unix local file --> "/path/path/path..."
	// "file://server/share/path/path/path..." - pc network file --> "\\server\share\path\path\path..."
	var localPath;
	if(originalPath.charAt(9) == ":") // pc local file
		localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
		localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
		localPath = unescape(originalPath.substr(7));
	else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
		localPath = unescape(originalPath.substr(5));
	else // pc network file
		localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
	// Load the original file
	var original = loadFile(localPath);
	if(original == null)
		{
		alert(config.messages.cantSaveError);
		displayTiddler(null,"SaveChanges",0,null,null,false,false);
		return;
		}
	// Locate the storeArea div's
	var posOpeningDiv = original.indexOf(startSaveArea);
	var posClosingDiv = original.lastIndexOf(endSaveArea);
	if((posOpeningDiv == -1) || (posClosingDiv == -1))
		{
		alert(config.messages.invalidFileError.format([localPath]));
		return;
		}
	// Save the backup
	if(config.options.chkSaveBackups)
		{
		var backupPath = localPath.substr(0,localPath.lastIndexOf(".")) + "." + (new Date()).convertToYYYYMMDDHHMMSSMMM() + ".html";
		var backup = saveFile(backupPath,original);
		if(backup)
			displayMessage(config.messages.backupSaved,"file://" + backupPath);
		else
			alert(config.messages.backupFailed);
		}
	// Save Rss
	if(config.options.chkGenerateAnRssFeed)
		{
		var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
		var rssSave = saveFile(rssPath,convertUnicodeToUTF8(generateRss()));
		if(rssSave)
			displayMessage(config.messages.rssSaved,"file://" + rssPath);
		else
			alert(config.messages.rssFailed);
		}
	// Save empty template
	if(config.options.chkSaveEmptyTemplate)
		{
		var emptyPath,p;
		if((p = localPath.lastIndexOf("/")) != -1)
			emptyPath = localPath.substr(0,p) + "/empty.html";
		else if((p = localPath.lastIndexOf("\\")) != -1)
			emptyPath = localPath.substr(0,p) + "\\empty.html";
		else
			emptyPath = localPath + ".empty.html";
		var empty = original.substr(0,posOpeningDiv + startSaveArea.length) + convertUnicodeToUTF8(generateEmpty()) + original.substr(posClosingDiv);
		var emptySave = saveFile(emptyPath,empty);
		if(emptySave)
			displayMessage(config.messages.emptySaved,"file://" + emptyPath);
		else
			alert(config.messages.emptyFailed);
		}
	// Save new file
	var revised = original.substr(0,posOpeningDiv + startSaveArea.length) + 
				convertUnicodeToUTF8(allTiddlersAsHtml()) + "\n\t\t" +
				original.substr(posClosingDiv);
	var newSiteTitle = store.getTiddlerText("SiteTitle","TiddlyWiki").htmlEncode();
	revised = revised.replace(new RegExp("<title>[^<]*</title>", "im"),"<title>"+ newSiteTitle +"</title>");
	var save = saveFile(localPath,revised);
	if(save)
		{
		displayMessage(config.messages.mainSaved,"file://" + localPath);
		store.setDirty(false);
		}
	else
		alert(config.messages.mainFailed);
}

function generateRss()
{
	var s = [];
	var d = new Date();
	var u = store.getTiddlerText("SiteUrl",null);
	// Assemble the header
	s.push("<" + "?xml version=\"1.0\"?" + ">");
	s.push("<rss version=\"2.0\">");
	s.push("<channel>");
	s.push("<title>" + store.getTiddlerText("SiteTitle","").htmlEncode() + "</title>");
	if(u)
		s.push("<link>" + u.htmlEncode() + "</link>");
	s.push("<description>" + store.getTiddlerText("SiteSubtitle","").htmlEncode() + "</description>");
	s.push("<language>en-us</language>");
	s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
	s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
	s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
	s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
	s.push("<generator>TiddlyWiki " + version.major + "." + version.minor + "." + version.revision + "</generator>");
	// The body
	var tiddlers = store.getTiddlers("modified");
	var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
	for (var t=tiddlers.length-1; t>=n; t--)
		s.push(tiddlers[t].saveToRss(u));
	// And footer
	s.push("</channel>");
	s.push("</rss>");
	// Save it all
	return s.join("\n");
}

function generateEmpty()
{
	var systemTiddlers = store.getTaggedTiddlers("systemTiddlers");
	var savedTiddlers = [];
	for(var s=0;s<systemTiddlers.length;s++)
		savedTiddlers.push(systemTiddlers[s].saveToDiv());
	return savedTiddlers.join("\n");
}

function allTiddlersAsHtml()
{
	var savedTiddlers = [];
	var tiddlers = store.getTiddlers("modified");
	for (var t = 0; t < tiddlers.length; t++)
		savedTiddlers.push(tiddlers[t].saveToDiv());
	return savedTiddlers.join("\n");
}

// UTF-8 encoding rules:
// 0x0000 - 0x007F:	0xxxxxxx
// 0x0080 - 0x07FF:	110xxxxx 10xxxxxx
// 0x0800 - 0xFFFF:	1110xxxx 10xxxxxx 10xxxxxx

function convertUTF8ToUnicode(u)
{
	var s = "";
	var t = 0;
	var b1, b2, b3;
	while(t < u.length)
		{
		b1 = u.charCodeAt(t++);
		if(b1 < 0x80)
			s += String.fromCharCode(b1);
		else if(b1 < 0xE0)
			{
			b2 = u.charCodeAt(t++);
			s += String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
			}
		else
			{
			b2 = u.charCodeAt(t++);
			b3 = u.charCodeAt(t++);
			s += String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
			}
	}
	return(s);
}

function convertUnicodeToUTF8(s)
{
	if(saveUsingSafari)
		return s;
	else if(window.Components)
		return mozConvertUnicodeToUTF8(s);
	else
		return manualConvertUnicodeToUTF8(s);
}

function manualConvertUnicodeToUTF8(s)
{
	var u = [];
	for(var t=0;t<s.length;t++)
		{
		var c = s.charCodeAt(t);
		if(c <= 0x7F)
			u.push(String.fromCharCode(c));
		else
			u.push("&#" + c.toString() + ";");
		}
	return(u.join(""));
}

function mozConvertUnicodeToUTF8(s)
{
	netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
	var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
	converter.charset = "UTF-8";
	var u = converter.ConvertFromUnicode(s);
	var fin = converter.Finish();
	if(fin.length > 0)
		return u + fin;
	else
		return u;
}

function saveFile(fileUrl, content)
{
	var r = null;
	if(saveUsingSafari)
		r = safariSaveFile(fileUrl, content);
	if((r == null) || (r == false))
		r = mozillaSaveFile(fileUrl, content);
	if((r == null) || (r == false))
		r = ieSaveFile(fileUrl, content);
	return(r);
}

function loadFile(fileUrl)
{
	var r = null;
	if(saveUsingSafari)
		r = safariLoadFile(fileUrl);
	if((r == null) || (r == false))
		r = mozillaLoadFile(fileUrl);
	if((r == null) || (r == false))
		r = ieLoadFile(fileUrl);
	return(r);
}

// Returns null if it can't do it, false if there's an error, true if it saved OK
function ieSaveFile(filePath, content)
{
	try
		{
		var fso = new ActiveXObject("Scripting.FileSystemObject");
		}
	catch(e)
		{
		//alert("Exception while attempting to save\n\n" + e.toString());
		return(null);
		}
	var file = fso.OpenTextFile(filePath,2,-1,0);
	file.Write(content);
	file.Close();
	return(true);
}

// Returns null if it can't do it, false if there's an error, or a string of the content if successful
function ieLoadFile(filePath)
{
	try
		{
		var fso = new ActiveXObject("Scripting.FileSystemObject");
		}
	catch(e)
		{
		//alert("Exception while attempting to load\n\n" + e.toString());
		return(null);
		}
	var file = fso.OpenTextFile(filePath,1);
	var content = file.ReadAll();
	file.Close();
	return(content);
}

// Returns null if it can't do it, false if there's an error, true if it saved OK
function mozillaSaveFile(filePath, content)
{
	if(window.Components)
		try
			{
			netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
			var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
			file.initWithPath(filePath);
			if (!file.exists())
				file.create(0, 0664);
			var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
			out.init(file, 0x20 | 0x02, 00004,null);
			out.write(content, content.length);
			out.flush();
			out.close();
			return(true);
			}
		catch(e)
			{
			//alert("Exception while attempting to save\n\n" + e);
			return(false);
			}
	return(null);
}

// Returns null if it can't do it, false if there's an error, or a string of the content if successful
function mozillaLoadFile(filePath)
{
	if(window.Components)
		try
			{
			netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
			var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
			file.initWithPath(filePath);
			if (!file.exists())
				return(null);
			var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
			inputStream.init(file, 0x01, 00004, null);
			var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
			sInputStream.init(inputStream);
			return(sInputStream.read(sInputStream.available()));
			}
		catch(e)
			{
			//alert("Exception while attempting to load\n\n" + e);
			return(false);
			}
	return(null);
}

function safariFilenameToUrl(filename) {
	return ("file://" + filename);
}

function safariLoadFile(url)
{
	url = safariFilenameToUrl(url);
	var plugin = document.embeds["tiddlyWikiSafariSaver"];
	return plugin.readURL(url);
}

function safariSaveFile(url,content)
{
	url = safariFilenameToUrl(url);
	var plugin = document.embeds["tiddlyWikiSafariSaver"];
	return plugin.writeStringToURL(content,url);
}

// Lifted from http://developer.apple.com/internet/webcontent/detectplugins.html
function detectPlugin()
{
	var daPlugins = detectPlugin.arguments;
	var pluginFound = false;
	if (navigator.plugins && navigator.plugins.length > 0)
		{
		var pluginsArrayLength = navigator.plugins.length;
		for (pluginsArrayCounter=0; pluginsArrayCounter < pluginsArrayLength; pluginsArrayCounter++ )
			{
			var numFound = 0;
			for(namesCounter=0; namesCounter < daPlugins.length; namesCounter++)
				{
				if( (navigator.plugins[pluginsArrayCounter].name.indexOf(daPlugins[namesCounter]) >= 0) || 
						(navigator.plugins[pluginsArrayCounter].description.indexOf(daPlugins[namesCounter]) >= 0) )
					numFound++;
				}
			if(numFound == daPlugins.length)
				{
				pluginFound = true;
				break;
				}
			}
	}
	return pluginFound;
}

// ---------------------------------------------------------------------------------
// Event handlers
// ---------------------------------------------------------------------------------

function onEditKey(e)
{
	if (!e) var e = window.event;
	clearMessage();
	var consume = false;
	switch(e.keyCode)
		{
		case 13: // Ctrl-Enter
		case 10: // Ctrl-Enter on IE PC
		case 77: // Ctrl-Enter is "M" on some platforms
			if(e.ctrlKey && this.id && this.id.substr(0,13) == "editorWrapper")
				{
				blurTiddler(this.id.substr(13));
				saveTiddler(this.id.substr(13));
				consume = true;
				}
			break;
		case 27: // Escape
			if(this.id && this.id.substr(0,13) == "editorWrapper")
				{
				blurTiddler(this.id.substr(13));
				displayTiddler(null,this.id.substr(13),1,null,null,false,false);
				consume = true;
				}
			break;
		}
	e.cancelBubble = consume;
	if(consume)
		if (e.stopPropagation) e.stopPropagation();
	return(!consume);

}

// Event handler for clicking on a tiddly link
function onClickTiddlerLink(e)
{
	if (!e) var e = window.event;
	var theTarget = resolveTarget(e);
	var theLink = theTarget;
	var title = null;
	do {
		title = theLink.getAttribute("tiddlyLink");
		theLink = theLink.parentNode;
	} while(title == null && theLink != null);
	if(title)
		{
		var toggling = e.metaKey || e.ctrlKey;
		if(config.options.chkToggleLinks)
			toggling = !toggling;
		var opening;
		if(toggling && document.getElementById("tiddler" + title))
			closeTiddler(title,e.shiftKey || e.altKey);
		else
			displayTiddler(theTarget,title,0,null,null,true,e.shiftKey || e.altKey);
		}
	clearMessage();
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}

// Event handler for mouse over a tiddler
function onMouseOverTiddler(e)
{
	var tiddler;
	if(this.id.substr(0,7) == "tiddler")
		tiddler = this.id.substr(7);
	if(tiddler)
		selectTiddler(tiddler);
}

// Event handler for mouse out of a tiddler
function onMouseOutTiddler(e)
{
	var tiddler;
	if(this.id.substr(0,7) == "tiddler")
		tiddler = this.id.substr(7);
	if(tiddler)
		deselectTiddler(tiddler);
}

// Event handler for double click on a tiddler
function onDblClickTiddler(e)
{
	if(!readOnly)
		{
		clearMessage();
		if(document.selection)
			document.selection.empty();
		var tiddler;
		if(this.id.substr(0,7) == "tiddler")
			tiddler = this.id.substr(7);
		if(tiddler)
			displayTiddler(null,tiddler,2,null,null,false,false);
		}
}

// Event handler for clicking on toolbar close
function onClickToolbarClose(e)
{
	if (!e) var e = window.event;
	clearMessage();
	if(this.parentNode.id)
		closeTiddler(this.parentNode.id.substr(7),e.shiftKey || e.altKey);
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}

// Event handler for clicking on toolbar permalink
function onClickToolbarPermaLink(e)
{
	if(this.parentNode.id)
		{
		var title = this.parentNode.id.substr(7);
		var t = encodeURIComponent(String.encodeTiddlyLink(title));
		if(window.location.hash != t)
			window.location.hash = t;
		}
}

// Event handler for clicking on toolbar close
function onClickToolbarDelete(e)
{
	clearMessage();
	if(this.parentNode.id)
		deleteTiddler(this.parentNode.id.substr(7));
}

// Event handler for clicking on the toolbar references button
function onClickToolbarReferences(e)
{
	if (!e) var e = window.event;
	var theTarget = resolveTarget(e);
	var popup = createTiddlerPopup(this);
	if(popup && this.parentNode.id)
		{
		var title = this.parentNode.id.substr(7);
		var references = store.getReferringTiddlers(title);
		var c = false;
		for(var r=0; r<references.length; r++)
			if(references[r].title != title)
				{
				createTiddlyLink(popup,references[r].title,true);
				c = true;
				}
		if(!c)
			popup.appendChild(document.createTextNode(config.views.wikified.toolbarReferences.popupNone));
		}
	scrollToTiddlerPopup(popup,false);
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}

// Event handler for clicking on a tiddler tag
function onClickTag(e)
{
	if (!e) var e = window.event;
	var theTarget = resolveTarget(e);
	var popup = createTiddlerPopup(this);
	var tag = this.getAttribute("tag");
	var title = this.getAttribute("tiddler");
	if(popup && tag)
		{
		var tagged = store.getTaggedTiddlers(tag);
		var c = false;
		for(var r=0;r<tagged.length;r++)
			if(tagged[r].title != title)
				{
				createTiddlyLink(popup,tagged[r].title,true);
				c = true;
				}
		var lingo = config.views.wikified.tag;
		if(c)
			{
			popup.insertBefore(document.createElement("hr"),popup.firstChild);
			var openAll = createTiddlyButton(null,lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
			openAll.setAttribute("tag",tag);
			popup.insertBefore(openAll,popup.firstChild);
			}
		else
			popup.appendChild(document.createTextNode(lingo.popupNone.format([tag])));
		}
	scrollToTiddlerPopup(popup,false);
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}

// Event handler for 'open all' on a tiddler popup
function onClickTagOpenAll(e)
{
	if (!e) var e = window.event;
	var tag = this.getAttribute("tag");
	var tagged = store.getTaggedTiddlers(tag);
	for(var t=tagged.length-1; t>=0; t--)
		displayTiddler(this,tagged[t].title,0,null,null,false,e.shiftKey || e.altKey);
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}

// Event handler for clicking on the 'add tag' button
function onClickAddTag(e)
{
	if (!e) var e = window.event;
	var theTarget = resolveTarget(e);
	var popup = createTiddlerPopup(this);
	var tiddler = this.getAttribute("tiddler");
	var tags = store.getTags();
	var lingo = config.views.editor.tagChooser;
	if(tags.length == 0)
		createTiddlyElement(popup,"div",null,null,lingo.popupNone);
	for (t=0; t<tags.length; t++)
		{
		var theTag = createTiddlyButton(popup,tags[t][0],lingo.tagTooltip.format([tags[t][0]]),onClickAddTagPopup);
		theTag.setAttribute("tag",tags[t][0]);
		theTag.setAttribute("tiddler",tiddler);
		}
	scrollToTiddlerPopup(popup,false);
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}

// Event handler for clicking on a tag in the 'add tag' popup
function onClickAddTagPopup(e)
{
	if (!e) var e = window.event;
	var theTarget = resolveTarget(e);
	var tiddler = this.getAttribute("tiddler");
	var tag = this.getAttribute("tag");
	var tagsBox = document.getElementById("editorTags" + tiddler);
	if(tagsBox)
		tagsBox.value += " " + String.encodeTiddlyLink(tag);
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
	return(false);
}

// Event handler for clicking on toolbar close
function onClickToolbarEdit(e)
{
	clearMessage();
	if(this.parentNode.id)
		displayTiddler(null,this.parentNode.id.substr(7),2,null,null,false,false);
}

// Event handler for clicking on toolbar save
function onClickToolbarSave(e)
{
	if(this.parentNode.id)
		saveTiddler(this.parentNode.id.substr(7));
}

// Event handler for clicking on toolbar save
function onClickToolbarUndo(e)
{
	if(this.parentNode.id)
		displayTiddler(null,this.parentNode.id.substr(7),1,null,null,false,false);
}

// Eek... it's bad that this is done via a function rather than a normal, copy-able href
function onClickPermaView()
{
	var tiddlerDisplay = document.getElementById("tiddlerDisplay");
	var links = [];
	for(var t=0;t<tiddlerDisplay.childNodes.length;t++)
		{
		var tiddlerName = tiddlerDisplay.childNodes[t].id.substr(7);
		links.push(String.encodeTiddlyLink(tiddlerName));
		}
	window.location.hash = encodeURIComponent(links.join(" "));
}

// ---------------------------------------------------------------------------------
// Animation engine
// ---------------------------------------------------------------------------------

function Animator()
{
	this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
	this.timerID; // ID of the timer used for animating
	this.animations = []; // List of animations in progress
	return this;
}

// Start animation engine
Animator.prototype.startAnimating = function() // Variable number of arguments
{
	for(var t=0; t<arguments.length; t++)
		this.animations.push(arguments[t]);
	if(this.running == 0)
		{
		var me = this;
		this.timerID = window.setInterval(function() {me.doAnimate(me);},25);
		}
	this.running += arguments.length;
}

// Perform an animation engine tick, calling each of the known animation modules
Animator.prototype.doAnimate = function(me)
{
	var a = 0;
	while(a<me.animations.length)
		{
		var animation = me.animations[a];
		animation.progress += animation.step;
		if(animation.progress < 0 || animation.progress > 1)
			{
			animation.stop();
			me.animations.splice(a,1);
			if(--me.running == 0)
				window.clearInterval(me.timerID);
			}
		else
			{
			animation.tick();
			a++;
			}
		}
}

// Map a 0..1 value to 0..1, but slow down at the start and end
Animator.slowInSlowOut = function(progress)
{
	return(1-((Math.cos(progress * Math.PI)+1)/2));
}

// ---------------------------------------------------------------------------------
// Zoomer animation
// ---------------------------------------------------------------------------------

function Zoomer(text,startElement,targetElement,slowly)
{
	this.element = document.createElement("div");
	this.element.appendChild(document.createTextNode(text));
	this.element.className = "zoomer";
	document.body.appendChild(this.element);
	this.startElement = startElement;
	this.startLeft = findPosX(this.startElement);
	this.startTop = findPosY(this.startElement);
	this.startWidth = this.startElement.offsetWidth;
	this.startHeight = this.startElement.offsetHeight;
	this.targetElement = targetElement;
	this.targetLeft = findPosX(this.targetElement);
	this.targetTop = findPosY(this.targetElement);
	this.targetWidth = this.targetElement.offsetWidth;
	this.targetHeight = this.targetElement.offsetHeight;
	this.progress = 0;
	this.step = slowly ? config.animSlow : config.animFast;
	this.targetElement.style.opacity = 0;
	return this;
}

Zoomer.prototype.stop = function()
{
	this.element.parentNode.removeChild(this.element);
	this.targetElement.style.opacity = 1;
}

Zoomer.prototype.tick = function()
{
	var f = Animator.slowInSlowOut(this.progress);
	this.element.style.left = this.startLeft + (this.targetLeft-this.startLeft) * f + "px";
	this.element.style.top = this.startTop + (this.targetTop-this.startTop) * f + "px";
	this.element.style.width = this.startWidth + (this.targetWidth-this.startWidth) * f + "px";
	this.element.style.height = this.startHeight + (this.targetHeight-this.startHeight) * f + "px";
	this.element.style.display = "block";
	this.targetElement.style.opacity = this.progress;
	this.targetElement.style.filter = "alpha(opacity:" + this.progress * 100 + ")";
}

// ---------------------------------------------------------------------------------
// Scroller animation
// ---------------------------------------------------------------------------------

function Scroller(targetElement,slowly)
{
	this.targetElement = targetElement;
	this.startScroll = findScrollY();
	this.targetScroll = ensureVisible(targetElement);
	this.progress = 0;
	this.step = slowly ? config.animSlow : config.animFast;
	return this;
}

Scroller.prototype.stop = function()
{
	window.scrollTo(0,this.targetScroll);
}

Scroller.prototype.tick = function()
{
	var f = Animator.slowInSlowOut(this.progress);
	window.scrollTo(0,this.startScroll + (this.targetScroll-this.startScroll) * f);
}

// ---------------------------------------------------------------------------------
// Slider animation
// ---------------------------------------------------------------------------------

// deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
function Slider(element,opening,slowly,deleteMode)
{
	this.element = element;
	element.style.display = "block";
	this.deleteMode = deleteMode;
	this.element.style.height = "auto";
	this.realHeight = element.offsetHeight;
	this.opening = opening;
	this.step = slowly ? config.animSlow : config.animFast;
	if(opening)
		{
		this.progress = 0;
		element.style.height = "0px";
		element.style.display = "block";
		}
	else
		{
		this.progress = 1;
		this.step = -this.step;
		}
	element.style.overflow = "hidden";
	return this;
}

Slider.prototype.stop = function()
{
	if(this.opening)
		this.element.style.height = "auto";
	else
		{
		switch(this.deleteMode)
			{
			case "none":
				this.element.style.display = "none";
				break;
			case "all":
				this.element.parentNode.removeChild(this.element);
				break;
			case "children":
				removeChildren(this.element);
				break;
			}
		}
}

Slider.prototype.tick = function()
{
	var f = Animator.slowInSlowOut(this.progress);
	var h = this.realHeight * f;
	this.element.style.height = h + "px";
	this.element.style.opacity = f;
}

// ---------------------------------------------------------------------------------
// Augmented methods for the JavaScript Number(), Array() and String() objects
// ---------------------------------------------------------------------------------

// Clamp a number to a range
Number.prototype.clamp = function(min,max)
{
	c = this;
	if(c < min)
		c = min;
	if(c > max)
		c = max;
	return c;
}

// Find an entry in an array. Returns the array index or null
Array.prototype.find = function(item)
{
	var p = null;
	for(var t=0; t<this.length; t++)
		if(this[t] == item)
			{
			p = t;
			break;
			}
	return p;
}

// Push a new value into an array only if it is not already present in the array. If the optional unique parameter is false, it reverts to a normal push
Array.prototype.pushUnique = function(item,unique)
{
	if(unique != undefined && unique == false)
		this.push(item);
	else
		{
		if(this.find(item) == null)
			this.push(item);
		}
}

// Get characters from the right end of a string
String.prototype.right = function(n)
{
	if(n < this.length)
		return this.slice(this.length-n);
	else
		return this;
}

// Trim whitespace from both ends of a string
String.prototype.trim = function()
{
	var regexpTrim = new RegExp("^\\s*(.*)\\s*$","mg");
	return(this.replace(regexpTrim,"$1"));
}

// Substitute substrings from an array into a format string that includes '%1'-type specifiers
String.prototype.format = function(substrings)
{
	var subRegExp = new RegExp("(?:%(\\d+))","mg");
	var currPos = 0;
	var r = [];
	do {
		var match = subRegExp.exec(this);
		if(match && match[1])
			{
			if(match.index > currPos)
				r.push(this.substring(currPos,match.index));
			r.push(substrings[parseInt(match[1])]);
			currPos = subRegExp.lastIndex;
			}
	} while(match);
	if(currPos < this.length)
		r.push(this.substring(currPos,this.length));
	return r.join("");
}

// Escape any special RegExp characters with that character preceded by a backslash
String.prototype.escapeRegExp = function()
{
	return(this.replace(new RegExp("[\\\\\\^\\$\\*\\+\\?\\(\\)\\=\\!\\|\\,\\{\\}\\[\\]\\.]","g"),"\\$&"));
}

// Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
String.prototype.htmlEncode = function()
{
	var regexpAmp = new RegExp("&","mg");
	var regexpLessThan = new RegExp("<","mg");
	var regexpGreaterThan = new RegExp(">","mg");
	var regexpQuote = new RegExp("\"","mg");
	return(this.replace(regexpAmp,"&amp;").replace(regexpLessThan,"&lt;").replace(regexpGreaterThan,"&gt;").replace(regexpQuote,"&quot;"));
}

// Process a string list of macro parameters into an array. Parameters can be quoted with "", '', [[]] or left unquoted (and therefore space-separated)
String.prototype.readMacroParams = function()
{
	var regexpMacroParam = new RegExp("(?:\\s*)(?:(?:\"([^\"]*)\")|(?:'([^']*)')|(?:\\[\\[([^\\]]*)\\]\\])|([^\"'\\s]\\S*))","mg");
	var params = [];
	do {
		var match = regexpMacroParam.exec(this);
		if(match)
			{
			if(match[1]) // Double quoted
				params.push(match[1]);
			else if(match[2]) // Single quoted
				params.push(match[2]);
			else if(match[3]) // Double-square-bracket quoted
				params.push(match[3]);
			else if(match[4]) // Unquoted
				params.push(match[4]);
			}
	} while(match);
	return params;
}

// Process a string list of tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
String.prototype.readBracketedList = function(unique)
{
	var bracketedPattern = "\\[\\[([^\\]]+)\\]\\]";
	var unbracketedPattern = "[^\\s$]+";
	var pattern = "(?:" + bracketedPattern + ")|(" + unbracketedPattern + ")";
	var re = new RegExp(pattern,"mg");
	var tiddlerNames = [];
	do {
		var match = re.exec(this);
		if(match)
			{
			if(match[1]) // Bracketed
				tiddlerNames.pushUnique(match[1],unique);
			else if(match[2]) // Unbracketed
				tiddlerNames.pushUnique(match[2],unique);
			}
	} while(match);
	return(tiddlerNames);
}

// Static method to bracket a string with double square brackets if it contains a space
String.encodeTiddlyLink = function(title)
{
	if(title.indexOf(" ") == -1)
		return(title);
	else
		return("[[" + title + "]]");
}

// Static method to left-pad a string with 0s to a certain width
String.zeroPad = function(n,d)
{
	var s = n.toString();
	if(s.length < d)
		s = "000000000000000000000000000".substr(0,d-s.length) + s;
	return(s);
}

// ---------------------------------------------------------------------------------
// RGB colour object
// ---------------------------------------------------------------------------------

// Construct an RGB colour object from a '#rrggbb' or 'rgb(n,n,n)' string or from separate r,g,b values
function RGB(r,g,b)
{
	this.r = 0;
	this.g = 0;
	this.b = 0;
	if(typeof r == "string")
		{
		if(r.substr(0,1) == "#")
			{
			this.r = parseInt(r.substr(1,2),16)/255;
			this.g = parseInt(r.substr(3,2),16)/255;
			this.b = parseInt(r.substr(5,2),16)/255;
			}
		else
			{
			var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/ ;
			var c = r.match(rgbPattern);
			if (c)
				{
				this.r = parseInt(c[1],10)/255;
				this.g = parseInt(c[2],10)/255;
				this.b = parseInt(c[3],10)/255;
				}
			}
		}
	else
		{
		this.r = r;
		this.g = g;
		this.b = b;
		}
	return this;
}

// Mixes this colour with another in a specified proportion
// c = other colour to mix
// f = 0..1 where 0 is this colour and 1 is the new colour
// Returns an RGB object
RGB.prototype.mix = function(c,f)
{
	return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
}

// Return an rgb colour as a #rrggbb format hex string
RGB.prototype.toString = function()
{
	var r = this.r.clamp(0,1);
	var g = this.g.clamp(0,1);
	var b = this.b.clamp(0,1);
	return("#" + ("0" + Math.floor(r * 255).toString(16)).right(2) +
				 ("0" + Math.floor(g * 255).toString(16)).right(2) +
				 ("0" + Math.floor(b * 255).toString(16)).right(2));
}

// ---------------------------------------------------------------------------------
// Augmented methods for the JavaScript Date() object
// ---------------------------------------------------------------------------------

// Substitute date components into a string
Date.prototype.formatString = function(template)
{
	template = template.replace("YYYY",this.getFullYear());
	template = template.replace("YY",String.zeroPad(this.getFullYear()-2000,2));
	template = template.replace("MMM",config.messages.dates.months[this.getMonth()]);
	template = template.replace("0MM",String.zeroPad(this.getMonth()+1,2));
	template = template.replace("MM",this.getMonth()+1);
	template = template.replace("DDD",config.messages.dates.days[this.getDay()]);
	template = template.replace("0DD",String.zeroPad(this.getDate(),2));
	template = template.replace("DD",this.getDate());
	template = template.replace("hh",this.getHours());
	template = template.replace("mm",this.getMinutes());
	template = template.replace("ss",this.getSeconds());
	return template;
}

// Convert a date to UTC YYYYMMDDHHMM string format
Date.prototype.convertToYYYYMMDDHHMM = function()
{
	return(String.zeroPad(this.getFullYear(),4) + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2));
}

// Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
{
	return(String.zeroPad(this.getFullYear(),4) + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + "." + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2) + String.zeroPad(this.getSeconds(),2) + String.zeroPad(this.getMilliseconds(),4));
}

// Static method to create a date from a UTC YYYYMMDDHHMM format string
Date.convertFromYYYYMMDDHHMM = function(d)
{
	var theDate = new Date(parseInt(d.substr(0,4),10),
							parseInt(d.substr(4,2),10)-1,
							parseInt(d.substr(6,2),10),
							parseInt(d.substr(8,2),10),
							parseInt(d.substr(10,2),10),0,0);
	return(theDate);
}

// ---------------------------------------------------------------------------------
// DOM utilities - many derived from www.quirksmode.org 
// ---------------------------------------------------------------------------------

function createTiddlyElement(theParent,theElement,theID,theClass,theText)
{
	var e = document.createElement(theElement);
	if(theClass != null)
		e.className = theClass;
	if(theID != null)
		e.setAttribute("id",theID);
	if(theText != null)
		e.appendChild(document.createTextNode(theText));
	if(theParent != null)
		theParent.appendChild(e);
	return(e);
}

function createTiddlyButton(theParent,theText,theTooltip,theAction,theClass,theId)
{
	var theButton = document.createElement("a");
	theButton.className = "button";
	if(theAction)
		{
		theButton.onclick = theAction;
		theButton.setAttribute("href","JavaScript:;");
		}
	theButton.setAttribute("title",theTooltip);
	if(theText)
		theButton.appendChild(document.createTextNode(theText));
	if(theClass)
		theButton.className = theClass;
	if(theId)
		theButton.id = theId;
	if(theParent)
		theParent.appendChild(theButton);
	return(theButton);
}

function createTiddlyLink(place,title,includeText)
{
	var text = includeText ? title : null;
	var subTitle;
	var tiddler = store.tiddlers[title];
	if(tiddler)
		subTitle = tiddler.getSubtitle();
	else
		subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
	var theClass = tiddler ? "tiddlyLinkExisting tiddlyLink" : "tiddlyLinkNonExisting tiddlyLink";
	var btn = createTiddlyButton(place,text,subTitle,onClickTiddlerLink,theClass);
	btn.setAttribute("tiddlyLink",title);
	return(btn);
}

function createExternalLink(place,url)
{
	var theLink = document.createElement("a");
	theLink.className = "externalLink";
	theLink.href = url;
	theLink.title = config.messages.externalLinkTooltip.format([url]);
	if(config.options.chkOpenInNewWindow)
		theLink.target = "_blank";
	place.appendChild(theLink);
	return(theLink);
}

// Find the tiddler instance (if any) containing a specified element
function findContainingTiddler(e)
{
	if(e == null)
		return(null);
	do {
		if(e != document)
			{
			if(e.id)
				if(e.id.substr(0,7) == "tiddler")
					return(e);
			}
		e = e.parentNode;
	} while(e != document);
	return(null);
}

// Resolve the target object of an event
function resolveTarget(e)
{
	var obj;
	if (e.target)
		obj = e.target;
	else if (e.srcElement)
		obj = e.srcElement;
	if (obj.nodeType == 3) // defeat Safari bug
		obj = obj.parentNode;
	return(obj);
}

// Return the content of an element as plain text with no formatting
function getElementText(elementID)
{
	var e = document.getElementById(elementID);
	var text = "";
	if(e.innerText)
		text = e.innerText;
	else if(e.textContent)
		text = e.textContent;
	return text;
}

// Get the scroll position for window.scrollTo necessary to scroll a given element into view
function ensureVisible(e)
{
	var posTop = findPosY(e);
	var posBot = posTop + e.offsetHeight;
	var winTop = findScrollY();
	var winHeight = findWindowHeight();
	var winBot = winTop + winHeight;
	if(posTop < winTop)
		return(posTop);
	else if(posBot > winBot)
		{
		if(e.offsetHeight < winHeight)
			return(posTop - (winHeight - e.offsetHeight));
		else
			return(posTop);
		}
	else
		return(winTop);
}

// Get the current height of the display window
function findWindowHeight()
{
	return(window.innerHeight ? window.innerHeight : document.body.clientHeight);
}

// Get the current vertical page scroll position
function findScrollY()
{
	return(window.scrollY ? window.scrollY : document.body.scrollTop);
}

function findPosX(obj)
{
	var curleft = 0;
	while (obj.offsetParent)
		{
		curleft += obj.offsetLeft;
		obj = obj.offsetParent;
		}
	return curleft;
}

function findPosY(obj)
{
	var curtop = 0;
	while (obj.offsetParent)
		{
		curtop += obj.offsetTop;
		obj = obj.offsetParent;
		}
	return curtop;
}

// Create a non-breaking space
function insertSpacer(place)
{
	var e = document.createTextNode(String.fromCharCode(160));
	if(place)
		place.appendChild(e);
	return e;
}

// Remove all children of a node
function removeChildren(e)
{
	while(e.hasChildNodes())
		e.removeChild(e.firstChild);
}

// Add a stylesheet, replacing any previous custom stylesheet
function setStylesheet(s,id)
{
	if(!id)
		id = "customStyleSheet";
	var n = document.getElementById(id);
	if(document.createStyleSheet) // Test for IE's non-standard createStyleSheet method
		{
		if(n)
			n.parentNode.removeChild(n);
		// This failed without the &nbsp;
		document.body.insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
		}
	else
		{
		if(n)
			n.replaceChild(document.createTextNode(s),n.firstChild);
		else
			{
			var n = document.createElement("style");
			n.type = "text/css";
			n.id = id;
			n.appendChild(document.createTextNode(s));
			document.getElementsByTagName("head")[0].appendChild(n);
			}
		}
}

// ---------------------------------------------------------------------------------
// End of scripts
// ---------------------------------------------------------------------------------

</script>
<style type="text/css">

body {
	background-color: #ffffff;
	font-size: 9pt;
	font-family: verdana,arial,helvetica;
	margin: 0em 0em 0em 0em;
	padding: 0em 0em 0em 0em;
	position: relative;
	z-index: 0;
}

a:link, a:visited {
	text-decoration: none;
}

a:hover, a:active {
	text-decoration: none;
}

#contentWrapper {
	position: relative;
}

#header {
}

#titleLine {
	color: #ffffff;
	background-color: #330000;
	padding: 5em 1em 1em 1em;
}

#titleLine a {
	color: #CCFF66;
}

#siteTitle {
	font-size: 26pt;
}

#siteSubtitle {
	padding-left: 1em;
	font-size: 10pt;
}

#mainMenu {
	position: absolute;
	left: 0em;
	width: 10em;
	line-height: 166%;
	padding: 1.5em 0.5em 0.5em 0.5em;
	font-size: 10pt;
	color: black;
	text-align: right;
}

#mainMenu .tiddlyLink {
	color: #996633;
}

#mainMenu .tiddlyLink:hover {
	background-color: #996633;
	color: #ffffff;
}

#mainMenu .externalLink {
	color: #996633;
	text-decoration: underline;
}

#mainMenu .externalLink:hover {
	background-color: #996633;
	color: #ffffff;
}

#mainMenu .button {
	color: #993300;
}

#mainMenu .button:hover {
	color: #ccff66;
	background-color: #993300;
}

#displayArea {
	margin: 1em 17em 0em 14em;
}

#tiddlerDisplay {
}

#messageArea {
	background-color: #993300;
	color: #ffffff;
	padding: 0.5em 0.5em 0.5em 0.5em;
	display: none;
}

#messageArea a:link, #messageArea a:visited {
	display: inline;
	text-decoration: underline;
	color: #cc9900;
}

#messageArea a:hover {
	color: #996633;
}

#messageArea a:active {
	color: #ffffff;
}

#popup {
	display: none;
	position: absolute;
	line-height: 110%;
	font-size: 8pt;
	color: #ccff66;
	background-color: #993300;
	padding: 0.25em 0.25em 0.25em 0.25em;
	border-right: 1px solid #330000;
	border-bottom: 1px solid #330000;
	z-index: 10;
}

#popup a {
	display: block;
	color: #ccff66;
	padding: 1px 1px 1px 1px;
}

#popup a:hover {
	background-color: #ccff66;
	color: #330000;
}

#popup hr {
	border-top: solid 1px #ccff66;
	border-left: none;
	border-right: none;
	border-bottom: none;
	height: 1px;
	width: 5em;
	left: 0em;
	color: #ccff66;
}

.tabset {
	padding: 1em 0em 0em 0.5em;
}

.tab {
	margin: 0em 0em 0em 0.25em;
	padding: 2px 2px 2px 2px;
}

.tabSelected {
	background-color: #eeeeaa;
}

.tabUnselected {
	background-color: #cc9900;
}

.tab:hover {
}

.tab:active {
}

.tabContents {
	padding: 0.5em 0.5em 0.5em 0.5em;
	background-color: #eeeeaa;
}

.tabContents ul, .tabContents ol {
	margin: 0;
	padding: 0;
}

.tabContents li {
	list-style: none;
}

.tabContents li.listLink {
   margin-left: .75em;
}

.tiddler {
	padding: 0em 0em 0em 0em;
}

.selectedTiddler {
	padding: 1em 1em 0em 1em;
	font-size: 9pt;
}

.unselectedTiddler {
	padding: 1em 1em 0em 1em;
	font-size: 9pt;
}

.tiddler a.tiddlyLinkExisting {
	font-weight: bold;
}

.tiddler a.tiddlyLinkNonExisting {
	font-style: italic;
}

.tiddler a.externalLink {
	text-decoration: underline;
}

.tiddler .button {
	padding: 0.2em 0.4em 0.2em 0.4em;
	color: #993300;
}

.tiddler .button:hover {
	text-decoration: none;
	color: #ccff66;
	background-color: #993300;
}

.tiddler .button:active {
	color: #ffffff;
	background-color: #cc9900;
}

.title {
	font-size: 10pt;
	font-weight: bold;
}

.selectedTiddler .title {
}

.toolbar {
	text-align: right;
	font-weight: normal;
	font-size: 8pt;
	padding: 0em 0em 0em 2em;
	color: #aaaaaa;
	visibility: hidden;
}

.toolbar #popup {
	text-align: left;
}

.selectedTiddler .toolbar {
	visibility: visible;
}

.footer {
	font-weight: normal;
	font-size: 8pt;
	margin: 0em 0em 0em 0em;
	padding: 0em 0em 0em 0em;
	color: #dddddd;
}

.selectedTiddler .footer {
	color: #888888;
}

.body {
	padding-top: 0.5em;
}

.viewer {
	color: #000000;
	line-height: 140%;
}

.viewer a:link, .body a:visited {
	color: #996633;
}

.viewer a:hover {
	color: #ffffff;
	background-color: #996633;
}

.viewer .button {
	margin: 0em 0.25em 0em 0.25em;
	padding: 0em 0.25em 0em 0.25em;
	background-color: #cc9900;
	color: #330000;
	border-right: 1px solid #33000;
	border-bottom: 1px solid #33000;
}

.viewer .button:hover {
	background-color: #eeeeaa;
	color: #cc9900;
}

.viewer blockquote {
	font-size: 8pt;
	line-height: 150%;
	border-left: 3px solid #666666;
	padding-left: 0.8em;
	margin-left: 2.5em;
}

.viewer ul {
	margin-left: 0.5em;
	padding-left: 1.5em;
}

.viewer ol {
	margin-left: 0.5em;
	padding-left: 1.5em;
}

.viewer h1,.viewer h2,.viewer h3,.viewer h4,.viewer h5 {
	font-weight: bold;
	text-decoration: none;
	background-color: #cccc99;
	padding-left: 0.4em;
}

.viewer h1 {
	font-size: 12pt;
}

.viewer h2 {
	font-size: 11pt;
}

.viewer h3 {
	font-size: 10pt;
}

.viewer h4 {
	font-size: 9pt;
}

.viewer h5 {
	font-size: 8pt;
}

.viewer table {
	border-collapse: collapse;
	border: 2px solid #303030;
	margin-left: 1.0em;
	margin-right: 1.0em;
	margin-top: 0.8em;
	margin-bottom: 0.8em;
	font-size: 100%;
}

.viewer th {
	background-color: #999966;
	border: 1px solid #606060;
	color: #ffffff;
	padding: 3px;
}

.viewer td, .viewer tr {
	border: 1px solid #606060;
	padding: 3px;
}

.viewer caption {
	padding: 3px;
}

.viewer pre {
	padding: 0.5em 0.5em 0.5em 0.5em;
	margin-left: 0.5em;
	font-size: 100%;
	line-height: 1.4em;
	color: #000000;
	border: 1px solid #996633;
	background-color: #eeeeaa;
	overflow: auto;
}

.viewer code {
	font-size: 100%;
	line-height: 1.4em;
	color: #663300;
}

.viewer hr {
	border-top: dashed 1px #606060;
	border-left: none;
	border-right: none;
	border-bottom: none;
	height: 1px;
	color: #666666;
}

.highlight, .marked {
	color: #000000;
	background-color: #ffe72f;
}

.editor {
	font-size: 8pt;
	color: #402C74;
	font-weight: normal;
}

.editor input {
	display: block;
	border: 1px solid black;
	width: 100%;
}

.editor textarea {
	display: block;
	font: inherit;
	border: 1px solid black;
	width: 100%;
}

.editorFooter {
	padding: 0.25em 0em 0.25em 0em;
	font-size: 8pt;
	color: #aaaaaa;
}

.editorFooter A {
	padding: 0.2em 0.4em 0.2em 0.4em;
	color: #993300;
}

.editorFooter A:hover {
	text-decoration: none;
	color: #ccff66;
	background-color: #993300;
}

.editorFooter A:active {
	color: #ffffff;
	background-color: #cc9900;
}

#sidebar {
	position: absolute;
	right: 0em;
	width: 16em;
	color: #000000;
	font-size: 8pt;
}

#sidebarOptions {
	padding-top: 0.5em;
	background-color: #cc9900;
}

#sidebarOptions .button {
	color: #993300;
	padding: 0.3em 0.2em 0.3em 1em;
	display: block;
}

#sidebarOptions .button:hover {
	color: #ccff66;
	background-color: #993300;
}

#sidebarOptions .button:active {
	color: #993300;
	background-color: #ccff66;
}

#sidebarOptions input {
	margin: 0.4em 0em 0.3em 1em;
}

#sidebarOptions .sliderPanel {
	padding: 0.5em 0.5em 0.5em 0.5em;
	font-size: 7pt;
	background-color: #eeeeaa;
}

#sidebarOptions .sliderPanel A {
	color: #993300;
	font-weight: bold;
}

#sidebarOptions .sliderPanel A:hover {
	color: #ccff66;
	background-color: #993300;
}

#sidebarOptions .sliderPanel A:active {
	color: #993300;
	background-color: #ccff66;
}

#sidebarOptions .sliderPanel input {
	margin: 0em 0em 0.3em 0em;
}

.sidebarSubHeading {
	font-size: 7pt;
	color: #330000;
}

#sidebarTabs {
	background-color: #cc9900;
}

#sidebarTabs .tabSelected {
	color: #ccff66;
	background-color: #996633;
	position: relative;
	top: -2px;
}

#sidebarTabs .tabUnselected {
	color: #ccff66;
	background-color: #993300;
}

#sidebarTabs .tabContents {
	background-color: #996633;
}

#sidebarTabs .txtMoreTab .tabSelected {
	background-color: #993300;
}

#sidebarTabs .txtMoreTab .tabUnselected {
	background-color: #330000;
}

#sidebarTabs .txtMoreTab .tabContents {
	background-color: #993300;
}

#sidebarTabs .tabContents .tiddlyLink {
	color: #ccff66;
}

#sidebarTabs .tabContents .tiddlyLink:hover {
	background-color: #ccff66;
	color: #330000;
}

#sidebarTabs .tabContents .button {
	color: #ccff66;
	padding: 0em 0em 0em 0em;
	display: inline;
}

#sidebarTabs .tabContents .button:hover {
	color: #330000;
	background-color: #ccff66;
}

#licensePanel {
	padding: 0.5em 0em 0.5em 0em;
}

#licensePanel A {
	display: block;
	padding: 0.2em 0.2em 0.2em 0.2em;
	color: #993300;
}

#licensePanel A:hover {
	text-decoration: none;
	color: #ccff66;
	background-color: #993300;
}

#licensePanel A:active {
	color: #993300;
	background-color: #ccff66;
}

#storeArea, #copyright {
	display: none;
}

.sparkline {
	background-color: #eeeeaa;
	border: none;
	line-height: 100%;
}

.sparktick {
	background-color: #993300;
	outline: 0;
}

.errorNoSuchMacro {
	color: #ffff00;
	background-color: #ff0000;
}

.zoomer {
	font-size: 10pt;
	display: none;
	color: #996633;
	position: absolute;
	padding: 1em 1em 1em 1em;
	border: 1px solid #996633;
}

#saveTest {
	display: none;
}

@media print {

#mainMenu, #sidebar, #messageArea {
	display: none ! important;
}

#displayArea {
	margin: 1em 1em 0em 1em;
}

}

</style>
<noscript>
<style type="text/css">

#contentWrapper {
	display: none;
}

#storeArea {
	display: block;
	margin: 4em 17em 3em 17em;
}

#storeArea div {
 padding: 0.5em; 0.5em; 0.5em; 0.5em;
 margin: 1em 0em 0em 0em;
 border-color: #f0f0f0 #606060 #404040 #d0d0d0;
 border-style: solid;
 border-width: 2px;
 height: 7em;
 overflow: auto;
}

#javascriptWarning {
	width: 100%;
	text-align: center;
	font-weight: bold;
	background-color: #dd1100;
	color: #ffffff;
	padding:1em 0em 1em 0em;
}

</style>
</noscript>
</head>
<body onload="main();" onunload="checkUnsavedChanges();">
	<script>
	if (detectPlugin("TiddlyWiki Saver"))
		{
		document.write('<embed style="display: none" name="tiddlyWikiSafariSaver" width="0" height="0" type="application/x-webkit-tiddlywiki-saver"></embed>'); 
		saveUsingSafari = true;
		}
	</script>
	<div id="copyright">
	Welcome to TiddlyWiki by Jeremy Ruston, Copyright &copy; 2005 Osmosoft Limited
	</div>
	<noscript>
		<div id="javascriptWarning">This page requires JavaScript to function properly</div>
	</noscript>
	<div id="saveTest"></div>
	<div id="contentWrapper">
		<div id="header">
			<div id="titleLine">
				<span id="siteTitle"></span>
				<span id="siteSubtitle"></span>
			</div>
		</div>
		<div id="sidebar">
			<div id="sidebarOptions"></div>
			<div id="sidebarTabs"></div>
		</div>
		<div id="mainMenu"></div>
		<div id="displayArea">
			<div id="messageArea"></div>
			<div id="tiddlerDisplay"></div>
		</div>
	</div>
	<div id="storeArea"><div tiddler="SiteTitle" modified="200506101229" modifier="LiamHatton" tags="">php-bulksms</div>
<div tiddler="SiteSubtitle" modified="200506101230" modifier="LiamHatton" tags="">website &amp; users' manual for the php class</div>
<div tiddler="RelatedLinks" modified="200506101259" modifier="LiamHatton" tags="">Official links directly maintained by the author of this class (LiamHatton):\n* ''[[Official website for php-bulksms|http://php-bulksms.liam.hatton.name]]''\n* ''[[PHP Classes project page|http://lhatton.users.phpclasses.org/browse/package/2257.html]]''\n* ''[[Public forum on Yahoo! Groups:|http://groups.yahoo.com/group/php-bulksms/]]'' used for new version announcements and user support,\n* ''[[Project listing on freshmeat.net|http://freshmeat.net/projects/php-bulksms/]]''\n\nThe following web sites are not affiliated with this class or the author:\n* ''[[BulkSMS website:|http://www.bulksms.com/]]'' website for the service this PHP class is written for,\n* ''[[BulkSMS EAPI documentation:|http://bulksms.vsms.net/docs/eapi/]]'' documentation for user development based on the BulkSMS HTTP API,\n* ''[[Net::SMS::BulkSMS:|http://search.cpan.org/~pedwards/Net-SMS-BulkSMS-1.00/]]'' Perl API on CPAN,\n* ''[[sendbulksms:|http://sendbulksms.sourceforge.net/]]'' Perl API,\n* ''[[pybulksms:|http://botanicus.net/dw/homepage.php]]'' Python API.</div>
<div tiddler="TiddlyWiki" modified="200506101652" modifier="LiamHatton" tags="">A TiddlyWiki is like a blog because it's divided up into neat little chunks, but it encourages you to read it by hyperlinking rather than sequentially: if you like, a non-linear blog analogue that binds the individual microcontent items into a cohesive whole. \n\nFor more information or to create your own TiddlyWiki, please visit the [[TiddlyWiki website|http://www.tiddlywiki.com/]].</div>
<div tiddler="HelloThere" modified="200506101704" modifier="LiamHatton" tags="">Welcome to the website and users' manual for php-bulksms, a PHP class for the purpose of sending SMS (short text messages) to mobile phones from your PHP scripts using the BulkSMS service.\n\nThis website is a single self-contained hypertext document using a MicroContent WikiWikiWeb called TiddlyWiki. It's written in HTML, CSS and JavaScript to run on any modern browser without needing any server-side logic. See UsingThisSite for more information on how to use this page.</div>
<div tiddler="LiamHatton" modified="200506101705" modifier="LiamHatton" tags="">Liam Hatton is the author of php-bulksms. His website is located [[here|http://liam.hatton.name/]], or you can send him an e-mail at [[hide@address.com|mailto:hide@address.com]], however please forward all support questions and comments to the MailingList so the answers can be available for the benefit of all people.</div>
<div tiddler="send_sms" modified="200506101759" modifier="LiamHatton" tags="">{{{bulksms-&gt;send_sms(@@color(red):array( … )@@, @@color(green):$quote=(false (default)|true)@@);}}}\n\nMethod for sending an SMS.  Requires an array to be sent to it containing parameters like those listed in the [[EAPI documentation|http://bulksms.vsms.net/docs/eapi/submission/send_sms/]]. Returns a StatusCode – this code can be checked to see whether the command has succeeded or not.\n\n$quote is an optional parameter which defaults to false if it is not specified. If this parameter is set to true, it will obtain a price quote (using the EAPI function get_quote) but will not send the SMS. If you are obtaining a price quote, the quotation can be read using the [[get_quotation]] method.\n\n----\n\n//Example #1: Sending an SMS://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$vars = array(&quot;message&quot; =&gt; &quot;Hello, this is a test&quot;, &quot;msisdn&quot;  =&gt; &quot;61433315882&quot;);\nif($sms-&gt;send_sms($vars) == SUCCESS) {\n    echo &quot;Message sending succeded&quot;;\n} else {\n    echo &quot;There was an error, status code: &quot;.$sms-&gt;get_status();\n}\n}}}\n\n//Example #2: Getting a price quote for sending an SMS://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$vars = array(&quot;message&quot; =&gt; &quot;Hello, this is a test&quot;, &quot;msisdn&quot;  =&gt; &quot;61433315882&quot;);\nif($sms-&gt;send_sms($vars, true) == SUCCESS) {\n    echo &quot;Cost to send SMS (in credits):  &quot;.$sms-&gt;get_quotation();\n} else {\n    echo &quot;There was an error, status code: &quot;.$sms-&gt;get_status();\n}\n}}}</div>
<div tiddler="get_credits" modified="200506101801" modifier="LiamHatton" tags="">{{{bulksms-&gt;get_credits();}}}\n\nRetrieves the remaining credit balance in your BulkSMS account. Returns a status code – this code can be checked to see whether the command has succeeded or not. After executing this function, retrieve your credit balance using [[get_response]].\n\n----\n\n//Example #1: Checking credit balance://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\nif($sms-&gt;get_credits() == SUCCESS) {\n    echo &quot;Remaining credit balance: &quot;.$sms-&gt;get_response();\n} else {\n    echo &quot;There was an error, status code: &quot;.$sms-&gt;get_status();\n}}}\n\n </div>
<div tiddler="IncomingMessages" modified="200506101820" modifier="LiamHatton" tags="">For push/manual retrieval of incoming messages:\n* [[get_inbox]]: retrieve inbox\n* [[get_status_report]]: get a status report for a particular SMS message\n\nFor handling messages pushed to the script by the BulkSMS servers (where the arrangement has been made with BulkSMS to do so):\n*[[load_incoming_vars]]: initialise/load incoming message variables,\n*[[get_incoming_obj]]: get incoming message information as an array.</div>
<div tiddler="get_status_report" modified="200506101823" modifier="LiamHatton" tags="">{{{bulksms-&gt;get_status_report(@@color(red):array( … )@@);}}}\n\nAllows you to get status information for a message you have previously sent. Requires an array to be sent to it containing parameters like those listed in the [[EAPI documentation| http://bulksms.vsms.net/docs/eapi/submission/send_sms/]]. Returns a StatusCode – this code can be checked to see whether the command has succeeded or not.\n\n//Note: This method is not recommended by BulkSMS unless there is no other option - it is apparently better to have BulkSMS set up their system to send status reports via HTTP to your server directly. There are functions implemented in this class to handle this sort of arrangement documented here under the section IncomingMessages.//\n\nYou need to read the response using the [[get_response]] method. The response is in a multidimensional array, like this:\n\n{{{\nArray\n(\n    [1] =&gt; Array                      // First object\n        (\n            [0] =&gt; 6143315882          // MSISDN\n            [1] =&gt; 10                 // Status Code\n        )\n)\n}}}\n\nEach line returned is a new array item $array[x], with each item containing the information returned to this function by the EAPI. Please see the [[EAPI documentation|http://bulksms.vsms.net/docs/eapi/status_reports/get_report/]] for more information.\n\n----\n\n//Example #1: Printing status information://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\nif($sms-&gt;get_status_report(array(&quot;batch_id&quot; =&gt; &quot;000&quot;)) == SUCCESS) {\n      echo &quot;Status report array:\n&quot;\n      print_r($sms-&gt;get_response());\n} else {\n      echo &quot;There was an error, status code: &quot;.$sms-&gt;get_status();\n}\n}}}</div>
<div tiddler="MessageQueuing" modified="200506101837" modifier="LiamHatton" tags="">Functions to add SMS messages to a queue, and send them all at once using EAPI functions.\n*[[push_sms_to_queue]]: add an SMS to the queue,\n*[[del_sms_from_queue]]: delete an SMS from the queue,\n*[[clear_queue]]: clear the queue,\n*[[get_queue]]: get the message queue as an array,\n*[[process_queue]]: process and send the messages in the queue.</div>
<div tiddler="SimpleFunctionality" modified="200506101841" modifier="LiamHatton" tags="">Functions used for basic implementation of SMS services.\n*[[send_sms]]: send a SMS or get a price quote;\n*[[get_credits]]: get credit balance for your account.\n\nAlso see ResponseInfo &amp; StatusCode for response and status information after running a function.</div>
<div tiddler="ResponseInfo" modified="200506101851" modifier="LiamHatton" tags="">Used to retrieve a response, status or error code after you run a function.\n*[[get_status]]: returns StatusCode for method that has previously been run,\n*[[get_eapi_status_code]] &amp; [[get_eapi_status_msg]]: returns status code or message returned by BulkSMS EAPI,\n*[[get_batch_id]], [[get_response]] &amp; [[get_quotation]]</div>
<div tiddler="BulkSMS" modified="200506101904" modifier="LiamHatton" tags="">BulkSMS is a service offered by Celerity Systems Pty Ltd (SA), providing SMS gateway services to mobile telephones worldwide. I am not affiliated with Celerity Systems or BulkSMS in any way; I am merely a customer. Therefore, I cannot assist in support questions regarding their service.\n\nPlease visit the [[BulkSMS website|http://www.bulksms.com/]] for more information.</div>
<div tiddler="LicenseInfo" modified="200506101904" modifier="LiamHatton" tags="">php-bulksms is open source and licensed under the LGPL (Lesser GNU Public License). The major difference between the standard and common GPL versus the lesser-known LGPL licence is that use in proprietary programs (ie. not open source) is permitted. A copy of this license is included in the official distribution archive.\n\nPlease visit the [[GNU LGPL website|http://www.gnu.org/copyleft/lesser.html]] to see more information about this license.</div>
<div tiddler="MailingList" modified="200506101905" modifier="LiamHatton" tags="">A forum on Yahoo! Groups exists for the purpose of user discussion, announcements and support. It is currently a low volume mailing list (approx. 1-2 messages a month) and it is strongly recommended that you subscribe to receive announcements for new releases of the class.\n\n* ''Subscribe to the list:'' send a blank e-mail to [[php-bulksms-hide@address.com|mailto:php-bulksms-hide@address.com]] or subscribe to the RSS feed at http://rss.groups.yahoo.com/group/php-bulksms/rss\n* ''Unsubscribe to the list:'' send a blank e-mail to [[php-bulksms-hide@address.com|mailto:php-bulksms-hide@address.com]]\n* ''Post to the list:'' send your message to [[php-hide@address.com|mailto:php-hide@address.com]]\n* ''List archives &amp; website:'' http://groups.yahoo.com/group/php-bulksms/</div>
<div tiddler="NumberList" modified="200506101918" modifier="LiamHatton" tags="">This class has the option of an inbuilt number list, ie. you can add numbers to the list in your PHP scripts (by, for example, reading them from a database) and send them all a short text message at once. There are a number of processing functions to make it easier to handle long lists of numbers.\n*[[get_number_list]]: returns an array containing all the numbers added to the list,\n*[[add_number_to_list]]: adds a number to the list,\n*[[del_number_from_list]]: removes a number from the list,\n*[[clear_list]]: erases the entire number list,\n*[[send_to_list]]: send to the numbers in the number list,\n*[[list2group]]: exports the number list to a PublicGroup.</div>
<div tiddler="NumberFormatting" modified="200506101925" modifier="LiamHatton" tags="">Used to fix a number so it is correctly formatted for use with BulkSMS (for example, a number that a user has entered) or to check to see if a number is in the correct format.\n*[[fix_number]]: fixes a phone number into the correct format\n*[[check_number]]: checks to see if a number sent to it is already in the correct format.</div>
<div tiddler="load_incoming_vars" modified="200508281303" modifier="YourName" tags="">{{{bulksms-&gt;load_incoming_vars(@@color(red):$password@@, @@color(green):$type=(AUTO (default) | INBOX | STATUS)@@);}}}\n\nThis function is for loading IncomingMessages and is put near the top of your script before you do anything else. The password that you have arranged with BulkSMS for HTTP message pushing needs to be specified. The second parameter is optional, it is set to AUTO (0) by default which means it will automatically guess the type of message being pushed, otherwise you can specify it as INBOX (2) for incoming inbox messages, or STATUS (1) for incoming status reports.\n\nThis function will also output headers to prevent caching in case your web server uses a reverse proxy. Do not output anything to the user when using this function because it will cause problems with setting the headers, and it will automatically output a sort of status code back to the BulkSMS server so there is no need to send anything back.  If you experience errors with this relating to setting the HTTP headers and you have made sure you are not outputting anything to the user in your script, it is probably because there is some white space before the &lt;?php tag in your script.\n\nThe script that you use for handling incoming messages should be hidden somewhere publicly on your webserver so it is not accessible to users, and it is a good idea to specify in your robots.txt file to prevent search bots from indexing the script.\n\nThis function will return [[FATAL]] (-1) if there is something wrong with the input provided, or [[SUCCESS]] (1) if the operation is successful.\n\n----\n\n//Example #1: Framework for incoming message handling script://\n{{{\n&lt;?php\ninclude_once('bulksms.php');\n$sms = new bulksms;\nif($sms-&gt;load_incoming_vars(&quot;password&quot;) == SUCCESS) {\n        $msg = get_incoming_obj();\n        switch($msg['incoming_type']) {\n               case INBOX:\n                       // Put handling code for new inbox messages here.\n               break;\n               case STATUS:\n                       // Put handling code for status reports here.\n               break;\n        }\n} else {\n        // It looks like somebody probably accessed the script directly.\n} ?&gt;\n}}}</div>
<div tiddler="FATAL" modified="200508281304" modifier="YourName" tags="">See StatusCode.</div>
<div tiddler="SUCCESS" modified="200508281304" modifier="YourName" tags="">See StatusCode.</div>
<div tiddler="COUNTRY_CODE" modified="200508281310" modifier="YourName" tags="">See GettingStarted.</div>
<div tiddler="BULKSMS_HOST" modified="200508281310" modifier="YourName" tags="">See GettingStarted.</div>
<div tiddler="StatusCode" modified="200508281310" modifier="YourName" tags="">The following status codes are used  (either returned by a function, or returned using [[get_status]]) with their corresponding meanings and pre-defined consonants:\n|!Meaning|!Code|!Consonant|\n|//Operation was successful.//|1|[[SUCCESS]]|\n|//Operation returned fatal error.//|-1|[[FATAL]]|\n|//Operation returned a transient error, try again later.//|-2|[[RETRY]]|\n|//Operation returned an error because you specified incorrect or invalid parameters.//|-3|INPUT_ERR|</div>
<div tiddler="INPUT_ERR" modified="200508281310" modifier="YourName" tags="">See StatusCode.</div>
<div tiddler="RETRY" modified="200508281311" modifier="YourName" tags="">See StatusCode.</div>
<div tiddler="get_inbox" modified="200508281311" modifier="YourName" tags="">{{{bulksms-&gt;get_inbox(@@color(red):array( … )@@);}}}\n\nRetrieves the contents of your message inbox. Can be sent an array containing parameters like those listed in the [[EAPI documentation|http://bulksms.vsms.net/docs/eapi/reception/get_inbox/]], but if no parameters are sent to it, it will retrieve all messages in your inbox. Returns a StatusCode – this code can be checked to see whether the command has succeeded or not.\n\n//Note: This method is not recommended by BulkSMS unless there is no other option - it is apparently better to have BulkSMS set up their system to send new messages via HTTP to your server directly. There are functions implemented in this class to handle this sort of arrangement documented here under the section IncomingMessages.//\n\nYou need to read the response using the [[get_response]] method. The response is in a multidimensional array, like this:\n\n{{{\nArray\n(\n   [1] =&gt; Array                               // First message.\n        (\n            [0] =&gt; 57770                      // Message ID\n            [1] =&gt; 61433315882                // MSISDN of sender\n            [2] =&gt; Test                       // Message\n            [3] =&gt; 2005-04-11 14:39:44        // Date &amp; Time\n            [4] =&gt; 447797803200               // MSISDN that received the message\n            [5] =&gt; 43722027                   // Referring message id.\n        )\n)\n}}}\n\nEach message is a new array item $array[x], with each array item containing the information returned to this function by the EAPI. Please see [[the EAPI section|http://bulksms.vsms.net/docs/eapi/reception/get_inbox/]] for more information. If there are no messages, this method will return a [[StatusCode]] of 1 ([[SUCCESS]]), but when you use [[get_response]], the response will be NULL.\n\n----\n\n//Example #1: Listing messages in the inbox://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\nif($sms-&gt;get_inbox() == SUCCESS) {\n      $msgs = $sms-&gt;get_response();\n      if(!$msgs) {\n            echo &quot;No messages in your inbox.&quot;;\n      } else {\n            echo count($msgs).&quot; inbox messages, as follows:\n\n&quot;\n            foreach($msgs as $item) {\n                  echo &quot;Message ID: &quot;.$item[0].&quot;\n&quot;;\n                  echo &quot;Sender: &quot;.$item[1].&quot;\n&quot;;\n                  echo &quot;Message: &quot;.$item[2].&quot;\n&quot;;\n                  echo &quot;Date &amp; Time: &quot;.$item[3].&quot;\n&quot;;\n                  echo &quot;MSISDN: &quot;.$item[4].&quot;\n&quot;;\n                  echo &quot;Referring message ID: &quot;.$item[5].&quot;\n\n&quot;;\n            }\n      }\n} else {\n      echo &quot;There was an error, status code: &quot;.$sms-&gt;get_status();\n}\n}}}</div>
<div tiddler="get_incoming_obj" modified="200508281312" modifier="YourName" tags="">{{{bulksms-&gt;get_incoming_obj();}}}\n\nThis function will return an array containing the information pushed to the script (ie. inbox message or status report). It is similar to simply reading the $_GET array, but at this time it adds one more field called 'incoming_type' which is for the message type, and is either set to INBOX (1) for new inbox message or STATUS (2) for status reports. \n\nSee [[here|http://bulksms.vsms.net/docs/eapi/reception/http_push/]] and [[here|http://bulksms.vsms.net/docs/eapi/status_reports/http_push/]] for more information on the parameters that are sent by BulkSMS. </div>
<div tiddler="MainMenu" modified="200508281312" modifier="YourName" tags="">HelloThere\nMailingList\nRelatedLinks\nLicenseInfo\nChangeList\n\n''About this page''\nTiddlyWiki\nUsingThisSite\nSaveChanges\n\n''Documentation''\nGettingStarted\n\n''Implementation''\nSimpleFunctionality\nIncomingMessages\nNumberList\nPublicGroup\nMessageQueuing\nNumberFormatting\nResponseInfo\nStatusCode\n\n© LiamHatton 2005</div>
<div tiddler="check_number" modified="200508281314" modifier="YourName" tags="">{{{bulksms-&gt;check_number(@@color(red):$num@@);}}}\n\nChecks to see if a number sent to it is in the correct format for use with BulkSMS. Returns Boolean - true if the number is in the correct format, otherwise it returns false.\n\n----\n\n//Example #1: Checking phone numbers.//\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$arr = array(&quot;61433315882&quot;,   // will evaluate as correct\n             &quot;01161433315882&quot;, // will evaluate as invalid (IDD code)\n             &quot;12345&quot;,         // will evaluate as invalid (too short)\n             &quot;+61-433315882&quot;, // will evaluate as invalid (invalid characters)\n             &quot;61 433 315 882&quot;  // will evaluate as invalid (whitespaces)\n             );\nforeach($arr as $i) {\n      if($sms-&gt;check_number($i) == true) {\n            echo $i.&quot;: correct\n&quot;;\n      } else {\n            echo $i.&quot;: incorrect\n&quot;;\n      }\n}\n}}}</div>
<div tiddler="get_number_list" modified="200508281315" modifier="YourName" tags="">{{{bulksms-&gt;get_number_list();}}}\n\nReturns an array containing all the numbers added to the list.</div>
<div tiddler="clear_list" modified="200508281318" modifier="YourName" tags="">{{{bulksms-&gt;clear_list();}}}\n\nErases the entire number list.</div>
<div tiddler="send_to_list" modified="200508281322" modifier="YourName" tags="">{{{send_to_list(@@color(red):array( … )@@, @@color(green):$quote=(false (default)|true), $remove_dups=(false (default)|true), $split=(int or NULL)@@);}}}\n\nCalls the [[send_sms]] method to send to the numbers in the number list. First parameter is required and is an array containing parameters like those listed in the EAPI documentation [[here|http://bulksms.vsms.net/docs/eapi/submission/send_sms/]]. \n\nFirst parameter is optional and set as false by default. When it is set to true, it will use quote_sms and get a price quote for sending the message to the list rather than actually sending the message.\n\nSecond parameter is set as false by default and is optional. When it is set to true, it will remove all duplicate numbers from the number list before sending.\n\nThird parameter is optional and is set as NULL by default. When set as an integer, it will split the list into the size as specified and send the list in chunks. If your list is larger than 5000 numbers, this function will automatically split the list otherwise you might experience timeouts. Please note that this class is untested and not recommended for large lists of numbers - I suggest writing your own software instead if you need to send to a large list of recipients.\n\nIf the number list is split, when you use [[get_status]], [[get_batch_id]], [[get_eapi_status_code]], [[get_eapi_status_msg]], [[get_eapi_status_msg]] &amp; bulksms-&gt;get_quotation() you will get back an array containing the information for each iteration of [[send_sms]]. Also, in this case, this method will also return an array containing the status codes returned by each iteration of [[send_sms]]. If the number list is not split, these commands will act as normal like [[send_sms]].\n\n----\n\n//Example #1: Adding numbers to the list and sending a SMS (simple)://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$sms-&gt;add_number_to_list('61433315882');\n$sms-&gt;add_number_to_list('18005551212');\n$vars = array(&quot;message&quot; =&gt; &quot;Hello, this is a test.&quot;);\nif($sms-&gt;send_to_list($vars) == SUCCESS) {\n      echo &quot;Message sending succeded&quot;;\n} else {\n      echo &quot;There was an error, status code: &quot;.$sms-&gt;get_status();\n}\n}}}\n\n//Example #2: Adding an array of numbers and sending an SMS to the list://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$arr = array('61433315882', '18005551212', '44987654321', '44123456789');\nforeach($arr as $item) {\n      $sms-&gt;add_number_to_list($item);\n}\n$vars = array(&quot;message&quot; =&gt; &quot;Hello, this is a test.&quot;);\nif($sms-&gt;send_to_list($vars) == SUCCESS) {\n      echo &quot;Message sending succeded&quot;;\n} else {\n      echo &quot;There was an error, status code: &quot;.$sms-&gt;get_status();\n}\n}}}\n\n//Example #3: Adding a longer list of numbers and sending an SMS (with split function) to the list://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$sms-&gt;add_number_to_list('61433315882');\n$sms-&gt;add_number_to_list('18005551212');\n$sms-&gt;add_number_to_list('44123456789');\n$sms-&gt;add_number_to_list('44987654321');\n$sms-&gt;add_number_to_list('44112233445');\n$sms-&gt;add_number_to_list('32112233445');\n// … add more numbers here …\n$vars = array(&quot;message&quot; =&gt; &quot;Hello, this is a test.&quot;);\n$status = $sms-&gt;send_to_list($vars, false, false, 5);\necho &quot;Sent SMS to list: &quot;\nforeach($status as $i) {\n      if($i == SUCCESS) {\n            echo &quot;Iteration successful&quot;;\n      } else {\n            echo &quot;Iteration not successful, returned: &quot;.$i;\n      }\n}\n}}}</div>
<div tiddler="add_to_public_group" modified="200508281326" modifier="YourName" tags="">{{{bulksms-&gt;add_to_public_group(@@color(red):$group_id, $msisdn,@@ @@color(green):$firstname, $lastname, $fixnumber=(true (default)|false)@@);}}}\n \nAdd a single number to a PublicGroup. This is a new function added in version 0.93 of this class.\n\nRequired parameters include the group ID as the first parameter (specified as $group_id) and phone number as the second. Optional parameters include the first and last name for the user associated with the mobile number (for your reference), and the option $fixnumber which will fix the number using [[fix_number]] unless otherwise specified as false. If $fixnumber is set as false, this function will return [[INPUT_ERR]] (-3) if the number is not in the correct format.\n\nIf the operation was successful, this command will return [[SUCCESS]] (1), otherwise it will return [[FATAL]] (-1).\n\n----\n\n//Example #1: Adding a number to a public group://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\nif($sms-&gt;add_to_public_group(&quot;000&quot;, &quot;61433315882&quot;, &quot;Liam&quot;, &quot;Hatton&quot;, false) == SUCCESS) {\n      echo &quot;Added to public group&quot;;\n} else {\n      echo &quot;There was an error, status code: &quot;.$sms-&gt;get_status();\n}\n}}}</div>
<div tiddler="list2group" modified="200508281327" modifier="YourName" tags="">{{{bulksms-&gt;list2group(@@color(red):$group_id@@);}}}\n\nExports the number list to a PublicGroup. This is a new function added in version 0.93 of this class.\n\nBecause this function executes an iteration of [[add_to_public_group]] for each number, it may take a while (and create a lot of traffic to the BulkSMS servers, which BulkSMS may not like) if your number list is very large. If there are more than 20-30 numbers in the list, you may need to extend the allowed execution of PHP scripts on your system otherwise you might experience problems with your scripts being terminated before this function has been given adequate time to add all numbers.\n\nThis function automatically removes duplicates from the NumberList, which accelerates the addition process (because BulkSMS doesn't allow duplicate numbers in a group) by requiring less instances of [[add_to_public_group]].\n\nThis function will return an array containing all the status codes for each iteration of [[add_to_public_group]] run.\n\n----\n\n//Example #1: Adding numbers to the list, and exporting the list to a public group://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$sms-&gt;add_number_to_list('61433315882');\n$sms-&gt;add_number_to_list('18005551212');\n$status = $sms-&gt;list2group(&quot;000&quot;);\necho &quot;Added number list to public group: &quot;\nforeach($status as $i) {\n      if($i == SUCCESS) {\n            echo &quot;Iteration successful&quot;;\n      } else {\n            echo &quot;Iteration not successful, returned: &quot;.$i;\n      }\n}\n}}}</div>
<div tiddler="PublicGroup" modified="200508281327" modifier="YourName" tags="">You can create a public group by creating a group using the BulkSMS web interface, and giving it permissions for public addition/deletion of numbers. These functions are ideal if you wish to run your own SMS mailing list, because you can subscribe/unsubscribe numbers using this class and send a message to the entire group using the BulkSMS web interface, or by using the send_sms function.\n*[[add_to_public_group]]: add a single number to a public group,\n*[[del_from_public_group]]: delete a number from a public group,\n*[[list2group]]: exports the NumberList to a public group</div>
<div tiddler="del_from_public_group" modified="200508281329" modifier="YourName" tags="">{{{bulksms-&gt;del_from_public_group(@@color(red):$group_id, $msisdn@@, @@color(green):$fixnumber=(true (default)|false)@@);}}}\n\nDeletes a single number from a PublicGroup. This is a new function added in version 0.93 of this class.\n\nRequired parameters include the group ID as the first parameter (specified as $group_id) and phone number as the second. The only optional parameter is $fixnumber which will fix the number provided using [[fix_number]] unless otherwise specified as false. If $fixnumber is set as false, this function will return [[INPUT_ERR]] (-3) if the number is not in the correct format.\n\nIf the operation was successful, this command will return [[SUCCESS]] (1), otherwise it will return [[FATAL]] (-1).\n\n---- \n\n//Example #1: Deleting a number from a public group://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\nif($sms-&gt;del_from_public_group(&quot;61433315882&quot;) == SUCCESS) {\n      echo &quot;Deleted public group&quot;;\n} else {\n      echo &quot;There was an error, status code: &quot;.$sms-&gt;get_status();\n}\n}}}</div>
<div tiddler="push_sms_to_queue" modified="200508281330" modifier="YourName" tags="">{{{bulksms-&gt;push_sms_to_queue(@@color(red):$vars@@);}}}\n\nAdds an SMS to the queue. Needs to be sent an array containing variables for the message as defined [[here|http://bulksms.vsms.net/docs/eapi/submission/send_batch/]] (under required and optional CSV columns). Returns a unique ID code associated with the message added to the queue.\n\n----\n\n//Example #1: Adding SMS messages to queue://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$sms-&gt;push_sms_to_queue(array(&quot;msisdn&quot; =&gt; &quot;61400233646&quot;, &quot;message&quot; =&gt; &quot;Test #1&quot;));\n$sms-&gt;push_sms_to_queue(array(&quot;msisdn&quot; =&gt; &quot;44123456789&quot;, &quot;message&quot; =&gt; &quot;Test #2&quot;));\n$sms-&gt;push_sms_to_queue(array(&quot;msisdn&quot; =&gt; &quot;12125551212&quot;, &quot;message&quot; =&gt; &quot;Test #3&quot;));\n}}}</div>
<div tiddler="del_number_from_list" modified="200508281338" modifier="YourName" tags="">{{{bulksms-&gt;del_number_from_list(@@color(red):$num@@);}}}\n\nRemoves a number from the list. This method will only delete the first addition of the number from the list, so if the number is added multiple times, you will need to use this method for each time the number is listed. This function returns 1 ([[SUCCESS]]) if is has successfully deleted the number from the list, or -3 (INPUT_ERR) if the number was not in the list. Therefore, if you wanted to delete a number that has been listed multiple times, you could write a loop to run this method until -3 ([[INPUT_ERR]]) has been returned.\n\n----\n\n//Example #1: Deleting a number, and printing the list.//\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$sms-&gt;add_number_to_list('61433315882');\n$sms-&gt;add_number_to_list('18005551212');\n$sms-&gt;del_number_from_list('61433315882');\n$list = $sms-&gt;get_number_list();\necho &quot;Numbers in the list:\n&quot;;\nforeach($list as $item) {\n      echo $item.&quot;\n&quot;;\n}\n}}}</div>
<div tiddler="add_number_to_list" modified="200508281339" modifier="YourName" tags="">{{{bulksms-&gt;add_number_to_list(@@color(red):$num@@);}}}\n\nAdds a number to the list. Does not check number for correct formatting, use [[check_number]] to do this. Returns a status code of 1 ([[SUCCESS]]), but you don't need to check for this.\n\n----\n\n//Example #1: Adding numbers to the list (simple)://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$sms-&gt;add_number_to_list('61433315882');\n$sms-&gt;add_number_to_list('18005551212');\n}}}\n\n//Example #2: Reformats numbers, adds them to the list and prints out the number list://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$arr = array('0433315882', '1 800 555 1212', '1800COLLECT', '44123456789');\nforeach($arr as $item) {\n      $item = $sms-&gt;fix_number($item);\n      $sms-&gt;add_number_to_list($item);\n}\n$list = $sms-&gt;get_number_list();\necho &quot;Reformatted/fixed numbers in the list:\n&quot;;\nforeach($list as $item) {\n      echo $item.&quot;\n&quot;;\n}\n}}}\n\n//Example #3: Only adds numbers that are correctly formatted, and prints out the number list://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$arr = array('+61-433-315-882', '1 800 555 1212', '1800COLLECT', '44123456789');\nforeach($arr as $item) {\n      if($sms-&gt;check_number($item) == true) {   // this will only evaluate true for 44123456789\n            $sms-&gt;add_number_to_list($item);\n      }\n}\n$list = $sms-&gt;get_number_list();\necho &quot;Correctly formatted numbers added to the list:\n&quot;;\nforeach($list as $item) {\n      echo $item.&quot;\n&quot;;\n}\n}}}</div>
<div tiddler="clear_queue" modified="200508281341" modifier="YourName" tags="">{{{bulksms-&gt;clear_queue();}}}\n\nClears the message queue completely. Always returns a response code of [[SUCCESS]] (1), but you don't need to check for this.</div>
<div tiddler="del_sms_from_queue" modified="200508281341" modifier="YourName" tags="">{{{bulksms-&gt;del_sms_from_queue(@@color(red):$id@@);}}}\n\nDeletes an SMS from the queue. Requires the ID for the message you want to delete (as provided by [[push_sms_to_queue]]) and returns a response code of [[SUCCESS]] (1) if it deleted the message from the queue, or [[INPUT_ERR]] (-3) if message associated with the ID could not be found.\n\n----\n\n//Example #1: SMS deletion from queue://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$id = $sms-&gt;push_sms_to_queue(array(&quot;msisdn&quot; =&gt; &quot;61400233646&quot;, &quot;message&quot; =&gt; &quot;Test&quot;));\n$sms-&gt;del_sms_from_queue($id);\necho &quot;Number of messages in queue: &quot;.count($sms-&gt;get_queue());\n}}}</div>
<div tiddler="get_queue" modified="200508281342" modifier="YourName" tags="">{{{bulksms-&gt;get_queue();}}}\n\nReturns the message queue as an array.</div>
<div tiddler="get_status" modified="200508281347" modifier="YourName" tags="">{{{bulksms-&gt;get_status();}}}\n\nReturns StatusCode for method that has previously been run (or an array of status codes if you have used the [[send_to_list]] method). See StatusCode for a list of the status codes used.</div>
<div tiddler="get_eapi_status_code" modified="200508281501" modifier="YourName" tags="">{{{\nbulksms-&gt;get_eapi_status_code();\nbulksms-&gt;get_eapi_status_msg();\n}}}\n\nReturns status code or message returned by BulkSMS's EAPI for method that has previously been run (or an array of status codes if you have used the [[send_to_list]] method). According to BulkSMS, you should not rely on the status message because it might change in future versions of the EAPI - you should only use the status code.  There are consonants defined in the script for your convenience, please examine the script if you wish to see the entire list.\n\nIn most cases, it isn't necessary to test for the EAPI status code to see if an operation has been successful, because [[get_status]] will return to you a suitable code for how you should handle the error in your script. You would only want to check this to see what kind of error occurred if you get back an error-type code from running the method.</div>
<div tiddler="get_eapi_status_msg" modified="200508281501" modifier="YourName" tags="">See [[get_eapi_status_code]].</div>
<div tiddler="get_response" modified="200508281502" modifier="YourName" tags="">{{{bulksms-&gt;get_response();}}}\n\n[[get_response]] returns the response after running a particular method, and is only used for some methods which return content. </div>
<div tiddler="get_quotation" modified="200508281503" modifier="YourName" tags="">{{{bulksms-&gt;get_quotation();}}}\n\n[[get_quotation]] returns a price quotation after using [[send_sms]] with the quotation feature enabled.</div>
<div tiddler="get_batch_id" modified="200508281504" modifier="YourName" tags="">{{{bulksms-&gt;get_batch_id();}}}\n \n[[get_batch_id]] returns batch ID received after using [[send_sms]] (or an array of batch ID's if you have used the [[send_to_list]] method). </div>
<div tiddler="process_queue" modified="200508281505" modifier="YourName" tags="">{{{bulksms-&gt;process_queue(@@color(green):$vars = NULL@@);}}}\n\nProcesses the message queue by generating a CSV from the queue and sending it via the BulkSMS EAPI function send_batch. Like [[send_sms]], it returns a StatusCode for the operation. You can retrieve the batch ID and various EAPI status information using the functions defined under ResponseInfo.\n\n$vars is an optional parameter which allows you to specify optional parameters in an array to be sent to the EAPI function send_batch as defined under Optional HTTP Parameters [[here|http://bulksms.vsms.net/docs/eapi/submission/send_batch/]].\n\n----\n\n//Example #1: Processing queue of SMS messages://\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\n$sms-&gt;push_sms_to_queue(array(&quot;msisdn&quot; =&gt; &quot;61400233646&quot;, &quot;message&quot; =&gt; &quot;Test #1&quot;));\n$sms-&gt;push_sms_to_queue(array(&quot;msisdn&quot; =&gt; &quot;44123456789&quot;, &quot;message&quot; =&gt; &quot;Test #2&quot;));\n$sms-&gt;push_sms_to_queue(array(&quot;msisdn&quot; =&gt; &quot;12125551212&quot;, &quot;message&quot; =&gt; &quot;Test #3&quot;));\n$status = $sms-&gt;process_queue();\nif($status == SUCCESS) {\n        echo &quot;Queue successfully processed, batch id: &quot;.$sms-&gt;get_batch_id();\n} else {\n        echo &quot;There was an error: &quot;.$sms-&gt;get_eapi_status_msg().&quot; (&quot;.$sms-&gt;get_eapi_status_code().&quot;)&quot;;\n}\n}}}</div>
<div tiddler="fix_number" modified="200508281506" modifier="YourName" tags="">{{{bulksms-&gt;fix_number(@@color(red):$num@@, @@color(green):$tr_letters=(true (default)|false)@@);}}}\n\nFixes a phone number that a user has supplied into the correct format for use with BulkSMS and returns the fixed number. If COUNTRY_CODE (when first setting up this script for use) has been set to something other than zero, it will add the correct country code where the first digit is 0 and the first digits cannot be identified as an IDD access code (suitable where this type of numbering plan is in place in some European countries). However, it is best in your script to ask the user to provide the number in the correct format, to make sure that what the user is entering is actually a phone number and not something else.\n\nA new feature was added with version 0.93 of this class, which provides an optional parameter $tr_letters which can be set to false. If this option is set to false, this function will not convert letters into numbers (ie. ABCD into 2223). If the number provided contains letters and this feature is set to false, this function will return false as the number is not valid. You might want to use this feature if you want to repair a number that a user has provided in a form, but you don't want to accept the input if the user has simply entered a text string. Is set to true by default for backward compatibility with older versions of this class.\n\n----\n\n//Example #1: Fixing phone numbers.//\n{{{\ninclude_once('bulksms.php');\n$sms = new bulksms;\necho $sms-&gt;fix_number(&quot;01161433315882&quot;).&quot;\n&quot;; // removes most IDD access codes\necho $sms-&gt;fix_number(&quot;+61 433 315 882&quot;).&quot;\n&quot;; // removes plus sign and whitespaces\necho $sms-&gt;fix_number(&quot;+61-433-315-882&quot;).&quot;\n&quot;; // removes foreign characters such as dashes\necho $sms-&gt;fix_number(&quot;614333HELLO2&quot;).&quot;\n&quot;; // converts letters into numbers\nif($sms-&gt;fix_number(&quot;614333HELLO2&quot;, false) == false) echo &quot;invalid\n&quot;; // will not convert this\necho $sms-&gt;fix_number(&quot;0433315882&quot;).&quot;\n&quot;; // drops 0 and appends country code set in COUNTRY_CODE\n}}}</div>
<div tiddler="GettingStarted" modified="200508281509" modifier="LiamHatton" tags="">''The latest recommended stable release is //1.0//.'' It is recommended that you keep your version up-to-date to account for new updates to the EAPI and bug fixes.\n!!!Download the latest version as a [[.ZIP|http://liam.hatton.name/dl/php-bulksms.zip]] or [[.TAR.GZ|http://liam.hatton.name/dl/php-bulksms.tar.gz]] archive.\nIt is also a good idea to save the latest copy of this documentation to your hard drive - see UsingThisSite on how to do this. You must also create a BulkSMS account online by visiting [[their website|http://www.bulksms.com/]] if you don't already have one.\n\nSimply open the file //'bulksms.php'// using your favourite text editor ([[SciTE|http://www.scintilla.org/SciTE.html]] is a good open-source text editor if you are still using Notepad) and edit the header to reflect your account details. Make sure you uncomment the correct country-based host for the country where your account is registered, otherwise your username/password combination will not work. \n\nUpload the two files //'bulksms.php'// &amp; //'http.inc'// (required for HTTP connectivity) to the same directory on your webserver. You can also upload the test suite script called //'test.php'// if you wish to test the class on your server.</div>
<div tiddler="DefaultTiddlers" modified="200508281523" modifier="LiamHatton" tags="">OldVersionWarning HelloThere BulkSMS GettingStarted MailingList LicenseInfo RelatedLinks</div>
<div tiddler="StyleSheet" modified="200508281537" modifier="LiamHatton" tags="">#tiddlerOldVersionWarning { border:1px solid black; background-color: aliceblue; }</div>
<div tiddler="ChangeList" modified="200508281545" modifier="LiamHatton" tags="">''1.0 (released 29th August 2005):''\n* First stable version.\n* Wrote new documentation using TiddlyWiki.\n\n''0.93 (released 22nd May 2005):''\n* Added new functions for MessageQueuing and handling IncomingMessages.\n* Added new feature allowing you to change the HTTP port used for connection (if you are behind a firewall)\n* Tidied up the header and added additional error handling for BULKSMS_HOST, in case it isn't set (before it would freeze if this was not set)\n\n''0.92 (released 4th May 2005):''\n* Fixed major bug in [[send_to_list]] which affected the StatusCode returned by the function (missing ()’s)\n* Added new functions for PublicGroups.\n* Added international support (thank you Andrew for letting me know – I completely forgot about this!).\n* Added new feature in [[fix_number]] function which makes it optional to translate letters.\n* Fixed some more grammatical errors in documentation, and fixed some errors in the examples.\n* Created MailingList on Yahoo! Groups for announcements and user support.\n\n''0.91 (released 13th April 2005):''\n* Fixed major bug in one of the regexp that caused the library to parse the status returned by EAPI incorrectly.\n* Corrected some grammatical and colouring errors in the documentation.\n\n''0.9 (released 12th April 2005):''\n* First release.</div>
<div tiddler="UsingThisSite" modified="200508281559" modifier="LiamHatton" tags="">Within the main story column you can click on bold links to read a linked tiddler. Click on italic links to create a new tiddler. When you hover the mouse over a tiddler several ToolbarButtons appear. You can edit the text of any tiddler by double-clicking on it (or selecting 'edit' from the toolbar), but your changes won't get saved permanently until you make your own copy of TiddlyWiki, as described in SaveChanges.\n\nIf you would like to save a copy of this documentation on your hard drive or add your own notes and additions to it and SaveChanges, you can do this by right clicking on [[this link|index.htm]] and selecting 'Save link as...' or 'Save target as...'. You can choose where to save the file, and what to call it (but keep the .HTML extension). Do ''not'' use the File/Save command in your browser to save TiddlyWiki, because of SaveUnpredictabilities. </div>
<div tiddler="SaveChanges" modified="200508281559" modifier="LiamHatton" tags="">You can SaveChanges if you're using FireFox or InternetExplorer:\n# if you're using Windows XP you might run into ServicePack2Problems\n# right click on [[this link|index.htm]] and select 'Save link as...' or 'Save target as...'\n** do ''not'' try to use the File/Save command in your browser because of SaveUnpredictabilities.\n** choose where to save the file, and what to call it (but keep the .HTML extension)\n# open the newly downloaded file in your browser\n# click the 'options' button on the right to set your username\n# edit, create and delete the tiddlers you want\n** you can change the SpecialTiddlers to change the SiteTitle and MainMenu etc.\n# click the 'save changes' button on the right to save your changes\n# TiddlyWiki will make a backup copy of the existing file, and then replace it with the new version\n</div>
<div tiddler="OldVersionWarning" modified="200508281606" modifier="LiamHatton" tags="">This is a saved copy of the website at [[http://liam.hatton.name/php-bulksms/|http://liam.hatton.name/php-bulksms/]] for distribution with the php-bulksms class archive, and may have been updated since release. To download the latest copy of this documentation, right click [[here|http://liam.hatton.name/php-bulksms/index.php]] and choose 'Save link as...' or 'Save target as...'. Save it in the same place and name as this file, overwriting the old version, and refresh your browser to see the latest changes.</div>
		</div>
	</body>
</html>
Return current item: PHP Bulk SMS