<?xml version="1.0" encoding="utf-8"?>
<!-- generator="wordpress/2.0.4" -->
<rss version="2.0" 
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	>

<channel>
	<title>upr00ted</title>
	<link>http://elliot.ecowizards.com/wp</link>
	<description>Webdesign wizardry, tutorials, and various other ramblings</description>
	<pubDate>Sun, 29 Jun 2008 16:42:53 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.0.4</generator>
	<language>en</language>
			<item>
		<title>Jquery: Running Events As You Leave a Page</title>
		<link>http://elliot.ecowizards.com/wp/archives/2008/06/29/jquery-running-events-as-you-leave-a-page/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2008/06/29/jquery-running-events-as-you-leave-a-page/#comments</comments>
		<pubDate>Sun, 29 Jun 2008 16:39:21 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>Scripting</category>
	<category>Coding</category>
	<category>jquery</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2008/06/29/jquery-running-events-as-you-leave-a-page/</guid>
		<description><![CDATA[I recently attended &#8220;An Event Apart, Boston.&#8221;  While there someone asked for my thoughts on a jquery problem which intrigued me.  Mostly I was interested because I couldn&#8217;t come up with a solid solution in the 5 minutes or so we had left before the next session started.  
The troubling spot for [...]]]></description>
			<content:encoded><![CDATA[<p>I recently attended &#8220;<a href="http://aneventapart.com">An Event Apart, Boston.</a>&#8221;  While there someone asked for my thoughts on a jquery problem which intrigued me.  Mostly I was interested because I couldn&#8217;t come up with a solid solution in the 5 minutes or so we had left before the next session started.  </p>
<p>The troubling spot for me was executing an animation on a link click before redirecting to the href of the link.  I&#8217;ve seen the <code>$("#content").animate({ opacity:1}, 100);</code> trick to add delays within a queue, but for some reason that doesn&#8217;t work when applied to the document.  </p>
<p>My proposed solution actually uses settimeout() which is what was suggested by the person asking the question.  My question to the greater community is: Is there a better way to delay a call to document.location.href?</p>
<p><a href="/slider/">Here&#8217;s my proposed jquery page redirect delay solution.</a><br />
<br />
<a href="/slider/slider.zip">Zipped for easy consumption</a></p>
<p>One note: I&#8217;m using jquery to move the content box off to the left and then bring it back in, so that people without javascript enabled will still see the content, but it seems to add an annoying flash of content sometimes.  To avoid this, just remove the jquery line <code>$("#content").css('left', '-3000%');</code> and add <code>left: 3000%;</code> to your css file. </p>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2008/06/29/jquery-running-events-as-you-leave-a-page/feed/</wfw:commentRSS>
		</item>
		<item>
		<title>Backing up linux, Mac, and Windows servers vi rsync</title>
		<link>http://elliot.ecowizards.com/wp/archives/2006/09/23/backing-up-linux-mac-and-windows-servers-vi-rsync/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2006/09/23/backing-up-linux-mac-and-windows-servers-vi-rsync/#comments</comments>
		<pubDate>Sat, 23 Sep 2006 13:56:23 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>Scripting</category>
	<category>OS X</category>
	<category>Linux</category>
	<category>Windows</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2006/09/23/backing-up-linux-mac-and-windows-servers-vi-rsync/</guid>
		<description><![CDATA[These scripts allows for remote incremental daily snapshot backups of linux, mac, and windows based servers.
The theory is that we want a full snapshot of any given day without taking up the full disk space required.  The solution us to use rsync with hard links.
In addition I&#8217;ve made it backup to an encrypted disk [...]]]></description>
			<content:encoded><![CDATA[<p>These scripts allows for remote incremental daily snapshot backups of linux, mac, and windows based servers.<br />
The theory is that we want a full snapshot of any given day without taking up the full disk space required.  The solution us to use rsync with hard links.<br />
In addition I&#8217;ve made it backup to an encrypted disk image stored on a usb drive in OS X.<br />
This makes it fairly portable and secure.<br />
So far the only down-side is that the password for the encrypted drive must be stored in the script (or in another text file) as plain text.  To avoid prying eyes the script has been chmod&#8217;ded 700, and is owned by root.</p>
<p>The setup:</p>
<ol>
<li>Create a public and private key pair and distribute the public to all linux servers to be backed up, and make sure sudo rsync can be run by the local backup user without requiring a password.</li>
<li>Create an encrypted disk image using Disk Utility under Mac OS X.  Ours is on a 250GB removable USB drive, but if you have access to a RAID drive with more space make it as large as you like.</li>
<li>
</li>
<p>Our setup creates a daily snapshot and maintains the last 30 days, rotating out the oldest to save space, adjust to the availability of your space.</p>
<li>Because rsync doesn&#8217;t run natively or easily on Windows I&#8217;m using XCOPY to backup windows servers to an intermediary Samba share on a linux box and then rsync&#8217;ing that.  While not perfect, it does allow for hard-linking, and daily snapshots.</li>
</ol>
<p>The script:<br />
<code>#!/bin/sh</p>
<p># make sure we're running as root<br />
if (( `id -u` != 0 )) ;<br />
then { echo "Sorry, must be root. Exiting..."; exit; }<br />
fi</p>
<p># Tell the log file we're starting:<br />
echo "==========================================="<br />
echo "Backup Started: "<br />
date </p>
<p>echo "Mounting the disk image"<br />
echo -n 'DiskImagePassword' | hdiutil attach -stdinpass /Volumes/backup1/backupimagename.dmg</p>
<p>echo "Rotating the backups"<br />
# Repeat for as many days as you would like to maintain your backup.<br />
# If you choose to run your cron job only once a week, this would be<br />
# the number of weekly backups<br />
mv /Volumes/backupimagename/daily-30 /Volumes/backupimagename/daily-tmp<br />
mv /Volumes/backupimagename/daily-29 /Volumes/backupimagename/daily-30<br />
mv /Volumes/backupimagename/daily-28 /Volumes/backupimagename/daily-29<br />
mv /Volumes/backupimagename/daily-27 /Volumes/backupimagename/daily-28<br />
mv /Volumes/backupimagename/daily-26 /Volumes/backupimagename/daily-27<br />
mv /Volumes/backupimagename/daily-25 /Volumes/backupimagename/daily-26<br />
mv /Volumes/backupimagename/daily-24 /Volumes/backupimagename/daily-25<br />
mv /Volumes/backupimagename/daily-23 /Volumes/backupimagename/daily-24<br />
mv /Volumes/backupimagename/daily-22 /Volumes/backupimagename/daily-23<br />
mv /Volumes/backupimagename/daily-21 /Volumes/backupimagename/daily-22<br />
mv /Volumes/backupimagename/daily-20 /Volumes/backupimagename/daily-21<br />
mv /Volumes/backupimagename/daily-19 /Volumes/backupimagename/daily-20<br />
mv /Volumes/backupimagename/daily-18 /Volumes/backupimagename/daily-19<br />
mv /Volumes/backupimagename/daily-17 /Volumes/backupimagename/daily-18<br />
mv /Volumes/backupimagename/daily-16 /Volumes/backupimagename/daily-17<br />
mv /Volumes/backupimagename/daily-15 /Volumes/backupimagename/daily-16<br />
mv /Volumes/backupimagename/daily-14 /Volumes/backupimagename/daily-15<br />
mv /Volumes/backupimagename/daily-13 /Volumes/backupimagename/daily-14<br />
mv /Volumes/backupimagename/daily-12 /Volumes/backupimagename/daily-13<br />
mv /Volumes/backupimagename/daily-11 /Volumes/backupimagename/daily-12<br />
mv /Volumes/backupimagename/daily-10 /Volumes/backupimagename/daily-11<br />
mv /Volumes/backupimagename/daily-9 /Volumes/backupimagename/daily-10<br />
mv /Volumes/backupimagename/daily-8 /Volumes/backupimagename/daily-9<br />
mv /Volumes/backupimagename/daily-7 /Volumes/backupimagename/daily-8<br />
mv /Volumes/backupimagename/daily-6 /Volumes/backupimagename/daily-7<br />
mv /Volumes/backupimagename/daily-5 /Volumes/backupimagename/daily-6<br />
mv /Volumes/backupimagename/daily-4 /Volumes/backupimagename/daily-5<br />
mv /Volumes/backupimagename/daily-3 /Volumes/backupimagename/daily-4<br />
mv /Volumes/backupimagename/daily-2 /Volumes/backupimagename/daily-3<br />
mv /Volumes/backupimagename/daily-1 /Volumes/backupimagename/daily-2<br />
mv /Volumes/backupimagename/daily-0 /Volumes/backupimagename/daily-1<br />
mkdir /Volumes/backupimagename/daily-0</p>
<p>echo "Backing up a Server"<br />
# Repeat as necessary<br />
# rsync uses RAM exponentially with the number of files you are backing up<br />
# You may want to break this command up into multiple if you have a server with many files such as a mail server<br />
rsync -az --delete --link-dest=/Volumes/backupimagename/daily-1/server.domain.com -e "ssh -i /path/to/private/key" backupuser@serverIP:/folder/to/backup /Volumes/backupimagename/daily-0/server.domain.com/</p>
<p>echo "Remote backup completed"<br />
date<br />
touch /Volumes/backupimagename/daily-0</p>
<p>echo "Deleting temp files"<br />
rm -rf /Volumes/backupimagename/daily-tmp</p>
<p># See how full the drive is<br />
df -h</p>
<p>date</p>
<p>echo "Unmounting the disk image:"<br />
hdiutil detach /Volumes/backupimagename</p>
<p># Tell the log file we're done:<br />
echo "Backup Finished: "<br />
echo "==========================================="<br />
echo " "<br />
date </code></p>
<p>Backing up the Windows to a Samba share is relatively straight forward. For security purposes I created Samba shares that are only accessible to one user, the account that runs the script on the Windows servers.</p>
<p>Samba Share:<br />
<code>[global]<br />
  workgroup = yourdomain<br />
  security = user<br />
wins support = yes<br />
[WinBackups]<br />
  path = /var/winbackups<br />
  comment = Windows Backups<br />
  public = no<br />
  write list = administrator</code></p>
<p>The Window script:<br />
<code>echo off<br />
:: variables<br />
set drive="\\linux-box\sambashare\backupfolder"<br />
set backup1="E:\FirstLocation"<br />
set backup2="E:\SecondLocation"<br />
set backupcmd=xcopy /s /c /d /e /h /i /r /k /y</p>
<p>echo ### Backing up First Location...<br />
%backupcmd% %backup1% %drive%</p>
<p>echo ### Backing up Second Location...<br />
%backupcmd% %backup2% %drive%</code></p>
<p>I&#8217;ve scheduled the xcopy script to run nightly well before the rsync script, with large sets of data you might have to run them on different days.</p>
<p>I&#8217;ve also scheduled the rsync backup script to run nightly and send me an email detailing what it wrote to the log.<br />
<code>MAILTO=" email@yourdomain.com"<br />
30 22 * * * /private/var/root/backup-scripts/backup-daily.sh 2&gt;&amp;1 | tee -a /private/var/root/backup-scripts/logs/backup-daily.log | mail -s "Daily Backup" email@yourdomain.com</code></p>
<p>If you wish to make a backup of your backup, simply copy the entire disk image to another drive.  Make sure the image isn&#8217;t mounted when you copy it.</p>
<p>Creating ssh keys:</p>
<p><a href="http://blog.innerewut.de/articles/2005/06/03/follow-up-on-remote-filesystem-snapshots-with-rsnapshot">This is based on this article.</a></p>
<h3>create a key pair on your backup server machine.</h3>
<p><em>This section should only be done once.</em></p>
<p><code>ssh-keygen -t rsa -f ~/rsync-key</code></p>
<p>This creates two files public and private keys.</p>
<p>place the private key into /etc/ssl/private/rsync.key and secure it.<br />
<code>mv rsync-key /etc/ssl/private/rsync.key<br />
chown root:root /etc/ssl/private/rsync.key<br />
chmod 600 /etc/ssl/private/rsync.key</code></p>
<p>The public key is the file that you place in the /home/(rsync_user)/.ssh/authorized_keys file on all your clients so that they know to allow you to use rsync as that user without a password. (Note that (rsync_user) should be replaced with the user you are using to connect with so a command that looks like this: <strong>rsync -avz -e &#8220;ssh -i /etc/ssl/private/rsync.key&#8221; user@server:/files/to/backup /backup/location</strong>, you would replace it with eanders.</p>
<p>Before you place it on your client machines you&#8217;ll want to tell it that it should only be valid if used from your backup server  and that it is only valid for the rsync command.  This is security for your <strong>client</strong> not your server.  Having this <strong>public</strong> key means that anyone on a machine with the <strong>private</strong> key can connect and execute stuff on your machine without a password.</p>
<p>You should place the following at the beginning of the rsync-key.pub file:<br />
<code>from="ip.of.backup.server",command="/home/(rsync_user)/.rsync/validate-rsync.sh"</code><br />
The file would then look something like this:<br />
<code>from="10.0.100.5",command="/home/(rsync_user)/.rsync/validate-rsync.sh" ssh-rsa AAAAB3NzaC1yc2...</code></p>
<p><em>Repeat the following section as needed:</em></p>
<p>In most circumstances you will only be backing up information that the user you connect as has permission to.  In these cases you should copy the edited rsync-key.pub file to the users home directory and place it in the .ssh/authorized_keys file.  If your user was eanders you would move the file to eanders&#8217; home directory and do something like this:</p>
<p><code>mkdir /home/(rsync-user)/.ssh<br />
cat rsync-key.pub &gt;&gt; .ssh/authorized_keys</code><br />
This will place the key at the end of the authorized_keys file.  It is now safe to delete the key.<br />
Finally you&#8217;ll need to place the validate-rsync script in the /home/(rsync-user)/.rsync/ directory on your client computer so that you can allow rsync to be run without a password, but nothing else will work.<br />
You can just create the .rsync directory and then paste the following into validate-rsync.sh within that file.<br />
<code>mkdir /home/(rsync-user)/.rsync<br />
vi /home/(rsync-user)/.rsync/validate-rsync.sh</code></p>
<p><code>#!/bin/sh<br />
case "$SSH_ORIGINAL_COMMAND" in<br />
*\&amp;*)<br />
echo "Rejected"<br />
;;<br />
*\;*)<br />
echo "Rejected"<br />
;;<br />
rsync*)<br />
$SSH_ORIGINAL_COMMAND<br />
;;<br />
*)<br />
echo "Rejected"<br />
;;<br />
esac</code></p>
<p>Then we tell it that the validate-rsync.sh file should be executable<br />
<code>chmod 755 /home/(rsync-user)/.rsync/validate-rsync.sh</code></p>
<p>At this point you should be able to run an rsync command from your backup server as root to backup anything that (rsync-user) has permission to backup.  Something like:<br />
<code>rsync -avz -e "ssh -i /etc/ssl/private/rsync.key" (rsync-user)@client_ip_address:/files/to/backup /backup/location</code></p>
<h3>If you want to backup a machine which contains files that are only readable by root you need to do the following</h3>
<p>This will be the case where you have a file server that hosts files which are only readable by inidividual users (root always has access) but your administrator user can&#8217;t access their files without the use of sudo.</p>
<p>First you will create an unprivileged user on the client machine, we are using the username rsync:<br />
<code>adduser rsync</code><br />
Give this user whatever password you want.</p>
<p>Then follow the instructions above replacing (rsync-user) with the user rsync.</p>
<p>Next you will need to login and gain root access to the server and we&#8217;ll move the rsync application and replace it with a script.<br />
<code>mv /usr/bin/rsync /usr/bin/rsync_real<br />
vi /usr/bin/rsync</code></p>
<p>Fill the file with this script:</p>
<p><code>#!/bin/sh<br />
/usr/bin/sudo /usr/bin/rsync_real "$@"</code></p>
<p>Then make it executable:</p>
<p><code>chmod 755 /usr/bin/rsync</code></p>
<p>This tells the computer that when you ask it to run rsync it should actually run it as root, so issuing:<br />
<code>rsync</code><br />
will result in this command actually running:<br />
<code>sudo rsync_real</code></p>
<p>Finally you need to tell your client computer that the (rsync-user) should be able to run rsync_real as sudo without a password since we&#8217;ll be running it without an interactive shell.  To do this run, as root:<br />
<code>visudo</code><br />
and add the following line:<br />
<code>(rsync-user)   ALL= NOPASSWD: /usr/bin/rsync_real</code></p>
<p>For our purposes we are making a single script that will run various rsync commands to backup servers across the wan, this script will reside in /var/usb1/backup-district.sh<br />
In addition we will rsync /var/usb1 to /var/usb2 each night with an rsync command.</p>
<p>If you are having problems where it is continually asking for your password (even though you’ve done all the key stuff) make sure that the line you added to visudo is at the bottom. That file appears to be processed from the top down instead of going from broad to granular.<br />
This is especially a problem when your user is also an administrator and already has an entry in visudo.</p>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2006/09/23/backing-up-linux-mac-and-windows-servers-vi-rsync/feed/</wfw:commentRSS>
		</item>
		<item>
		<title>Allow users to reset their Active Directory passwords via a web form.</title>
		<link>http://elliot.ecowizards.com/wp/archives/2006/09/23/allow-users-to-reset-their-active-directory-passwords-via-a-web-form/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2006/09/23/allow-users-to-reset-their-active-directory-passwords-via-a-web-form/#comments</comments>
		<pubDate>Sat, 23 Sep 2006 12:17:15 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>Scripting</category>
	<category>Active Directory</category>
	<category>Linux</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2006/09/23/allow-users-to-reset-their-active-directory-passwords-via-a-web-form/</guid>
		<description><![CDATA[This is the functioning post that allowed us to be able to reset user&#8217;s Active Directory passwords via a php page hosted on a linux box.  Combine this with the password resetting delegation of a previous post for a bit more security.  We&#8217;re also going to set this up to figure out what [...]]]></description>
			<content:encoded><![CDATA[<p>This is the functioning post that allowed us to be able to reset user&#8217;s Active Directory passwords via a php page hosted on a linux box.  Combine this with the password resetting delegation of a previous post for a bit more security.  We&#8217;re also going to set this up to figure out what group someone is in and let teachers reset thier students passwords.</p>
<p>Before you do this you need to generate an ssl certificate on the windows box and import it into the linux box, see a previous post.</p>
<p>This information was mostly gleaned from here:<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
The post: <a href="http://forums.devshed.com/ldap-programming-76/modifying-active-directory-passwords-through-php-and-iis-74683-7.html">http://forums.devshed.com/ldap-programming-76/modifying-active-directory-passwords-through-php-and-iis-74683-7.html</a><br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>Here is the final script I am using for a user to change his or her password.<br />
The form to submit to the script:<br />
<code>&lt;form method="post" action="change_password.php"&gt;<br />
username: &lt;input type="text" name="uid" /&gt;<br />
&lt;br /&gt;<br />
password: &lt;input type="password" name="password" /&gt;<br />
&lt;br /&gt;<br />
new password: &lt;input type="password" name="newpass1" /&gt;<br />
&lt;br /&gt;<br />
confirm new password: &lt;input type="password" name="newpass2" /&gt;<br />
&lt;br /&gt;<br />
&lt;input type="submit" name="submit" value="Change Password" /&gt;<br />
&lt;/form&gt;</code><br />
Config File:<br />
<code>&lt; ?PHP<br />
/*** Variable Settings ***/<br />
// administrative bind user<br />
// Admin account with permission to reset passwords<br />
$adminUID = 'adminusername';<br />
$adminPass = 'AdministrativePassword';<br />
// ldap server info, moved to config file<br />
$ldapserver = 'ldapserver.mydomain.com';<br />
$baseDN = 'DC=mydomain,DC=com';<br />
?&gt;</code><br />
Script:<br />
<code>&lt; ?PHP<br />
require_once('/var/www_config_files/secure/change_password.inc.php');<br />
/*** Variable Settings ***/<br />
$uid = $_POST['uid']; // Should be something like jsmith<br />
$userbindDN = $uid . '@yourdomain.com'; // jsmith@yourdomain.com<br />
//existing password<br />
$userbindPass = $_POST['password'];<br />
// new password<br />
$passwd1 = $_POST['newpass1'];<br />
$passwd2 = $_POST['newpass2'];<br />
// administrative bind user<br />
// Admin account with permission to reset passwords<br />
$authbindDN = $adminUID . '@yourdomain.com';<br />
$authbindPass = $adminPass;</p>
<p>// ldap server info, moved to config file<br />
//$ldapserver = 'ldapserver.yourdomain.com';<br />
//$baseDN = 'DC=mydomain,DC=com';<br />
/**************************/</p>
<p>/************* Main Script Code ***************/<br />
/**  Connect SSL to Ldap Server **/</p>
<p>$ldap = ldap_connect('ldaps://'.$ldapserver,686);<br />
//$ldap = ldap_connect($ldapserver);<br />
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);<br />
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);</p>
<p>echo "Verifying old password ...&lt;br&gt;";</p>
<p>ldap_bind($ldap, $userbindDN, $userbindPass);</p>
<p>if (ldap_errno($ldap) !== 0)<br />
{<br />
    exit('ERROR: Username or Password Invalid - <a href="./" title="Please try again">Please Try again</a>');<br />
    // exit('ERROR: User ID/Password Invalid - '.ldap_error($ldap));<br />
}<br />
/**  We got this far, let's bind with an admin user **/<br />
echo "Authenticated, changing password ...&lt;br /&gt;";</p>
<p>ldap_bind($ldap, $authbindDN, $authbindPass);<br />
if (ldap_errno($ldap) !== 0)<br />
{<br />
      exit('ERROR: Unable to bind with admin user info - '.ldap_error<br />
($ldap));<br />
}</p>
<p>// Searching for the user<br />
$filter = "(|(samaccountname=$uid))";<br />
$justthese = array("cn");</p>
<p>$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);<br />
// no matching records<br />
$info = ldap_get_entries($ldap, $searchResults);<br />
if ($searchResults === false)<br />
{<br />
   exit('User ($uid) not found.');<br />
}<br />
if (!is_resource($searchResults))<br />
{<br />
   exit('Error in search results.');<br />
}</p>
<p>//echo "&lt;pre&gt;";<br />
//print_r($info);<br />
//echo "&lt;/pre&gt;";</p>
<p>$entry = ldap_first_entry($ldap, $searchResults);<br />
if (!is_resource($entry))<br />
{<br />
     exit('Couldn\'t get entry');<br />
}<br />
$userDn = ldap_get_dn($ldap, $entry);</p>
<p>if ($passwd1 == $passwd2){<br />
     // prepare data<br />
     $newPassword = $passwd1;<br />
     $newPassword = "\"" . $newPassword . "\"";<br />
     $len = strlen($newPassword);<br />
     for($i = 0; $i &lt; $len; $i++)<br />
   {<br />
         $newPassw .= "{$newPassword{$i}}\000";<br />
   }<br />
     $newPassword = $newPassw;<br />
     $userdata['unicodePwd'] = $newPassword;</p>
<p>//   echo "Changing Password&lt;br /&gt;&lt;br /&gt;";<br />
   echo "Username = ".$uid."&lt;br /&gt;";<br />
//   echo "User login ID = ".$userbindDN."&lt;br /&gt;";</p>
<p>   $result = ldap_mod_replace($ldap, $userDn , $userdata);<br />
     if($result)<br />
   {<br />
//        echo "User modified!&lt;br /&gt;" ;<br />
     }else{<br />
        echo "There was a problem!&lt;br /&gt;";<br />
        echo ldap_error($ldap)."&lt;br /&gt;";<br />
   }<br />
     /**  Now try to bind with the username and new password to<br />
ensure change**/<br />
     echo "Now testing new password to insure change&lt;br /&gt;";<br />
     ldap_bind($ldap, $userbindDN, $passwd1);<br />
     if (ldap_errno($ldap) !== 0)<br />
     {<br />
         exit('ERROR: User ID/Password Invalid - '.ldap_error($ldap));<br />
     }else{<br />
         echo "Password Verified.&lt;br /&gt;Password change complete.&lt;br /&gt;";<br />
        echo "&lt;p&gt;You may now close this window, your password for<br />
                your computer, email, and web access has been updated.&lt;/p&gt;";<br />
     }<br />
}<br />
?&gt;</code></p>
<p>In addition I have modified this script to allow teachers to reset student passwords:<br />
The page that requests information:<br />
<code>&lt;html&gt;<br />
&lt;head&gt;<br />
&lt;title&gt;Reset Student Password&lt;/title&gt;<br />
&lt;/head&gt;<br />
&lt;body&gt;<br />
&lt;h2&gt;Reset a Student Password&lt;/h2&gt;<br />
&lt;p&gt;Teachers may use this form to reset the password of a student in their class. Simply fill out all of the fields and click the "Reset Student Password" button.&lt;/p&gt;<br />
&lt;form method="post" action="change_password.php"&gt;<br />
Teacher Username: &lt;input type="text" name="uid" /&gt;<br />
&lt;br /&gt;<br />
Teacher Password: &lt;input type="password" name="password" /&gt;<br />
&lt;br /&gt;<br />
Student Username: &lt;input type="text" name="student_uid" /&gt;<br />
&lt;br /&gt;<br />
New Student Password: &lt;input type="password" name="newpass1" /&gt;<br />
&lt;br /&gt;<br />
Confirm Student Password: &lt;input type="password" name="newpass2" /&gt;<br />
&lt;br /&gt;<br />
&lt;input type="submit" name="submit" value="Reset Student Password" /&gt;<br />
&lt;/form&gt;<br />
&lt;/body&gt;<br />
&lt;/html&gt;</code></p>
<p>Uses the same config file from above.</p>
<p>The script:<br />
<code>&lt; ?PHP<br />
require_once('/var/www_config_files/secure/change_password.inc.php');<br />
/*** Variable Settings ***/<br />
$uid = $_POST['uid']; // Should be something like jsmith<br />
$student_uid = $_POST['student_uid'];<br />
$userbindDN = $uid . '@yourdomain.com'; // jsmith@yourdomain.com<br />
//existing password<br />
$userbindPass = $_POST['password'];<br />
// new password<br />
$passwd1 = $_POST['newpass1'];<br />
$passwd2 = $_POST['newpass2'];<br />
// administrative bind user<br />
// Admin account with permission to reset passwords<br />
$authbindDN = $adminUID . '@yourdomain.com';<br />
$authbindPass = $adminPass;<br />
// ldap server info, moved to config file<br />
//$ldapserver = 'ldapserver.yourdomain.com';<br />
//$baseDN = 'DC=yourdomain,DC=com';<br />
/**************************/<br />
/*<br />
The theory: Bind as the teacher to verify password and get group membership<br />
Bind as the admin<br />
Grab student dn and group membership<br />
if the teacher is a member of teachers and the student is a member of students<br />
check to see that they are both a member of another group (other than Domain Users)<br />
If they are: reset the student password</p>
<p>*/<br />
/************* Main Script Code ***************/<br />
/**  Connect SSL to LDAP Server **/<br />
?&gt;<br />
&lt;html&gt;<br />
&lt;head&gt;<br />
&lt;title&gt;Reset Student Password Results&lt;/title&gt;<br />
&lt;/head&gt;<br />
&lt;body&gt;<br />
&lt; ?<br />
//echo "Connecting SSL to server&lt;br&gt;";<br />
$ldap = ldap_connect('ldaps://'.$ldapserver,686);<br />
//$ldap = ldap_connect($ldapserver);<br />
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);<br />
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);<br />
/**  Now try to bind with the username and password **/<br />
echo "Verifying teacher credentials ...&lt;br /&gt;";<br />
ldap_bind($ldap, $userbindDN, $userbindPass);<br />
if (ldap_errno($ldap) !== 0)<br />
{<br />
     exit('ERROR: User Teacher username or password invalid');<br />
     //exit('ERROR: User ID/Password Invalid - '.ldap_error($ldap));<br />
}<br />
echo "Teacher credentials verified. &lt;br /&gt;";</p>
<p>/**  We got this far, let's bind with an admin user **/<br />
ldap_bind($ldap, $authbindDN, $authbindPass);<br />
if (ldap_errno($ldap) !== 0)<br />
{<br />
      exit('ERROR: Unable to bind with admin user info - '.ldap_error<br />
($ldap));<br />
}</p>
<p>// Figure out who the teacher is so we can get their membership info<br />
$filter = "(&amp;(samaccountname=".$uid."))";<br />
$justthese = array("dn");</p>
<p>$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);<br />
// no matching records<br />
$info = ldap_get_entries($ldap, $searchResults);<br />
if ($searchResults === false)<br />
{<br />
   exit('User ($uid) not found in AD');<br />
}<br />
if (!is_resource($searchResults))<br />
{<br />
   exit('Error in search results.');<br />
}</p>
<p>//echo "&lt;pre&gt;";<br />
//print_r($info);<br />
for ($i=0; $i &lt; $info["count"]; $i++) {<br />
        //print "LDAP DN: " . $info[$i]['dn'];<br />
        //echo "&lt;br /&gt;&lt;br /&gt;";<br />
        $userDN = $info[$i]['dn'];<br />
        }<br />
//echo "&lt;/pre&gt;";</p>
<p>$filter = "(&amp;(samaccountname=".$student_uid."))";<br />
$justthese = array("dn");</p>
<p>$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);<br />
// no matching records<br />
$info = ldap_get_entries($ldap, $searchResults);<br />
if ($searchResults === false)<br />
{<br />
   exit('User ($uid) not found in AD');<br />
}<br />
if (!is_resource($searchResults))<br />
{<br />
   exit('Error in search results.');<br />
}</p>
<p>for ($i=0; $i &lt; $info["count"]; $i++) {<br />
        //print "LDAP DN: " . $info[$i]['dn'];<br />
        //echo "&lt;br /&gt;&lt;br /&gt;";<br />
        $student_userDN = $info[$i]['dn'];<br />
}</p>
<p>// Searching for the teacher<br />
$filter = "(&amp;(objectCategory=group)(member=".$userDN."))";<br />
$justthese = array("dn");</p>
<p>$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);<br />
// no matching records<br />
$info = ldap_get_entries($ldap, $searchResults);<br />
if ($searchResults === false)<br />
{<br />
   exit('User ($uid) not found in AD');<br />
}<br />
if (!is_resource($searchResults))<br />
{<br />
   exit('Error in search results.');<br />
}</p>
<p>//echo "&lt;pre&gt;";<br />
for ($i=0; $i &lt; $info["count"]; $i++) {<br />
        $teacher_groups[] = $info[$i]['dn'];<br />
}<br />
//print_r($teacher_groups);<br />
//echo "&lt;/pre&gt;";</p>
<p>// Searching for the student<br />
$filter = "(&amp;(objectCategory=group)(member=".$student_userDN."))";<br />
$justthese = array("dn");</p>
<p>$searchResults = ldap_search($ldap, $baseDN, $filter, $justthese);<br />
// no matching records<br />
$info = ldap_get_entries($ldap, $searchResults);<br />
if ($searchResults === false)<br />
{<br />
   exit('User ($student_uid) not found in AD');<br />
}<br />
if (!is_resource($searchResults))<br />
{<br />
   exit('Error in search results.');<br />
}</p>
<p>//echo "&lt;/pre&gt;&lt;pre&gt;";<br />
for ($i=0; $i &lt; $info["count"]; $i++) {<br />
        $student_groups[] = $info[$i]['dn'];<br />
}<br />
//print_r($student_groups);<br />
//echo "&lt;/pre&gt;";</p>
<p>foreach ($teacher_groups as $teacher_group) {<br />
        $is_teacher = stristr($teacher_group, 'Teacher');<br />
}<br />
//echo "$is_teacher &lt;br /&gt;";<br />
if($is_teacher !== 'FALSE') {<br />
echo "Verifying group membership...";<br />
// In this case the teacher must be a member of the same group as the student<br />
// This was added to allow fifth grade teachers to reset fifth grades student<br />
// passwords, but not sixth grade student passwords.<br />
        foreach ($student_groups as $student_group) {<br />
                $same_group = array_search($student_group, $teacher_groups);<br />
                //echo $same_group;</p>
<p>        }<br />
        if($same_group !== 'FALSE') {<br />
                echo "Group membership verified";<br />
                echo "&lt;br /&gt;";<br />
                echo "Resetting student password...";</p>
<p>        //---------------------------------------------//<br />
// Check Passwords to make sure they match.<br />
if ($passwd1 == $passwd2){<br />
     // prepare data<br />
        $newPassword = $passwd1;<br />
        $newPassword = "\"" . $newPassword . "\"";<br />
        $len = strlen($newPassword);<br />
        for($i = 0; $i &lt; $len; $i++) {<br />
                $newPassw .= "{$newPassword{$i}}\000";<br />
        }<br />
        $newPassword = $newPassw;<br />
        $userdata['unicodePwd'] = $newPassword;</p>
<p>        echo "for username = ".$student_uid."&lt;br /&gt;";</p>
<p>   $result = ldap_mod_replace($ldap, $student_userDN, $userdata);<br />
     if($result)<br />
   {<br />
        echo "User modified!&lt;br /&gt;" ;<br />
     }else{<br />
        echo "There was a problem!&lt;br /&gt;";<br />
        echo ldap_error($ldap)."&lt;br /&gt;";<br />
   }<br />
     /**  Now try to bind with the username and new password to<br />
insure change**/<br />
     echo "Now testing new password to ensure change&lt;br /&gt;";<br />
     ldap_bind($ldap, $student_userDN, $passwd1);<br />
     if (ldap_errno($ldap) !== 0)<br />
     {<br />
         exit('ERROR: User ID/Password Invalid - '.ldap_error($ldap));<br />
     }else{<br />
         echo "Password Verified.&lt;br /&gt;Password change complete.&lt;br /&gt;";<br />
        echo "&lt;p&gt;You may now close this window, the student password has been modified.&lt;/p&gt;&lt;p&gt;";<br />
     }<br />
}</p>
<p>//--------------------------------------------------//</p>
<p>        }<br />
}<br />
else {<br />
        echo "Sorry we could not verity that you are a teacher.";<br />
}<br />
?&gt;<br />
&lt;/p&gt;&lt;/pre&gt;&lt;/body&gt;<br />
&lt;/html&gt;<br />
</code>
</p>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2006/09/23/allow-users-to-reset-their-active-directory-passwords-via-a-web-form/feed/</wfw:commentRSS>
		</item>
		<item>
		<title>Delegate Password Resetting Control in Active Directory</title>
		<link>http://elliot.ecowizards.com/wp/archives/2006/09/23/delegate-password-resetting-control-in-active-directory/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2006/09/23/delegate-password-resetting-control-in-active-directory/#comments</comments>
		<pubDate>Sat, 23 Sep 2006 11:50:11 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>Active Directory</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2006/09/23/delegate-password-resetting-control-in-active-directory/</guid>
		<description><![CDATA[This allows you to have a user who can reset passwords for other users, but not necessarily administer the domain.  We are using it so people can reset their passwords from a PHP page and so teachers will be able to reset their students&#8217; passwords.
See:http://support.microsoft.com/default.aspx?scid=KB;en-us;296999

Create a group that you want to be able to [...]]]></description>
			<content:encoded><![CDATA[<p>This allows you to have a user who can reset passwords for other users, but not necessarily administer the domain.  We are using it so people can reset their passwords from a PHP page and so teachers will be able to reset their students&#8217; passwords.<br />
See:<a href="http://support.microsoft.com/default.aspx?scid=KB;en-us;296999">http://support.microsoft.com/default.aspx?scid=KB;en-us;296999</a></p>
<ol>
<li>Create a group that you want to be able to reset passwords</li>
<li>Add the users you want to give the password resetting ability to to that group.</li>
<li>Right Click on an OU that you want the resetters to be able to reset and choose &#8220;Delegate Control&#8221;</li>
<li>Click Next and then add the Group you just created to the empty box.</li>
<li>Click Next and then Check the Reset Password Box.</li>
<li>Click Finish</li>
</ol>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2006/09/23/delegate-password-resetting-control-in-active-directory/feed/</wfw:commentRSS>
		</item>
		<item>
		<title>Network Printers Assigned by Computer via a script run by a GPO</title>
		<link>http://elliot.ecowizards.com/wp/archives/2006/09/23/network-printers-assigned-by-computer-via-a-script-run-by-a-gpo/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2006/09/23/network-printers-assigned-by-computer-via-a-script-run-by-a-gpo/#comments</comments>
		<pubDate>Sat, 23 Sep 2006 11:46:17 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>Scripting</category>
	<category>Active Directory</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2006/09/23/network-printers-assigned-by-computer-via-a-script-run-by-a-gpo/</guid>
		<description><![CDATA[To assign a network printer via GPO you can either assign them to the user or to a computer.  Scripting Printer installation for a user is fairly simple and straight forward.


Create a script to map the appropriate printer:
Option Explicit
Dim objNetwork
Set ObjNetwork=CreateObject("Wscript.Network")
objNetwork.addWindowsPrinterConnection"\\serveripaddress\printername"
Wscript.Quit


In windows explorer copy the file.  Open GPMC.


Create a GPO for the appropriate [...]]]></description>
			<content:encoded><![CDATA[<p>To assign a network printer via GPO you can either assign them to the user or to a computer.  Scripting Printer installation for a user is fairly simple and straight forward.</p>
<ol>
<li>
Create a script to map the appropriate printer:<br />
<code>Option Explicit<br />
Dim objNetwork</p>
<p>Set ObjNetwork=CreateObject("Wscript.Network")<br />
objNetwork.addWindowsPrinterConnection"\\serveripaddress\printername"<br />
Wscript.Quit</code>
</li>
<li>
In windows explorer copy the file.  Open GPMC.
</li>
<li>
Create a GPO for the appropriate user OU.  Edit the settings and navigate to:<br />
<code>User Configuration-&gt;Windows Settings-&gt;Scripts-&gt;Logon</code>
</li>
<li>Add a script, just type the name of the file, not the path.</li>
<li>Click the Show Files button and Paste your script into the Folder that opens.</li>
</ol>
<p>If you login as a user within this OU you should now see the printer is available to you.</p>
<p>To assign a printer by computer, such as in a lab situation where anyone who logs in should have access to the printer follow the previous steps, but make the GPO on the OU that contains the computers.  Note that you are making a GPO that targets users, but applying it to computers.  To make this work you need to add one more setting to your GPO.</p>
<ol>
<li>Navigate to:<br />
<code>Computer Configuration-&gt;Administrative Templates-&gt;System-&gt;Group Policy</code>
</li>
<li>Set the value of &#8220;User Group Policy loopback processing mode&#8221; to Enabled and use the &#8220;Merge&#8221; option.</li>
</ol>
<p>If you now login to a computer in the OU that you just applied the policy to you will have access to that printer, but if you move to a computer outside the OU it won&#8217;t be available to you.</p>
<p>The Group Policy Loopback processing mode of merge essentially means that policies should be applied to the computer at startup, the user at login, and then the user section of the computer policy afterward (still at login.)</p>
<p>Here is a relatively concise set of information on <a href="http://technet2.microsoft.com/WindowsServer/en/library/abe2b1a9-975f-4b2f-b771-9e6a903e97db1033.mspx?mfr=true">GPO loopback.</a>
</p>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2006/09/23/network-printers-assigned-by-computer-via-a-script-run-by-a-gpo/feed/</wfw:commentRSS>
		</item>
		<item>
		<title>LTSP booting old mac hardware</title>
		<link>http://elliot.ecowizards.com/wp/archives/2006/05/18/ltsp-booting-old-mac-hardware/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2006/05/18/ltsp-booting-old-mac-hardware/#comments</comments>
		<pubDate>Thu, 18 May 2006 15:04:53 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>Linux</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2006/05/18/ltsp-booting-old-mac-hardware/</guid>
		<description><![CDATA[This will help you boot 333mhz and 350mhz imacs.
Some places to start:
https://wiki.edubuntu.org/EdubuntuDocumentation/LTSPCrossArchSetup
http://linuxcommand.org/man_pages/dhcpd8.html
The trick I&#8217;ve found is that the ltsp-build-client script doesn&#8217;t complete correctly unless you run it all locally.  So, after you have completed the server section, move to the client.  Instead of a live disk I strongly suggest installing edubuntu locally and [...]]]></description>
			<content:encoded><![CDATA[<p>This will help you boot 333mhz and 350mhz imacs.<br />
Some places to start:</p>
<p><a href="https://wiki.edubuntu.org/EdubuntuDocumentation/LTSPCrossArchSetup">https://wiki.edubuntu.org/EdubuntuDocumentation/LTSPCrossArchSetup</a><br />
<a href="http://linuxcommand.org/man_pages/dhcpd8.html">http://linuxcommand.org/man_pages/dhcpd8.html</a></p>
<p>The trick I&#8217;ve found is that the ltsp-build-client script doesn&#8217;t complete correctly unless you run it all locally.  So, after you have completed the server section, move to the client.  Instead of a live disk I strongly suggest installing edubuntu locally and getting it up-to-date with <code>apt-get update &amp;&amp; apt-get upgrade</code>.<br />
After running ltsp-build-client you need to move two folders from the client to the server.<br />
You can<br />
<code>scp -r /var/lib/tftboot/* administrator@serverip:~/</code><br />
and then move it (as root) to the /var/lib/tftpboot folder on the server) </p>
<p>Then I found that mounting /opt/ltsp from the server onto the client in a different location worked well for moving the files.</p>
<p><code>mkdir /opt/ltsp/server<br />
sudo mount serverip:/opt/ltsp /opt/ltsp/server<br />
cp -a /opt/ltsp/powerpc /opt/ltsp/server/</code><br />
Finally to boot 333mhz machines it looks like you&#8217;ll have to run though the whole thing again, but on the older machine.  Then it needs to netboot using bootp instead of dhcp so you have to add individual lines for each client in /etc/ltsp/dhcpd.conf that references the mac address of the machine.<br />
Something like this:<br />
<code>host orange1 {<br />
        hardware ethernet 00:50:e4:b9:18:07;<br />
        fixed-address 192.168.0.201;<br />
        filename "/ltsp/yaboot";<br />
        option root-path "/opt/ltsp/powerpc";<br />
}</code></p>
<p>Your entire dhcpd.conf file should look like this:</p>
<p><code>authoritative;</p>
<p>allow booting;<br />
allow bootp;</p>
<p>subnet 192.168.0.0 netmask 255.255.255.0 {<br />
  range 192.168.0.20 192.168.0.200;<br />
  option broadcast-address 192.168.0.255;<br />
  option routers 192.168.0.1;<br />
  option subnet-mask 255.255.255.0;</p>
<p>}</p>
<p>option root-path "/opt/ltsp/powerpc_indigo";</p>
<p>if substring (option vendor-class-identifier, 0, 9) = "AAPLBSDPC" {<br />
        filename      "/ltsp/yaboot";<br />
        option vendor-class-identifier "AAPLBSDPC";<br />
        option vendor-encapsulated-options<br />
                01:01:02:08:04:01:00:00:01:82:05:69:6d:61:63:34;<br />
}<br />
else {<br />
        filename    "/ltsp/pxelinux.0";<br />
}</p>
<p>host orange1 {<br />
        hardware ethernet 00:50:e4:b9:18:07;<br />
        fixed-address 192.168.0.201;<br />
        filename "/ltsp/yaboot";<br />
        option root-path "/opt/ltsp/powerpc";<br />
}</code>
</p>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2006/05/18/ltsp-booting-old-mac-hardware/feed/</wfw:commentRSS>
		</item>
		<item>
		<title>Modifying a styleswitcher to make a language switcher.</title>
		<link>http://elliot.ecowizards.com/wp/archives/2005/03/26/modifying-a-styleswitcher-to-make-a-language-switcher/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2005/03/26/modifying-a-styleswitcher-to-make-a-language-switcher/#comments</comments>
		<pubDate>Sat, 26 Mar 2005 13:38:04 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>Scripting</category>
	<category>Coding</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2005/03/26/modifying-a-styleswitcher-to-make-a-language-switcher/</guid>
		<description><![CDATA[A quick how-to on how to make a site multi-lingual with a splash of php.]]></description>
			<content:encoded><![CDATA[<p>This is a modified version of <a href="http://www.alistapart.com/articles/phpswitch/">Alistapart&#8217;s PHP styleswitcher.</a>  The idea was to take a site that is both in English and Spanish and allow someone to make a semi-permanent decision on which language they would like to use.  This project came through a friend of mine, and with few details, so I figured I&#8217;d pick a method that could be easily changed if it didn&#8217;t fit his current setup.  </p>
<p><a href="http://www.pixelconductor.com/david/index_en.php">Here is a rudimentary example if you want to see it in action.</a> (After you&#8217;ve chosen your language just try going to the other page in the address bar!)</p>
<p>My idea was that the english pages would be named something like index_en.html and the spanish pages would be named index_es.html.  This means that to switch from one to the other, all you need to do is swap out the bit at the end of the filename.  (You could modify this so that it swapped out a directory name instead, and then you could have a /en/ directory and a /es/ directory in the path name and essentially maintain two different folder structurers.)  Okay, enough hypotheticals.  Here is how to do it.</p>
<p>The first page you need to build is a modified version of the alistapart styleswitcher.  Place this code in a file called switcher.php:<br />
<code>&lt; ?php<br />
setcookie ('sitelang', $set, time()+31536000, '/', 'yourdomain.com', '0');<br />
header("Location: $HTTP_REFERER");<br />
?&gt;</code><br />
This simply takes an argument passed to it through the url bar and redirects the user back to the same page but with a cookie set that contains the information passed to it. (Don&#8217;t forget to change the domain name.)</p>
<p>Next you need to put the following snippets of code a the very top of each of your pages, one for the Spanish pages and one for the English pages:</p>
<p><code>&lt; ?php<br />
 if ($sitelang == '_en') {<br />
	$location = $_SERVER["PHP_SELF"];<br />
	$goto = str_replace("_es", "_en", "$location");<br />
 	header("Location: $goto");<br />
 	die();<br />
 }<br />
?&gt;</code><br />
The first one goes in the top of your Spanish pages, an simply checks to see if the cookie says you should be on an english page, if it does, it replaces the _es in the page you are currently on with _en and then redirects you to the English page.<br />
<code>&lt; ?php<br />
 if ($sitelang == '_es') {<br />
	$location = $_SERVER["PHP_SELF"];<br />
	$goto = str_replace("_en", "_es", "$location");<br />
 	header("Location: $goto");<br />
 	die();<br />
 }<br />
?&gt;</code><br />
The English version works exactly the same way, except that it replaces _en with _es.</p>
<p>Finally you need to have some way for your browsers to choose their preferred language.  In this case I chose to pass the argument through the URL, but you could create a form on your page that had a submit button and some radio buttons to make your choice.  The following two links should be present somewhere on every page.<br />
<code><a href="./switcher.php?set=_es">Oprima aquí para español!<br />
&lt;br /&gt;<br />
</a><a href="./switcher.php?set=_en">Click here for English!</a></code><br />
The first line lets you choose Spanish as your language and the second lets you choose English.<br />
Sorry if my Spanish is a little out of practice. . .<br />
Post a comment if you find this useful.</p>
<h3>Update 3-28-05</h3>
<p>After a little more discussion about the specifics of what my friend wanted, we came to the conclusion that while this works really well, it wasn&#8217;t quite what he was looking for.  </p>
<p>The site he was looking to use this is actually 3 sites, where the first is a splash site for the other two.<br />
So, the final decision was made to have the splash site allow a user to choose their language and to choose if that language is permanent or not.  Were still working out the specifics, but here&#8217;s a way to set the language and forward people to the correct url based on their decision.</p>
<p>The index page for the splash site would contain the following code:<br />
<code>&lt; ?PHP<br />
 if ($sitelang == '_es') {<br />
 	header("Location: http://www.thespanishsite.org");<br />
 	die();<br />
 }<br />
 if ($sitelang == '_en') {<br />
 	header("Location: http://www. thesenglishsite.org");<br />
 	die();<br />
 }<br />
?&gt;</code><br />
Then within the body you need a form:<br />
<code>&lt;form action="switcher.php" method="post" name="radio"&gt;<br />
&lt;label for="set"&gt;Español&lt;/label&gt;<br />
&lt;input name="set" id="set" type="radio" value="_es" checked="checked" /&gt;<br />
&lt;br /&gt;<br />
&lt;label for="set"&gt;English&lt;/label&gt;<br />
&lt;input name="set" id="set" type="radio" value="_en" /&gt;<br />
&lt;br /&gt;<br />
&lt;label for="length"&gt;Remember this setting&lt;/label&gt;&lt;input name="length" id="length" type="checkbox" value="year" /&gt;<br />
&lt;br /&gt;<br />
&lt;input type="submit" value="Set Language"/&gt;<br />
&lt;/form&gt;</code><br />
Then the switcher needs to be changed so that it knows about temporary and long term cookies:<br />
<code>&lt; ?php<br />
if ($length == 'year') {<br />
	setcookie ('sitelang', $set, time()+31536000, '/', 'yoursplashdomain.com', '0');<br />
	}<br />
else {<br />
	// this cookie just needs to survive long enough to get the user to the right place<br />
	// one time.<br />
	setcookie ('sitelang', $set, time()+60, '/', 'yoursplashdomain.com', '0');<br />
	}<br />
header("Location: $HTTP_REFERER");<br />
?&gt;</code><br />
That should do it.<br />
<a href="http://www.pixelconductor.com/david/index.php">You can try out a demo here.</a>
</p>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2005/03/26/modifying-a-styleswitcher-to-make-a-language-switcher/feed/</wfw:commentRSS>
		</item>
		<item>
		<title>Safari Double-dip, slow dns, and page load failure final fix</title>
		<link>http://elliot.ecowizards.com/wp/archives/2005/02/22/safari-double-dip-slow-dns-and-page-load-failure-final-fix/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2005/02/22/safari-double-dip-slow-dns-and-page-load-failure-final-fix/#comments</comments>
		<pubDate>Tue, 22 Feb 2005 12:24:53 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>OS X</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2005/02/22/safari-double-dip-slow-dns-and-page-load-failure-final-fix/</guid>
		<description><![CDATA[After months of hitting the refresh button to get Safari to go to some sites, it seems I've finally found a fix.]]></description>
			<content:encoded><![CDATA[<p>This issue seems to have cropped up in Panther (10.3.5 or 10.3.6) <a href="http://www.macworld.com">Macworld</a> covered it in their September 2004 issue, but their fix didn&#8217;t seem to help me.  It&#8217;s been posted on <a href="http://www.tek-tips.com/viewthread.cfm?qid=944197&#038;page=7">numerous</a> <a href="http://www.macfixit.com/article.php?story=20041102083044472">bulletin boards</a> and people seem to be of the impression that 10.3.6 fixed the problem for them.  </p>
<p>The Codepoet found that <a href="http://www.codepoetry.net/archives/2004/11/03/doubledip_dns.php">turning off IPv6</a> worked for himself.  This had no effect for me.  It seems that the general consensus is either that the new handling of IPv6 records by DNS servers or slow ping times to DNS servers was to blame for this new bug.</p>
<p>The Mac Fix it article above eluded to my final solution.  If the problem had to do with other people&#8217;s DNS servers, why not run one locally.  I went into <code>/etc/hostconfig</code> and changed the DNSSERVER line to read: <code>DNSSERVER=-YES-</code> (open with sudo).  Problem fixed.  This seems to have worked on both my G5 and my girlfriend&#8217;s iBook.  Finally for good measure I added <code>0 0 1 * * root /usr/bin/curl ftp://ftp.rs.internic.net/domain/named.root &gt; /var/named/named.ca</code> to <code>/etc/crontab</code> so that the root server list will get updated once a month.  Thanks  to the O&#8217;rielly Mac Dev Center for a <a href="http://www.macdevcenter.com/pub/a/mac/2003/04/15/bind.html">bit of a BIND Tutorial.</a></p>
<p>If anyone knows a reason why it might be bad to run BIND on a laptop I&#8217;d love to hear it.  That was my only hesitation.
</p>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2005/02/22/safari-double-dip-slow-dns-and-page-load-failure-final-fix/feed/</wfw:commentRSS>
		</item>
		<item>
		<title>My latest million dollar idea</title>
		<link>http://elliot.ecowizards.com/wp/archives/2005/01/30/my-latest-million-dollar-idea/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2005/01/30/my-latest-million-dollar-idea/#comments</comments>
		<pubDate>Mon, 31 Jan 2005 02:28:47 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>Gift of Gab</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2005/01/30/my-latest-million-dollar-idea/</guid>
		<description><![CDATA[These come to me at the oddest times, thoughts about how to make the world just a little better, or more often, how to make a piece of technology a bit more fun.]]></description>
			<content:encoded><![CDATA[<p>These ideas come to me at the oddest times, thoughts about how to make the world just a little better, or more often, how to make a piece of technology a bit more fun.  My latest one happened when I was at <a href="http://www.massmoca.org">MASS MoCA</a> with a few friends.  We were in a dark room where there was a video playing of a car full of fireworks exploding super-imposed over Times Square.  At the end of the video it faded to black before repeating.  But the room was still lit by the LCD projectors that couldn&#8217;t quite do &#8220;black.&#8221;</p>
<p>My million dollar idea would make LCD projectors go all the way black.  It seems like a simple task, yet hasn&#8217;t been done to my knowledge.  </p>
<p>If anyone manages to use this theory, I&#8217;d appreciate some credit, or royalties, though I know that by putting this out on my public blog, for all 2 readers to read, I&#8217;m essentially giving my idea away.  </p>
<p>Here it goes. </p>
<p>Take an idea like those sunglasses that get darker when you walk outside and lighter when you go inside.  As far as I know those are just a photo sensitive skin placed on the lens.  If there were a way to tell those glasses to go totally dark immediately or totally light immediately you have just created an electronic shutter.  All you need is some material that when electrically activated (or deactivated) goes completely opaque.  Then you simply have to test the incoming signal to the projector and see if it is supposed to turn on any of its pixels.  If the answer is no, then you activate the shutter.  If a single pixel is supposed to be on, then you deactivate it.  The benefit over a real shutter is simply that it shouldn&#8217;t increase the internal heat inside the projector where a physical barrier would. </p>
<p>Alright, time for bed for me, I&#8217;ll see if this still looks good in the morning.
</p>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2005/01/30/my-latest-million-dollar-idea/feed/</wfw:commentRSS>
		</item>
		<item>
		<title>Rediscovering JavaScript</title>
		<link>http://elliot.ecowizards.com/wp/archives/2005/01/30/rediscovering-javascript/</link>
		<comments>http://elliot.ecowizards.com/wp/archives/2005/01/30/rediscovering-javascript/#comments</comments>
		<pubDate>Mon, 31 Jan 2005 00:53:04 +0000</pubDate>
		<dc:creator>Elliot</dc:creator>
		
	<category>CSS</category>
	<category>Coding</category>
		<guid isPermaLink="false">http://elliot.ecowizards.com/wp/archives/2005/01/30/rediscovering-javascript/</guid>
		<description><![CDATA[How to use JavaScript the "right" way, and how not to write confusing HTML, just to add JavaScript.]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s been a long time since I played around with JavaScript.  I was pretty much under the impression it was dead and basically made editing pages more difficult.  When I would go back and try and update pages that I had written long ago with a smattering of script in the header, and some strewn throughout to make things happen, I basically found that it was just too much work for the benefit it provided.  With CSS I could accomplish 90% of what I used to do with JavaScript and it was much easier to read and maintain.</p>
<p>I think I&#8217;m a born again JavaScript fiend.  After recently discovering the great site <a href="http://www.onlinetools.org/articles/unobtrusivejavascript/">Unobtrusive Javascript</a>, I&#8217;m once again sold.  This site makes it easy to separate the script from the CSS from the HTML, and all work harmoniously together.  </p>
<p>After playing around with their method of <a href="http://www.onlinetools.org/articles/unobtrusivejavascript/cssjsseparation.html">separating CSS and JS from the HTML</a> I was finally able to elegantly solve an issue I&#8217;ve been wanting a solution to for years.  I love how <a href="http://gmail.com">gmail</a> does the whole stack of cards thing with your email.  But more than that I love how they show and hide parts of the page arbitrarily.  When you are reading a message there is a nice little reply box down at the bottom that when you put your cursor in to write a reply gets bigger, but doesn&#8217;t refresh the page so you can continue to type.  And when you want to look back through some old messages all you do is click on the stack of cards and viola there you go, your old messages are right there.  </p>
<p>My dilemma was more simplistic.  I need to make a large form that I wanted to display on a single page, but not force people to scroll down through the whole thing, and I didn&#8217;t want people to have to click through sections of it because I want people to go right where they need to go without having to navigate a menu if they don&#8217;t want to.  And I wanted to allow people to check over the entire form before submitting it, so they could fill in the whole thing at once if they wanted to, or could submit just parts, and through the wonders of MYSQL they could come back later and finish up.</p>
<p>The solution I had in mind was to show the headings for the sections of the form, and then allow people to click on the header to expand or collapse the section of the form.  In addition I wanted a way to expand and collapse the whole form so that if you wanted to jump around from one section to another you could with minimal scrolling.</p>
<p>JavaScript to the rescue.  </p>
<p>Using the basic function provided by the fine folks over at Unobtrusive Javascript I was able to make a page where all h2 tags turn into buttons that expand and contract their sibling divs.  Then after some trial and error as I got my mind wrapped around what they were doing, I was able to take h3 tags and make them expand and contract all of the divs that immediately proceed h2 tags.  The beauty is that all the CSS and all of the JavaScript are in their own files, no body tag onload() functions, no extra classes or id&#8217;s even.  When CSS is needed for a specific application it is called and generated by the javascript, and the javacript file itself tells the HTML page to load it when the window loads, so all you have to change in your HTML is to include a single line telling it where to find the JavaScript file.  </p>
<p>Alright, enough talk here are the three files: (note this is part of a php file so the values for the form fields are self populating with php code, get rid of it if you don&#8217;t want this.)</p>
<h3>First the HTML File:</h3>
<p><code>&lt;html&gt;<br />
&lt;head&gt;<br />
&lt;/head&gt;<br />
&lt;body&gt;<br />
&lt;h3&gt;Collapse All Sections&lt;/h3&gt;<br />
&lt;form action="processupdates.php" method="post"&gt;<br />
&lt;h2&gt;Personal Inforamtion&lt;/h2&gt;<br />
&lt;div&gt;<br />
&lt;label for="FirstName"&gt;First Name: &lt;/label&gt;<br />
&lt;input type="text" id="FirstName" name="FirstName" value="First" size="30" maxlength="60" tabindex="1"/&gt;<br />
&lt;label for="LastName"&gt;Last Name: &lt;/label&gt;<br />
&lt;input type="text" id="LastName" name="LastName" value="last" size="30" maxlength="60" tabindex="2"/&gt;<br />
&lt;/div&gt;<br />
&lt;h2&gt;Other Inforamtion&lt;/h2&gt;<br />
&lt;div&gt;<br />
&lt;label for="FirstName"&gt;First Name: &lt;/label&gt;<br />
&lt;input type="text" id="FirstName" name="FirstName" value="&lt;? echo $line["FirstName"]; ?/&gt;" size="30" maxlength="60" tabindex="1"&gt;<br />
&lt;label for="LastName"&gt;Last Name: &lt;/label&gt;<br />
&lt;input type="text" id="LastName" name="LastName" value="&lt;? echo $line["LastName"]; ?/&gt;" size="30" maxlength="60" tabindex="2"&gt;<br />
&lt;/div&gt;<br />
&lt;h2&gt;More Inforamtion&lt;/h2&gt;<br />
&lt;div&gt;<br />
&lt;label for="FirstName"&gt;First Name: &lt;/label&gt;<br />
&lt;input type="text" id="FirstName" name="FirstName" value="&lt;? echo $line["FirstName"]; ?/&gt;" size="30" maxlength="60" tabindex="1"&gt;<br />
&lt;label for="LastName"&gt;Last Name: &lt;/label&gt;<br />
&lt;input type="text" id="LastName" name="LastName" value="&lt;? echo $line["LastName"]; ?/&gt;" size="30" maxlength="60" tabindex="2"&gt;<br />
&lt;/div&gt;<br />
&lt;br /&gt;<br />
&lt;input type="submit" id="Save Application" value="submit"/&gt;<br />
&lt;/form&gt;<br />
&lt;h3&gt;Collapse All Sections&lt;/h3&gt;<br />
&lt;/body&gt;<br />
&lt;/html&gt;</code></p>
<h3>Next the CSS:</h3>
<p><code>body{<br />
	background:#f8f8f8;<br />
	color:#333;<br />
	font-family:Arial, Helvetica, sans-serif;<br />
}<br />
h2{<br />
	font-size:110%;<br />
	font-weight:normal;<br />
	width: 40em;<br />
}<br />
h3{<br />
	font-size:110%;<br />
	font-weight:normal;<br />
	width: 40em;<br />
}<br />
.hidden{<br />
	display:none;<br />
}<br />
.shown{<br />
	display:block;<br />
}<br />
.trigger{<br />
	background:#ccf;<br />
	cursor:n-resize;<br />
}<br />
.open{<br />
	background:#66f;<br />
	cursor:s-resize;<br />
}<br />
.hover{<br />
	background:#99c;<br />
}</code></p>
<h3>Finally the JavaScript:</h3>
<p><code>function collapse()<br />
{<br />
//Collapses and expands siblings of h2 tags in html file<br />
	if(!document.createTextNode){return;}<br />
	var heads=document.getElementsByTagName('h2');<br />
	for(var i=0;i&lt;heads .length;i++)<br />
	{<br />
	//Figure out what should be opened and closed.<br />
		var tohide=heads[i].nextSibling;<br />
		while(tohide.nodeType!=1)<br />
		{<br />
			tohide=tohide.nextSibling;<br />
		}<br />
		//open all parts that should be open, and set their parents to be ready to close them<br />
		cssjs('add',tohide,'shown')<br />
		cssjs('add',heads[i],'trigger')<br />
		heads[i].tohide=tohide;<br />
		//Make them act more like links so the user gets a response<br />
		heads[i].onmouseover=function()<br />
		{<br />
			cssjs('add',this,'hover');<br />
		}<br />
		heads[i].onmouseout=function()<br />
		{<br />
			cssjs('remove',this,'hover');<br />
		}<br />
		heads[i].onclick=function()<br />
		{<br />
			if(cssjs('check',this.tohide,'hidden'))<br />
			{<br />
				cssjs('swap',this,'trigger','open');<br />
				cssjs('swap',this.tohide,'hidden','shown');<br />
			} else {<br />
				cssjs('swap',this,'open','trigger');<br />
				cssjs('swap',this.tohide,'shown','hidden');<br />
			}<br />
		}<br />
	}<br />
	var mainhead=document.getElementsByTagName('h3');<br />
	for(var i=0;i&lt;mainhead.length;i++)<br />
	{<br />
	//run through and make h3's into close and open all links<br />
		cssjs('add',mainhead[i],'trigger');<br />
		this.innerHTML = 'Collapse all Sections';<br />
		mainhead[i].onmouseover=function()<br />
		{<br />
			cssjs('add',this,'hover');<br />
		}<br />
		mainhead[i].onmouseout=function()<br />
		{<br />
			cssjs('remove',this,'hover');<br />
		}<br />
		mainhead[i].onclick=function()<br />
		{<br />
			heads=document.getElementsByTagName('h2');<br />
			if(cssjs('check',this,'trigger'))<br />
			//This runs through and opens all sections cleaning up the h2's so that they are ready to close again<br />
			{<br />
				for(var j=0;j&lt;heads.length;j++)<br />
				{<br />
					if(cssjs('check',heads[j],'trigger'))<br />
					{<br />
						cssjs('swap',heads[j],'trigger','open');<br />
						cssjs('swap',heads[j].tohide,'hidden','shown');<br />
					}<br />
				}<br />
				for(var l=0;l&lt;mainhead.length;l++)<br />
				{<br />
				//Makes sure that all h3s are ready to close the sections<br />
					mainhead[l].innerHTML = 'Expand all Sections';<br />
					cssjs('swap',mainhead[l],'trigger','open');<br />
				}<br />
			} else {<br />
			//This runs through and closes all sections cleaning up the h2's so that they are ready to open again<br />
				for(var k=0;k&lt;heads.length;k++)<br />
				{<br />
					if(cssjs('check',heads[k],'open'))<br />
					{<br />
						cssjs('swap',heads[k],'open','trigger');<br />
						cssjs('swap',heads[k].tohide,'shown','hidden');<br />
					}<br />
				}<br />
				for(var m=0;m&lt;mainhead.length;m++)<br />
				{<br />
				//Makes sure that all h3s are ready to re-open the sections<br />
					cssjs('swap',mainhead[m],'open','trigger');<br />
					mainhead[m].innerHTML = 'Collapse all Sections';<br />
				}<br />
			}<br />
		}<br />
	}<br />
}<br />
function cssjs(a,o,c1,c2)<br />
{<br />
//Applies action a to object o by adding, removing, swapping, or checking for classes c1 or c2.<br />
	switch (a){<br />
		case 'swap':<br />
	o.className=!cssjs('check',o,c1)?o.className.replace(c2,c1):o.className.replace(c1,c2);<br />
		break;<br />
		case 'add':<br />
			if(!cssjs('check',o,c1)){o.className+=o.className?' '+c1:c1;}<br />
		break;<br />
		case 'remove':<br />
			var rep=o.className.match(' '+c1)?' '+c1:c1;<br />
			o.className=o.className.replace(rep,'');<br />
		break;<br />
		case 'check':<br />
			return new RegExp('b'+c1+'b').test(o.className)<br />
		break;<br />
	}<br />
}<br />
//make it all happen when the page loads<br />
window.onload=collapse;</code></heads></code>
</p>
]]></content:encoded>
			<wfw:commentRSS>http://elliot.ecowizards.com/wp/archives/2005/01/30/rediscovering-javascript/feed/</wfw:commentRSS>
		</item>
	</channel>
</rss>
