Major revamps to the template parser. Fixed a few security holes that could allow PHP to be injected in untimely places in TPL code. Improved Ux for XSS attempt in tplWikiFormat. Documented many functions. Backported much cleaner parser from 2.0 branch. Beautified a lot of code in the depths of the template class. Pretty much a small-scale Extreme Makeover.
/*
* Auto-completing page/username fields
*/
// The ultimate Javascript app: AJAX auto-completion, which responds to up/down arrow keys, the enter key, and the escape key
// The idea was pilfered mercilessly from vBulletin, but uses about 8
// bytes of vB code. All the rest was coded by me, Mr. Javascript Newbie...
// ...in about 8 hours.
// You folks better like this stuff.
function nameCompleteEventHandler(e)
{
if(!e) e = window.event;
switch(e.keyCode)
{
case 38: // up
unSelectMove('up');
break;
case 40: // down
unSelectMove('down');
break;
case 27: // escape
case 9: // tab
destroyUsernameDropdowns();
break;
case 13: // enter
unSelect();
break;
default: return false; break;
}
return true;
}
function unSelectMove(dir)
{
if(submitAuthorized) return false;
var thediv = document.getElementById(unObjDivCurrentId);
thetable = thediv.firstChild;
cel = thetable.firstChild.firstChild;
d = true;
index = false;
changed = false;
// Object of the game: extract the username, determine its index in the userlist array, and then color the menu items and set unObjCurrentSelection
while(d) // Set to false if an exception occurs or if we arrive at our destination
{
//*
if(!cel) d=false;
celbak = cel;
cel = cel.nextSibling;
if(!cel) d=false;
try {
if(cel.firstChild.nextSibling) html = cel.firstChild.nextSibling.innerHTML;
else html = cel.firstChild.innerHTML;
cel.firstChild.className = 'row1';
if(cel.firstChild.nextSibling) cel.firstChild.nextSibling.className = 'row1';
thename = html.substr(7, html.length-15);
// FINALLY! we have extracted the username
// Now get its position in the userlist array
if(thename == unObjCurrentSelection)
{
index = parseInt(in_array(thename, userlist));
}
if(typeof(index) == 'number')
{
if(dir=='down')
n = index+1;
else if(dir == 'up')
n = index - 1;
// Try to trap moving the selection up or down beyond the top of bottom
if(n > userlist.length-1 || n < 0)
{
cel.firstChild.className = 'row2';
if(cel.firstChild.nextSibling) cel.firstChild.nextSibling.className = 'row2';
return;
}
if(dir=='down') no=cel.nextSibling;
else if(dir=='up') no=cel.previousSibling;
no.firstChild.className = 'row2';
if(no.firstChild.nextSibling) no.firstChild.nextSibling.className = 'row2';
if(no.firstChild.id)
{
scroll = getScrollOffset() + getHeight();
elemht = getElementHeight(no.firstChild.id);
elemoff = fetch_offset(no.firstChild);
whereto = elemoff['top'] + elemht;
if(whereto > scroll)
{
window.location.hash = '#'+no.firstChild.id;
unObj.focus();
}
}
cel=cel.nextSibling;
unObjCurrentSelection = userlist[n];
index = false;
changed = true;
return;
}
} catch(e) { }
//*/ d = false;
}
}
function unSelect()
{
if(!unObj || submitAuthorized) return false;
if ( unObjCurrentSelection )
unObj.value = unObjCurrentSelection;
destroyUsernameDropdowns();
}
function in_array(needle, haystack)
{
for(var i in haystack)
{
if(haystack[i] == needle) return i;
}
return false;
}
function ajaxUserNameComplete(o)
{
if(!o) {destroyUsernameDropdowns(); return;}
if(!o.value) {destroyUsernameDropdowns(); return;}
if(o.value.length < 3) {destroyUsernameDropdowns(); return;}
//if(IE) return; // This control doesn't work in IE. Yes, it's true! document.createElement doesn't work.
if(!o.id)
{
o.id = 'usernametextboxobj_' + Math.floor(Math.random() * 10000000);
}
unObj = o;
o.setAttribute("autocomplete","off");
o.onkeyup = function(e, o) { o=unObj; if(!nameCompleteEventHandler(e)) ajaxUserNameComplete(o); }
val = escape(o.value).replace('+', '%2B');
ajaxGet(stdAjaxPrefix+'&_mode=fillusername&name='+val, function()
{
if(ajax.readyState==4)
{
// Determine the appropriate left/top positions, then create a div to use for the drop-down list
// The trick here is to be able to make the div dynamically destroy itself depending on how far the user's mouse is from it
destroyUsernameDropdowns();
off = fetch_offset(unObj);
dim = fetch_dimensions(unObj);
left = off['left'];
i1 = off['top'];
i2 = dim['h'];
var top = 0;
top = i1 + i2;
var thediv = document.createElement('div');
thediv.className = 'tblholder';
thediv.style.marginTop = '0px';
thediv.style.position = 'absolute';
thediv.style.top = top + 'px';
thediv.style.left = left + 'px';
thediv.style.zIndex = getHighestZ() + 2;
id = 'usernamehoverobj_' + Math.floor(Math.random() * 10000000);
unObjDivCurrentId = id;
thediv.id = id;
unObj.onblur = function() { destroyUsernameDropdowns(); }
eval(ajax.responseText);
if(errorstring)
{
html = '<span style="color: #555; padding: 4px;">'+errorstring+'</span>';
}
else
{
html = '<table border="0" cellspacing="1" cellpadding="3" style="width: auto;"><tr><th><small>Username matches</small></th></tr>';
cls = 'row2';
unObjCurrentSelection = userlist[0];
for(i=0;i<userlist.length;i++)
{
tmpnam = 'listobjnode_'+Math.floor(Math.random() * 10000000);
html = html + '<tr><td id="'+tmpnam+'" class="'+cls+'" style="cursor: pointer;" onclick="document.getElementById(\''+unObj.id+'\').value=\''+userlist[i]+'\';destroyUsernameDropdowns();"><small>'+userlist[i]+'</small></td></tr>';
if(cls=='row2') cls='row1';
}
html = html + '</table>';
}
thediv.innerHTML = html;
var body = document.getElementsByTagName('body');
body = body[0];
unSelectMenuOn = true;
submitAuthorized = false;
body.appendChild(thediv);
}
});
}
function ajaxPageNameComplete(o)
{
if(!o) {destroyUsernameDropdowns(); return;}
if(!o.value) {destroyUsernameDropdowns(); return;}
if(o.value.length < 3) {destroyUsernameDropdowns(); return;}
if(IE) return; // This control doesn't work in IE. Yes, it's true! document.createElement doesn't work.
if(!o.id)
{
o.id = 'usernametextboxobj_' + Math.floor(Math.random() * 10000000);
}
o.setAttribute("autocomplete","off");
unObj = o;
o.onkeyup = function(e, o) { o=unObj; if(!nameCompleteEventHandler(e)) ajaxPageNameComplete(o); }
val = escape(o.value).replace('+', '%2B');
ajaxGet(stdAjaxPrefix+'&_mode=fillpagename&name='+val, function()
{
if(!ajax) return;
if(ajax.readyState==4)
{
// Determine the appropriate left/top positions, then create a div to use for the drop-down list
// The trick here is to be able to make the div dynamically destroy itself depending on how far the user's mouse is from it
destroyUsernameDropdowns();
off = fetch_offset(unObj);
dim = fetch_dimensions(unObj);
left = off['left'];
top = off['top'] + dim['h'];
var thediv = document.createElement('div');
thediv.className = 'tblholder';
thediv.style.marginTop = '0px';
thediv.style.position = 'absolute';
thediv.style.top = top + 'px';
thediv.style.left = left + 'px';
thediv.style.zIndex = getHighestZ() + 2;
id = 'usernamehoverobj_' + Math.floor(Math.random() * 10000000);
unObjDivCurrentId = id;
thediv.id = id;
eval(ajax.responseText);
if(errorstring)
{
html = '<span style="color: #555; padding: 4px;">'+errorstring+'</span>';
}
else
{
html = '<table border="0" cellspacing="1" cellpadding="3" style="width: auto;"><tr><th colspan="2">Page name matches</th></tr><tr><th><small>Page title</small></th><th><small>Page ID</small></th></tr>';
cls = 'row2';
unObjCurrentSelection = userlist[0];
for(i=0;i<userlist.length;i++)
{
tmpnam = 'listobjnode_'+Math.floor(Math.random() * 10000000);
html = html + '<tr><td id="'+tmpnam+'" class="'+cls+'" style="cursor: pointer;" onclick="document.getElementById(\''+unObj.id+'\').value=\''+userlist[i]+'\';destroyUsernameDropdowns();"><small>'+namelist[i]+'</small></td><td class="'+cls+'" style="cursor: pointer;" onclick="document.getElementById(\''+unObj.id+'\').value=\''+userlist[i]+'\';destroyUsernameDropdowns();"><small>'+userlist[i]+'</small></td></tr>';
if(cls=='row2') cls='row1';
}
html = html + '</table>';
}
thediv.innerHTML = html;
var body = document.getElementsByTagName('body');
body = body[0];
unSelectMenuOn = true;
submitAuthorized = false;
body.appendChild(thediv);
}
});
}
function destroyUsernameDropdowns()
{
var divs = document.getElementsByTagName('div');
var prefix = 'usernamehoverobj_';
for(i=0;i<divs.length;i++)
{
if ( divs[i].id )
{
if(divs[i].id.substr(0, prefix.length)==prefix)
{
divs[i].innerHTML = '';
divs[i].style.display = 'none';
}
}
}
unSelectMenuOn = false;
unObjDivCurrentId = false;
unObjCurrentSelection = false;
submitAuthorized = true;
}
function get_parent_form(o)
{
if ( !o.parentNode )
return false;
if ( o.tagName == 'FORM' )
return o;
var p = o.parentNode;
while(true)
{
if ( p.tagName == 'FORM' )
return p;
else if ( !p )
return false;
else
p = p.parentNode;
}
}